Chapter 12. Geometry Management

This chapter discusses how composite and constraint widgets manage the layout of widgets, and how to write your own simple composite and constraint widgets.

Geometry managing widgets lay out your application's widgets on screen according to certain rules. You cannot hardcode the position of widgets in an application because X applications can be resized and they must reposition their widgets to take advantage of the available space. Because of the window manager, even the initial size of the application may not be the application's preferred size.

Chapter 3, More Techniques for Using Widgets, demonstrated how you can use existing composite widgets such as RowColumn and constraint widgets such as Form in the application. You can control widget layout rules with resources. However, you may find that no existing composite or constraint widget can be configured with resources to have the layout rules you need. In this case, you will need to write your own composite or constraint widget or modify an existing one. However, before embarking on writing one of these widgets, you should realize that composite and constraint widgets are complex. First investigate the alternatives! Perhaps you can find a composite or constraint widget from another widget set that has the layout characteristics you need. If you determine that you have no alternative but to write your own composite or constraint widget, you should keep it as simple as possible. It is much easier to write a special-purpose widget that handles a limited layout situation than it is to write a general-purpose composite or constraint widget like RowColumn or Form.

A composite widget is defined as any widget that is a subclass of the Xt-defined class Composite. A constraint widget is any widget that is a subclass of the Xt-defined class Constraint. Constraint is a subclass of Composite. As you may recall, a composite widget is the simplest kind of geometry-managing widget; it handles all its children equally, or handles each child in a fixed way. For example, the RowColumn widget handles all of its children equally. As an example of a special-purpose composite widget, Section 12.2, "Writing a Composite Widget" describes a composite widget called ScrollBox that manages two scrollbars and a main window. This widget requires that it have exactly three children added in a particular order.

A constraint widget has all the characteristics of composite widgets but maintains configurable data about each child so that it can cater to the needs of each child. By setting the constraint resources of a child of a constraint widget, you configure the constraint widget's layout policy for that child. Constraint widgets are inherently more powerful than composite widgets, but are also more complicated to write. A constraint widget requires all the code of a composite widget, plus code to handle the constraints of each child. Because of this complexity, you should hesitate even further before attempting to write a constraint widget. As an example of the code for a constraint widget, this section describes the Athena Form widget. (The Motif Form widget is too complex to be described here.)

Composite and constraint widgets can be used within widgets as well as within applications. For example, you may recall that the xbitmap application was implemented using the BitmapEdit widget that appeared inside a ScrolledWindow or MainWindow widget that in turn added scrollbars. We could rewrite the BitmapEdit widget to provide its own scrollbars, controllable through resources. Although this wouldn't be worth the effort, it could lead to better scrolling performance. This new widget, which might be called ScrolledBitmap, would be a composite widget and have three children: BitmapEdit and two ScrollBar widgets. This kind of compound widget is actually made up of several widgets, but to the application writer it should act like a single widget. The advantage of this rewrite would be that the application code would become simpler, and the bitmap editor with scrollbars could be easily used in other applications. The fourth major section of this chapter describes how to write a compound widget.

Chapter 13, Menus, Gadgets, and Cascaded Popups, describes how to write a widget capable of being the parent of gadgets, which are windowless widgets.

It is a good idea to define exactly what is meant by the geometry of a widget. The geometry of a widget is its position, its size, and its border width. These are the Core fields x, y, width, height, and border_width. Border width is included because window positions are measured from the origin of the parent (the top-left corner inside the border of the parent) to the top-left corner outside the border of the child. Therefore, changing the border width of a widget by 1 pixel moves the origin of that widget 1 pixel along both the x and y axes relative to its parent. This concept is shown in Figure 12-1.

Geometry management may also control the stacking order of a group of children (that is, which children appear to be on top). However, control of the stacking order requires more programmer effort than control of geometry, because it isn't completely built into Xt.

Figure 12-1. Role of border width in widget geometry


How Composite Management Works

Like simple widgets, the characteristics of composite widgets are defined by their methods. The key methods in a composite widget are the Core methods resize and query_ geometry and the Composite methods geometry_manager and change_managed. We will discuss these methods first, and then move on to the other methods defined by Composite, insert_child and delete_child, which are infrequently used.

We'll begin with a summary of what these four most important methods do. In short, they handle interactions with the three generations of widgets involved in direct geometry interactions with a composite widget: the parent of the composite widget, the composite widget itself, and the children of the composite widget.

  • The resize method moves and resizes the child widgets as necessary to fit within the composite widget's new size.

  • The query_geometry method supplies a preferred geometry to a widget's parent when the parent calls XtQueryGeometry(). The parent makes this call in the process of determining a new layout for its children.

  • The geometry_manager method handles resize requests from the child widgets. Usually, the only kind of child that will make a resize request to the parent is another composite or constraint widget. However, some simple widgets do request resizing. For example, when a Label widget's string is changed through resources, the Label widget increases its own size (this is allowed only in the set_values method). Xt then calls the parent's geometry_manager method, which must decide whether this new size is acceptable and then make any changes. When a child widget asks for more space and the composite widget doesn't have enough, the composite widget's geometry_manager may ask its own parent for more space by calling XtMakeGeometryRequest().

  • The change_managed method changes the layout of the children when a child is managed or unmanaged. A child is managed initially when XtRealizeWidget() is called (after children are created with XtCreateManagedWidget()) or when XtManageChild() or XtManageChildren() is called to add an already-realized widget to a composite parent's managed set. (A widget can be unmanaged later to remove it from the screen without destroying it, and then managed again at any time. Each time a child is managed or unmanaged, or destroyed, the change_managed method is called.)

The resize and query_geometry methods were already introduced in Chapter 7, Basic Widget Methods, as they are written in simple widgets. In composite widgets, they have the same job, but it is more complicated because they now have children to worry about. We will discuss these methods again in this chapter as they appear in composite widgets.

To write any of these four methods, you need to look at all the ways that geometry changes can occur, so that you know all the situations in which the methods will be called. Here are the four most important situations:

  • During the negotiation that takes place to determine the initial size of each widget when an application starts up.

  • When the user resizes the entire application.

  • When a widget requests a size change from the application.

  • When a widget is resized by the application.

As you can see, there are many cases. It helps to be systematic about understanding and programming for these cases. The next four sections describe each of these cases one at a time.

The complexity of the geometry negotiations in the following description may be intimidating. A truly general-purpose composite widget is a large, complex piece of software. You should leave this programming to the widget writers who write commercial widget sets, and concentrate on things that are more important in your application. The purpose of this description is to let you understand how complete composite widgets work, not to suggest that you should try to write one. However, it is possible to write small, special-purpose composite widgets that solve particular layout problems. Composite widgets are simpler when they are more authoritarian--when they don't do as much to try to satisfy the preferences of their children. Section 12.2, "Writing a Composite Widget" describes ScrollBox, a simple composite widget designed solely to manage a main widget and two scrollbars. Writing this kind of widget is manageable because the widget manages a fixed number of children and has simple layout rules.

Initial Geometry Negotiation

At least one geometry negotiation takes place in any application, even if the application is never resized. This geometry negotiation occurs when an application starts up.

Figure 12-2 shows the process of initial geometry negotiation in schematic form.

Figure 12-2. Initial geometry negotiation, assuming sufficient shell space

The call to XtRealizeWidget() initiates a two-step process. When XtRealizeWidget() is called on the top-level widget, most or all of the widgets have been created but windows have not been created for them. XtRealizeWidget() initiates a geometry negotiation that ripples through the widget hierarchy (as is described below). The widgets' realize methods (which create windows for the widgets) are not called until this process is complete.

XtRealizeWidget() first calls the change_managed method of every composite widget in the application, beginning with the lowest widgets in the hierarchy (called a post-order traversal). Each change_managed method determines an initial size for each child and calls XtMoveWidget() and/or XtResizeWidget() for the child. All the change_managed methods are called until the one in the Shell widget. (Remember that the Shell widget is a composite widget that has only one child.) The child is also a composite widget (except in single-widget applications such as xhello). When the Shell widget is reached, the Shell widget size is set to the size of its child and the process stops unless the user has specified an initial geometry for the entire application through the resource database or command line.

A change_managed method can (but is not required to) determine each of its children's preferred geometry by calling XtQueryGeometry() for each child. This will result in the query_geometry method of each child being called. Instead of calling XtQueryGeometry(), the change_managed method may use the child's Core width and height fields.

The change_managed method does not determine the composite widget's own size. That job is for the parent of the composite widget, which is another composite widget.

If the user-specified, top-level widget geometry is different from the geometry of the Shell widget's child after all the change_managed methods are called, then the Shell widget resizes its child to the user-specified size. This makes Xt call the resize method of the child composite, and this resize method reconsiders the layout of its children. This process proceeds down the chain of widgets to the bottom. At each stage the resize method can (but need not) call XtQueryGeometry() for each child to get each child's opinion of the intended geometry for that child.

Figure 12-3 shows the continued process of initial geometry negotiation if the user has specified the top-level geometry through resources rather than accepting the application's built-in defaults.

Figure 12-3. Initial geometry negotiation, if resizing is necessary

Note that this process and the methods involved are more complicated if the change_managed or resize methods call XtQueryGeometry(). XtQueryGeometry() calls a child widget's query_geometry method, as described in Section 12.2.5, "XtQueryGeometry and the query_geometry Method." When the change_managed or resize methods call XtQueryGeometry(), they need to propose a complete geometry for the child by setting fields in the arguments of the XtQueryGeometry() call, and then do different things depending upon which of the three different answers it receives from the child. The XtGeometryAlmost answer includes a suggested compromise. The change_managed or resize method will decide on a new size based on the compromise, and may or may not make another XtQueryGeometry() call to make this suggestion to the child. The code to perform all of this is extensive because the suggestions and answers are in the form of structures with several fields. Only the Frame, MainWindow, and ScrolledWindow classes in the Motif set call XtQueryGeometry().

Notice that this process can result in the resize, change_managed, and possibly the query_geometry methods of every widget being called, but the geometry_manager method does not play a part.

Once this process is complete, XtRealizeWidget() calls the realize methods of all the widgets and actually creates windows for them. Up to this point, all the methods were simply changing the widget size and position parameters in Xt structures, not the sizes of actual windows. XtRealizeWidget() also maps all managed widgets.

User Resizes the Application

When XtRealizeWidget() returns, most applications call XtAppMainLoop(). Internally, this calls the Xlib call XNextEvent() which sends the batch of queued window creation and window map requests to the server.[82]Most window managers can intercept the mapping request for the top-level window and draw a rubber-band outline of the application on the screen, ready for the user to place or resize the application. With certain settings mwm can either place windows for you or allow you to place and size them. If the user simply places the application, no new geometry negotiation takes place. But if the user resizes the application, a new round of geometry negotiation takes place, identical to the process described above where the user specified a top-level widget geometry.

In other words, the process that occurs when the user resizes the application with the window manager is the same as when the user specifies top-level widget geometry with resources or the command line. This is also the same as when the user later resizes the existing application. This process was illustrated in Figure 12-2 and Figure 12-3.

This process uses all the methods except geometry_manager.

Widget Desires a Size Change

When an application sets a widget resource that affects what is displayed in the widget, it may be logical for the widget to ask its parent for a new size. This would occur in the set_values method of the widget. The widget sets its desired geometry into its Core geometry fields (width, height, x, y, and border_width). Xt finishes calling all the set_values methods (because they chain), and then calls XtMakeGeometryRequest() to ask the parent widget for a geometry change. Note that the set_values and initialize methods are the only place where widgets are allowed to set their own size directly. (In set_values they can do so only because of an XtVaSetValues() call to change a resource that affects geometry.)

Figure 12-4 shows what happens when a widget requests a size change.

Figure 12-4. A widget requesting a size change

Of course, composite widgets often want a size change because their children have asked to be resized. The composite widget has no knowledge of what caused the child's size change. Therefore, composite widgets call XtMakeGeometryRequest() themselves to see if their parent will allow them to change size. Composite widgets should request such a change; otherwise their parent may have unused space or have other children that need more space. However, handling the variety of responses to the request is not trivial.

For both simple and composite widgets, Xt calls the geometry_manager method of the parent widget when XtMakeGeometryRequest() is called, and the method is responsible for deciding whether to except, reject, or compromise on the requested geometry. The geometry_manager method may have to ask its parent to decide whether it can accept its child's proposal. If so, it makes another XtMakeGeometryRequest() call. This can go on to arbitrary depth, and the final answer will trickle back down to the parent of the original requestor. The XtMakeGeometryRequest() call itself will change the child's geometry if the answer is yes. If the answer is no, the child gets this information and may try another geometry. If the answer is a compromise, then the child will get the compromise information returned in its call to XtMakeGeometryRequest() and make another request to be able to proceed. More on this interaction in Section 12.2.6, "XtMakeGeometryRequest and the geometry_manager Method."

This process uses only the geometry_manager methods of each widget. However, this method may call XtQueryGeometry() to determine the needs of each of its children before replying to the XtMakeGeometryRequest() request. When the process is finished, the resize method is called for any widgets that have been resized. However, the change_managed method is never involved.

Application Resizes a Widget

An application may change the size of a widget by setting the XtNx, XtNy, XtNwidth, XtNheight, or XtNborderWidth resources directly (as opposed to setting a resource which indirectly affects the size, as covered in the last section). If that widget has no children, the widget code does nothing, but Xt queries the parent with an XtMakeGeometryRequest(). The complete negotiation process as described for XtRealizeWidget() is repeated, except that the current top-level Shell size is used as a user-specified size (because ultimately it is).

Although the widget code did nothing, the set_values method sets the new values specified through resources into the Core fields. If the XtMakeGeometryRequest() request is denied by the parent, Xt sets these Core fields back to their original values. If the parent suggests a compromise, the set_values_almost method, which is described below, is called.

If the widget whose size is changed is a widget with children, the negotiation process is the same, except that when it is complete the children of the widget need to be laid out, and if the children have children they need to be laid out too, and so on.

Writing a Composite Widget

The process of writing a composite widget is the same as writing a simple widget. You copy the three files of some existing composite widget, perhaps Composite itself, make global name changes to make the files a skeleton for a distinct class, and then write new methods.

As mentioned earlier, writing a general-purpose composite widget is not a trivial task and should be done only when other options fail. Because composite widgets have no user interface, you may be able to find a composite widget with the proper characteristics from another widget set. There are several public domain widget sets to look in. Note, however, that commercial widget set vendors may design in a private protocol between their composite widgets and their children, which make the composite widgets unable to correctly manage widgets from other sets. And if you are writing a commercial product, you may have to pay a binary license fee to the commercial widget set vendor for each copy of your product. Especially for large software houses, it is a good idea to have at least one programmer adept at writing composite widgets and the query_geometry methods of simple widgets.

Small scale composite widgets that handle a small set of circumstances are not difficult to write, because you can make a number of simplifying assumptions. As an example, this section describes a very simple composite widget called ScrollBox, which is designed to manage a BitmapEdit widget (or any other main widget) and two ScrollBar widgets. It handles only these three widgets, and they must be added in a particular order. Because we know the geometry preferences of these three widgets (the bigger the better, but no size in particular), we can dispense with querying them about their preferred geometry. We also know that none of our children will request to be resized. Therefore, we do not need a geometry_manager method.

A ScrollBox widget is shown managing BitmapEdit and two scrollbars in Figure 12-5.[83]

Figure 12-5. A ScrollBox widget at two different sizes

The Athena Viewport widget does scrollbar management in a more general way than does ScrollBox. It is a subclass of Form that takes any main window as a child and creates scrollbars. It shows only a small portion of the main window and uses the scrollbars to determine which portion of the main window is shown. But Viewport doesn't work well with BitmapEdit because BitmapEdit has a built-in ability to display in a smaller window that conflicts with Viewport's efforts. Besides, Viewport is several times larger and more complicated than ScrollBox, because it includes the scrollbar callback functions and because it honors a child's geometry preferences. ScrollBox is a modest widget that manages the geometry of scrollbars, leaving their connection with the main window up to the application. This demonstrates the essential elements of a composite widget without too much complication.[84]

The ScrollBox widget code, along with a version of xbitmap that uses it, is available in the example source in the ch09 directory. It lays out its children by adjusting the width and height of the three children so that they fill the ScrollBox widget, while keeping the width of the Scrollbar widgets constant. The width of the Scrollbar widgets is set through their resources and is never modified by ScrollBox.

The first few sections below describe the methods that are used in ScrollBox and that are required in all composite widgets. First, we discuss the Core initialize, realize, set_values, resize, and query_geometry methods as they are used in composite widgets. Then, we discuss how the ScrollBox widget implements layout calculations in a common routine called by the set_values, resize, and change_managed values. This is followed by further discussion of the change_managed and query_geometry methods. Then, we go on to discuss the methods not used by ScrollBox, but that would be used in more complicated composite widgets, in particular geometry_manager. Finally, we briefly discuss the methods available in composite widgets but rarely needed: set_values_almost, insert_child, and delete_child.

Basic Core Methods in Composite Widgets

Both composite and constraint widgets are subclasses of Core. Therefore, they have all the Core methods described in Chapters 6 and 7. However, since composite and constraint widgets usually have no input and output semantics, the expose method is set to NULL and the widget has no default translation table or actions. As a result, all the event-oriented fields in the Core class structure become irrelevant to composite and constraint widgets.

But composite and constraint widgets do use the Core initialize, realize, and set_values methods. These methods have the same roles as for simple widgets. The initialize method initializes instance part variables and checks initial resource values. The realize method sets window attribute values and then creates a window for the widget. The set_values method updates any instance part fields that depend on resources. Since composite and constraint widgets don't need GCs, initialize and set_values don't contain code to create and change GCs as in simple widgets.

These three methods for ScrollBox are absolutely minimal, and call a common routine called DoLayout when any actual sizing or positioning of widgets is required. The initialize method simply sets the widget's default width and height, the realize method is inherited, and the set_values method changes the layout of children when either of ScrollBox's two resources is changed. These resources control the vertical and horizontal distance in pixels that will be left between the Scrollbar widgets and the main widget, and between each of these widgets and the borders of ScrollBox. Example 12-1 shows the set_values method of ScrollBox.

Example 12-1. ScrollBox: the set_values method

/* ARGSUSED */
static Boolean SetValues(current, request, new, args, num_args)
Widget current, request, new;
ArgList args;
Cardinal *num_args;
{
    ScrollBoxWidget sbwcurrent = (ScrollBoxWidget) current;
    ScrollBoxWidget sbwnew = (ScrollBoxWidget) new;
   /* need to relayout if h_space or v_space change */
    if ((sbwnew->scrollBox.h_space != sbwcurrent->scrollBox.h_space) ||
            (sbwnew->scrollBox.v_space !=
            sbwcurrent->scrollBox.v_space))
        DoLayout(sbwnew);
    return False;
}


Two more Core methods are used in composite widgets: resize and query_geometry. The resize method changes the layout of its children and is shown in Example 12-2.

Example 12-2. ScrollBox: the resize method

static void Resize(w)
Widget w;
{
    ScrollBoxWidget sbw = (ScrollBoxWidget) w;
    DoLayout(sbw);
}


The query_geometry method answers the parent's inquiry about a size change for this composite widget and is shown in Example 12-3.

Example 12-3. ScrollBox: the query_geometry method

/* ARGSUSED */
static XtGeometryResult QueryGeometry(w, request, reply_return)
Widget w;
XtWidgetGeometry *request, *reply_return;
{
    XtGeometryResult result;
    request->request_mode &= CWWidth | CWHeight;
    if (request->request_mode == 0)
    /* parent isn't going to change w or h, so nothing to
     * re-compute */
       return XtGeometryYes;
    /* if proposed size is large enough, accept it.  Otherwise,
     * suggest our arbitrary initial size. */
    if (request->request_mode & CWHeight) {
        if (request->height < INITIAL_HEIGHT) {
            result = XtGeometryAlmost;
            reply_return->height = INITIAL_HEIGHT;
            reply_return->request_mode |= CWHeight;
        }
        else
            result = XtGeometryYes;
    }
    if (request->request_mode & CWWidth) {
        if (request->width < INITIAL_WIDTH) {
            result = XtGeometryAlmost;
            reply_return->width = INITIAL_WIDTH;
            reply_return->request_mode |= CWWidth;
        }
        else
            result = XtGeometryYes;
    }
    return(result);
}


Although the query_geometry method has the same role in all widgets, composite and simple, a composite widget's size preference depends on its children. Normally this means the query_geometry method will query its children and try different layouts until it arrives at the geometry, or some approximation of it, suggested by its parent. This calculation is complicated because the widget may have any kind of child, and their responses to geometry suggestions are unpredictable. ScrollBox ignores this complexity because it knows exactly what kinds of children it will have and what their characteristics are. Therefore, its query_geometry method is basically the same as the query_geometry method of a simple widget.

To be more precise, what this query_geometry method does is accept any size suggested by the parent which is larger than the minimum useful size of the application. When the suggested size is too small, the query_geometry method uses the minimum useful size as a compromise. Note, however, that this is really hardcoding the characteristics of the child into our composite widget. It would be better to add resources to control the minimum useful size.

Laying Out Child Widgets

Composite widgets need to calculate a layout and manipulate their child widgets from set_values, from resize, and from change_managed. Therefore, in most composite widgets this common code is placed in a single routine called DoLayout. Example 12-4 shows the DoLayout routine from ScrollBox.

Example 12-4. ScrollBox: private routine to lay out child widgets

/* ARGSUSED */
static DoLayout(w)
Widget w;
{
    ScrollBoxWidget sbw = (ScrollBoxWidget) w;
    Widget main, vscroll, hscroll;
    Widget child;
    Dimension mw, mh; /* main window */
    Dimension vh;     /* vertical scrollbar length (height) */
    Dimension hw;     /* horizontal scrollbar length (width) */
    Position vx;
    Position hy;
    int i;
    if (sbw->composite.num_children != 3)
        XtAppError(XtWidgetToApplicationContext(sbw),
                "ScrollBox: must manage exactly three widgets.");
    for (i = 0; i < sbw->composite.num_children; i++) {
        child = sbw->composite.children[i];
        if (!XtIsManaged(child)) {
            XtAppError(XtWidgetToApplicationContext(sbw),
                "ScrollBox: all three widgets must be managed.");
        }
    }
    /* Child one is the main window, two is the vertical scrollbar,
     * and three is the horizontal scrollbar. */
    main = sbw->composite.children[0];
    vscroll = sbw->composite.children[1];
    hscroll = sbw->composite.children[2];
    /* Size all three widgets so that space is fully utilized. */
    mw = sbw->core.width - (2 * sbw->scrollBox.h_space) -
            vscroll->core.width - (2 * vscroll->core.border_width)
            - (2 * main->core.border_width);
    mh = sbw->core.height - (2 * sbw->scrollBox.v_space) -
            hscroll->core.height - (2 * hscroll->core.border_width)
            - (2 * main->core.border_width);
    vx = main->core.x + mw + sbw->scrollBox.h_space +
            main->core.border_width + vscroll->core.border_width;
    hy = main->core.y + mh + sbw->scrollBox.v_space +
            main->core.border_width + hscroll->core.border_width;
    vh = mh; /* scrollbars are always same length as main window */
    hw = mw;
    XtResizeWidget(main, mw, mh);
    XtResizeWidget(vscroll, vscroll->core.width, vh);
    XtMoveWidget(vscroll, vx, vscroll->core.y);
    XtResizeWidget(hscroll, hw, hscroll->core.height);
    XtMoveWidget(hscroll, hscroll->core.x, hy);
}


In general, DoLayout moves and resizes the child widgets according to its layout policy. This routine may query the children with XtQueryGeometry() before making decisions, but it is not required to. In this case, there is no need to because ScrollBox handles only two types of widgets with no size preferences.

DoLayout is passed only one argument, ScrollBox's own widget ID (a pointer to its widget instance structure). But the composite children field in ScrollBox's instance structure is an array of the IDs of all the children, and num_children is the number of children.[85]

When each child is added to a composite widget, its ID is added to the children field of the composite part of the instance structure, and the num_children field is incremented. Therefore, the code to lay out the children is usually a loop that treats each child one at a time. This often takes two passes, since the routine needs to know which children are managed before it can determine their final geometries. All children, even unmanaged ones, are listed in the children and num_children fields.

This particular DoLayout procedure makes sure that there are exactly three children and that they are all managed. Then, it calculates the positions and sizes for all the children so that they will fill all the available space in ScrollBox's own window. Finally, it calls XtResizeWidget() and XtMoveWidget(), which check to see if there was any change before making Xlib calls to move and resize the windows.

The change_managed Method

In every composite widget, the change_managed method is called once (and only once, even when there are multiple children) during the XtRealizeWidget() process to determine an application's initial layout. change_managed is also called when an application later unmanages a managed widget or manages an unmanaged widget (as long as the XtNmappedWhenManaged resource has its default value). Therefore, change_managed also calls DoLayout.

An application unmanages a widget to remove the widget from visibility without destroying it, and at the same time to tell the composite widget to change the layout of the remaining widgets to fill the gap. This is done by calling XtUnmanageChild() or XtUnmanageChildren(). The application can then make the composite widget redisplay the widget by calling XtManageChild() or XtManageChildren(). This response depends on the Core XtNmappedWhenManaged resource having its default value, True. When set to False, the management state has no effect on mapping, and the application must call XtMapWidget() and XtUnmapWidget() instead. Usually an application does this so that a widget will become invisible without triggering a re-layout to fill in the space it has vacated. Therefore, change_managed need not check the XtNmapWhenManaged resource of each child. Example 12-5 shows the change_managed method of ScrollBox.

Example 12-5. A basic change_managed method

static void ChangeManaged(w)
ScrollBoxWidget w;
{
    ScrollBoxWidget sbw = (ScrollBoxWidget) w;
    DoLayout(sbw);
}


The geometry_manager Method

The geometry_manager method responds to requests by the children to be resized. ScrollBox does not manage widgets that request to be resized, so theoretically it should not need a geometry_manager, but as of R4, Xt requires that all composite widgets have one. ScrollBox's geometry manager method is very simple, and is shown in Example 12-6.

Example 12-6. A simple geometry_manager method

static XtGeometryResult
GeometryManager(w, request, reply)
Widget w;
XtWidgetGeometry *request;
XtWidgetGeometry *reply;
{
    return XtGeometryYes;
}


Section 12.2.6, "XtMakeGeometryRequest and the geometry_manager Method" describes what would take place in a more complex geometry_manager, and Section 12.4.6, "The geometry_manager Method" provides an example.

You have now seen all the code in ScrollBox! To summarize, a very basic composite widget such as ScrollBox has a standard initialize method, resize and change_managed methods that just call DoLayout, and a set_values method that calls DoLayout when any resource that affects layout is changed. The DoLayout routine actually lays out the children. The widget's query_geometry method is basically just like a simple widget's query_geometry. Now we'll move on to describe what may be added to this skeleton to make more fully-featured composite widgets.

XtQueryGeometry and the query_geometry Method

We have mentioned that XtQueryGeometry() calls a child's query_geometry method, but not the details of how this works. The query_geometry method for simple widgets is described in Chapter 7, Basic Widget Methods. The role of this method in composite widgets is the same, but the details of its job are different. You may recall that this method is passed pointers to two XtWidgetGeometry structures, one which specifies the parent's proposed geometry, and the other which is used by the child to return a compromise geometry. These two structures are allocated by the method that calls XtQueryGeometry() and passed as pointers to that call. The XtGeometryResult enum returned by the query_geometry method is passed right through as the returned value of XtQueryGeometry().

Composite and constraint widgets play the role of both parent and child. When you write a composite widget, you may call XtQueryGeometry() in several places to get the child's response to your proposed size. You will also need to write a query_geometry method so that your composite can respond to its parent's XtQueryGeometry() request.

A query_geometry method in a composite widget should base its response on the size preferences of its children. It should calculate a new layout based on the proposed geometry passed in, and then query its children to get their opinions of their new geometry. If any of the children is a composite widget, they may query their children, and so on. Therefore, these requests tend to trickle down to the lowest widget in the hierarchy. ScrollBox took the biggest shortcuts in its query_geometry method. Not only didn't it query its children, but it hardcoded its response based on the characteristics of the kind of main window it expected. This would be the first place to begin improving ScrollBox.

Note, however, that a composite widget is allowed to be authoritarian and not ask its children whether they like the sizes they are about to be given. However, this kind of composite widget will not be suitable as a parent of a widget that really needs certain size preferences.

A parent must specify a complete proposed geometry when calling XtQueryGeometry(), not just the changes it intends to make.

XtMakeGeometryRequest and the geometry_manager Method

XtMakeGeometryRequest() calls are made for two reasons. First, when a composite widget honors its children's size preferences, it may find that its current size is inadequate to lay out its children. In this case, it should ask its parent to be resized by calling XtMakeGeometryRequest(). Second, Xt calls XtMakeGeometryRequest() for a widget when the application has changed a resource that affects geometry.

As mentioned above, XtMakeGeometryRequest() calls the parent's geometry_ manager method. The parent's geometry_manager has the job of deciding whether the size proposed by the child is acceptable. A subclass of Composite must either define a geometry_manager method, or set this field in the class structure to NULL, because there is no default method to inherit. The XtInheritGeometryManager symbol can be used only in subclasses of a class that defines a geometry_manager method. Any composite widget allowing its children to suggest resizing will require a geometry_manager method of its own.

The way the arguments and returned values are passed between XtMakeGeometryRequest() and the parent's geometry_manager method is almost exactly parallel to the way XtQueryGeometry() calls the child's query_geometry method. Both calls take pointers to two structures of the same types where one is used for a returned compromise. Both take no more arguments other than the widget ID. Both return an enum value of type XtGeometryResult. The returned value of the geometry_manager method is, generally speaking, passed through as the returned value of XtMakeGeometryRequest(). Review Section 7.6, "The query_geometry Method" so that these structures, their fields and values, and the returned values are fresh in your mind.

One difference between the way the query_geometry and geometry_manager methods are invoked is that the geometry_manager method can return a fourth enum value, XtGeometryDone (in addition to XtGeometryYes, XtGeometryNo, and XtGeometryAlmost). The return codes of the geometry_manager method are summarized in Table 12-1.

Table 12-1. Return Codes of geometry_manager Method

Code

Description

XtGeometryNo

Requested change is denied

XtGeometryAlmost

A compromise is suggested

XtGeometryYes

Requested change is accepted, let XtMakeGeometryRequest() make change

XtGeometryDone

Requested change is accepted, I have made change


XtGeometryDone means that geometry_manager approves of the change and has actually made the change. XtMakeGeometryRequest() never returns XtGeometryDone though; it returns XtGeometryYes when the geometry_manager returns XtGeometryYes or XtGeometryDone. When the geometry_manager returns XtGeometryYes, the XtMakeGeometryRequest() call itself makes the size change. All these shenanigans simply allow the parent to make the size change by calling its normal layout code or to let XtMakeGeometryRequest() do it, depending on which is most convenient.

The second difference is that XtMakeGeometryRequest() and the geometry_manager method interpret the XtGeometryNo returned value differently. For XtMakeGeometryRequest() and geometry_manager, it has its intuitive meaning that the requested change is denied. For query_geometry and XtQueryGeometry(), the symbol should really be XtGeometryNoChange. Since this symbol doesn't exist, XtGeometryNo has to do double duty, meaning in this case that the proposed size and current size are the same.

The final difference is an additional mask for the request_mode field of the XtWidgetGeometry structure that contains the proposed change. In XtMakeGeometryRequest() requests, the mask XtCWQueryOnly can be ORed with the masks that identify which fields in the proposed geometry the child considers important. This indicates that the proposed change should not be made, but that the geometry_manager method should fill in the return structure with the changes it would have made. This flag is used whenever a widget is making an XtMakeGeometryRequest() from its geometry_manager method: these requests are intermediate (triggered by requests from children). The composite widget does not actually want to be resized until it has made a suggestion for its own size to its parent, received an answer from the parent, recalculated the layout of its children, and queried its children, if necessary, to see that the new size is adequate for everybody.

This also makes it obvious that the geometry_manager method you write for your composite widget must be prepared to handle the XtCWQueryOnly mask. It should calculate a layout but not actually move or resize any widgets.

ScrollBox does not need a geometry_manager method because it knows that its children will never make geometry requests. However, any composite widget that accepts all kinds of children requires a geometry_manager method. In Section 12.4.6, "The geometry_manager Method" below, the geometry_manager method of the Form widget is shown and described.

Similar to XtMakeGeometryRequest(), but less general, is XtMakeResizeRequest(). Instead of passing two structures, XtMakeResizeRequest() passes two width and height pairs. Otherwise, the results of this call are the same.

Shell widgets (which are a subclass of Composite) have an extension structure (see Section 14.13, "Class Extension Structures") that contains a root_geometry_manager field. This field is a pointer to a function which acts like the geometry_manager method that would exist in the composite parent of the Shell widget, if Shell had one. This method is needed because the only “parent” of Shell is the window manager. Since it is unlikely that you will need to write your own Shell widget, you are unlikely to have to write or even read a root_geometry_manager method.

The set_values_almost Method

As mentioned above, a child widget may request a geometry change for one of three reasons:

  • The application just called XtSetValues() and set a geometry field. In this case, Xt makes the geometry request to the parent to allow the parent to overrule or modify the change.

  • The application just called XtSetValues() and set a resource that affects geometry, like the string displayed in a Label widget. The set_values method of the child changes the geometry fields in the widget directly. Then Xt makes a geometry request to the parent, to allow the parent to overrule or modify the change.

  • The child may decide it needs more or less room because of some kind of user input, or because its own children need more or less room. In this case, it calls XtMakeGeometryRequest() itself, and handles the various returned values itself. If the widget wants to use set_values_almost to make compromise suggestions (since it is designed for that purpose and may be there anyway), the widget will have to call the method itself.

In the first two cases, Xt calls XtMakeGeometryRequest(), while in the third case, the child must call the function itself. If the returned value is XtGeometryYes, the XtMakeGeometryRequest() call itself (or Xt) has resized the child.

When XtMakeGeometryRequest() is called by Xt, and its returned value is XtGeometryAlmost or XtGeometryNo, Xt calls the set_values_almost method of the widget whose geometry is changing. A return value of XtGeometryNo is like the parent saying: “I don't like the geometry you suggested, and I don't have any compromise to suggest.” A return value of XtGeometryAlmost means “The geometry you suggested is not quite acceptable: would this compromise suit you?” The job of set_values_almost is to accept the compromise geometry proposed by the parent or to propose a different geometry to the parent. Once a new geometry is proposed by the set_values_almost method, Xt calls the parent's geometry_manager method again, and the cycle repeats until the geometry_manager returns XtGeometryYes or XtGeometryDone, or until the child gives up trying to change size. Figure 12-6 illustrates this process.

Figure 12-6. Geometry negotiation by the set_values_almost method


Most widgets inherit this method from the Core widget by specifying XtInheritSetValuesAlmost in the Core class part initialization. This inherited method always approves the suggestion made by the parent geometry_manager method. If your widget really depends on being certain sizes, however, you will need to write a set_values_ almost method. You should never specify a NULL set_values_almost method because Xt will print a warning message when set_values_almost would have been called, and continue as if it had been called and had returned XtGeometryYes.

The set_values_almost method is passed pointers to two XtWidgetGeometry structures: request and reply. The request structure contains the child's original request and reply includes the geometry_manager method's compromise geometry if geometry_manager returned XtGeometryAlmost. To accept the compromise, the procedure must copy the contents of the reply geometry into the request geometry; to attempt an alternate geometry, the procedure may modify any part of the request argument; to terminate the geometry negotiation and retain the original geometry, the procedure must set request->request_mode to zero.

If geometry_manager returned XtGeometryNo, it will not have generated a compromise. In this case, the set_values_almost method may suggest a new geometry, but it is probably not worth it since the method has no information upon which to base its changes to its previous suggestion. The set_values_almost method at this point should usually just set request->request_mode to zero to terminate the geometry negotiation.

The insert_child and delete_child Methods

The Composite class has an instance part structure that contains an array of all the widget's children (even those not currently managed), the current number of children, and the total number of child slots available. The insert_child method inserts the ID of a child into this array. It is called when the child is created by a call to XtCreateWidget() or XtCreateManagedWidget(). Most widgets inherit the insert_child method from the Composite class by specifying the symbolic constant XtInheritInsertChild in the class structure initialization. A class would replace the default insert_child method to control the position of each child added, or to limit the number or classes of widgets that can be added.

A composite widget can control the position of each child added by calling a function whose pointer is stored in the instance part field insert_position. The function should return the number of widgets before the widget. The XtNinsertPosition resource sets this function pointer. The default insert_position function returns the current number of children. Of course, because this resource's value is a function pointer, it can be specified in the application only at run time, never through the resource files or command line.

The delete_child method removes the ID of a child from the child array and is called when the application calls XtDestroyWidget(). This method is almost always inherited from Composite by specifying the symbolic constant XtInheritDeleteChild in the class structure initialization.

How Constraint Management Works

The first thing to realize about constraint widgets is that everything said about composite widgets is still true. Because Constraint is a subclass of Composite, all the methods described above are still present and have the same tasks. However, constraint widgets also maintain a structure full of data attached to each child, set through resources. Every time it lays out the children, the constraint widget reads this data to determine how to handle that child. Of course, it still may query each children to get its opinion of a new size. The constraint information adds another level of complexity to the situation.

Like composite widgets, constraint widgets can be drastically simplified by reducing flexibility and features. The Athena Form widget, for example, never queries its children for their geometry input and never asks its parent for a size change. Furthermore, its constraints for each child are quite limited. This makes Form quite short and simple, but also means that it doesn't always do the right thing.

Writing a Constraint Widget

The following sections describe the portions of the Athena Form widget that relate to geometry management. We have chosen the Athena Form widget instead of the Motif Form widget because the Athena Form widget is less than one quarter the size and is much simpler to understand. The principles at work are similar, and an understanding of the Athena implementation should give you a good start towards understanding geometry management under Motif.

However, before we start, an introduction to the constraints of the Athena Form widget is in order. As you should remember, constraints appear to the user to be resources of the child widgets managed by the Form. Looking at these resources gives you a good idea of the kinds of things that can be done with Athena Form constraints.

  • The resources XtNhorizDistance and XtNfromHoriz specify the widget position in terms of a specified number of pixels horizontally away from another widget in the form. As an example, XtNhorizDistance could equal 10 and XtNfromHoriz could be the widget ID of another widget in the Form. (When specified in a resource file, XtNfromHoriz is set using the instance name of another widget in the form.) The new widget will always be placed 10 pixels to the right of the widget defined in XtNfromHoriz, regardless of the size of the Form. If XtNfromHoriz equals NULL, then XtNhorizDistance is measured from the left edge of the Form.

  • Similarly, the resources XtNvertDistance and XtNfromVert specify the widget position in terms of a specified number of pixels vertically away from another widget in the Form. If XtNfromVert equals NULL, then XtNvertDistance is measured from the top of the Form.

    When set in the application, the values for XtNfromHoriz and XtNFromVert must be widget IDs. But in the resource database, widget names are used instead, since the actual widget ID changes each time the application is run. Athena uses an Xmu converter from widget name to widget ID, which is useful when setting constraints from resource files. However, Motif does not provide such a converter.

  • The XtNtop, XtNbottom, XtNleft, and XtNright resources tell the Form where to position the child when the Form is resized. The values of these resources are specified by the enum XtEdgeType, which is defined in <X11/Xaw/Form.h>.

  • The values XtChainTop, XtChainBottom, XtChainLeft, and XtChainRight specify that a constant distance is to be maintained from an edge of the child to, respectively, the top, bottom, left, and right edges of the Form.

  • The value XtRubber specifies that a proportional distance from the edge of the child to the left or top edge of the Form is to be maintained when the Form is resized. The proportion is determined from the initial position of the child and the initial size of the Form. Form provides a StringToEdgeType conversion to allow the resize constraints to be easily specified in a resource file.

The Core Resource List

The Form widget has only one resource of its own, XtNdefaultDistance, as shown in Example 12-7. This resource is used only as the default for two of the Constraint resources, XtNhorizDistance and XtNvertDistance. XtNdefaultDistance is used to set the instance field default_spacing, which is used in only one place in the widget, in the Constraint initialize method described in Section 12.4.4, "The Constraint initialize Method."

Example 12-7. Form: the Core resource list

#define Offset(field) XtOffsetOf(FormRec, form.field)
static XtResource resources[] = {
    {
        XtNdefaultDistance,
        XtCThickness,
        XtRInt,
        sizeof(int),
        Offset(default_spacing),
        XtRImmediate,
        (XtPointer)4
    }
};
#undef Offset


The Constraint Resource List

The Form widget has three groups of constraint resources. XtNhorizDistance, XtNfromHoriz, XtNvertDistance, and XtNfromVert together control the initial position of a child. XtNtop, XtNleft, XtNbottom, and XtNright govern repositioning of the child when Form is resized. The XtNresizable resource controls whether the geometry_manager of this widget will honor requests to change the geometry of this child. Note that XtNresizable does not control whether this constraint widget can resize a child--only whether or not it will do so because of a request from the child.[86]

For more details about how these constraint resources work, read about them on the reference page for the Form widget in Volume Five, X Toolkit Intrinsics Reference Manual.

Constraint resources are also called simply constraints, particularly because they are stored in a Core instance field called constraints. Example 12-8 shows Form's constraint resource list.

Example 12-8. Form: constraint resource list

static XtEdgeType defEdge = XtRubber;
#define Offset(field) XtOffsetOf(FormConstraintsRec, form.field)
static XtResource formConstraintResources[] = {
    {
    XtNhorizDistance,
    XtCThickness,
    XtRInt,
    sizeof(int),
    Offset(dx),
    XtRImmediate,
    (XtPointer)DEFAULTVALUE
    },
    {XtNfromHoriz, XtCWidget, XtRWidget, sizeof(Widget),
        Offset(horiz_base), XtRWidget, (XtPointer)NULL},
    {XtNvertDistance, XtCThickness, XtRInt, sizeof(int),
        Offset(dy), XtRImmediate, (XtPointer)DEFAULTVALUE},
    {XtNfromVert, XtCWidget, XtRWidget, sizeof(Widget),
        Offset(vert_base), XtRWidget, (XtPointer)NULL},
    {XtNtop, XtCEdge, XtREdgeType, sizeof(XtEdgeType),
        Offset(top), XtREdgeType, (XtPointer)&defEdge},
    {XtNbottom, XtCEdge, XtREdgeType, sizeof(XtEdgeType),
        Offset(bottom), XtREdgeType, (XtPointer)&defEdge},
    {XtNleft, XtCEdge, XtREdgeType, sizeof(XtEdgeType),
        Offset(left), XtREdgeType, (XtPointer)&defEdge},
    {XtNright, XtCEdge, XtREdgeType, sizeof(XtEdgeType),
        Offset(right), XtREdgeType, (XtPointer)&defEdge},
    {XtNresizable, XtCBoolean, XtRBoolean, sizeof(Boolean),
        Offset(allow_resize), XtRImmediate, (XtPointer)False},
};
#undef Offset

The corresponding data structure that this resource list references, FormConstraints, is defined in the private include file for the widget. Its definition is shown in Example 12-9.

Example 12-9. Form: constraint data structure

typedef struct _FormConstraintsPart {
/*
 * Constraint Resources.
 */
    XtEdgeType  top, bottom,  /* where to drag edge on resize     */
                left, right;
    int         dx;           /* desired horiz offset             */
    int         dy;           /* desired vertical offset          */
    Widget      horiz_base;   /* measure dx from here if non-null */
    Widget      vert_base;    /* measure dy from here if non-null */
    Boolean     allow_resize; /* True if child may request resize */
/*
 * Private constraint variables.
 * These store the dimensions of the child prior to layout.
 */
    int         virtual_width, virtual_height;
/*
 * Size of this child as it would be if we did not impose the
 * constraint that its width and height must be greater than zero (0).
 */
    LayoutState layout_state;   /* temporary layout state  */
} FormConstraintsPart;
typedef struct _FormConstraintsRec {
    FormConstraintsPart form;
} FormConstraintsRec, *FormConstraints;


The constraints part structure should be considered an instance part structure. This structure has public fields set through resources and private fields that hold state data, just like an instance part structure. Note also that the FormConstraints structure is built the same way as instance structures, by combining part structures for each class into a complete constraint structure. This allows subclasses of Form to create their own constraint part structure and add it after the Form constraint part.

When a widget is created as a child of a constraint widget, the constraint instance structure (FormConstraintsRec, in this case) is placed in the constraints field of the Core instance structure. Xt makes the constraint resources stored there settable, like resources defined by the child even though they are actually defined and used by the parent.

Note that the constraint resource list of a widget can be queried with XtGetConstraintResourceList(), although this is rarely needed in widget or application code.

Class Structure Initialization

The Form class is a subclass of Constraint. Therefore, its class structure contains class parts for Core, Composite, Constraint, and Form. Example 12-10 shows the class structure initialization of Form. Several methods referenced here have not been discussed so far in this book. They are the Core methods class_initialize and class_part_init, and the Constraint methods initialize and set_values. These and all the geometry management-related methods of Form are discussed in Section 12.4.6, "The geometry_manager Method."

Example 12-10. Form: class structure initialization

FormClassRec formClassRec = {
  { /* Core class fields  */
    /* superclass         */    (WidgetClass) &constraintClassRec,
    /* class_name         */    "Form",
    /* widget_size        */    sizeof(FormRec),
    /* class_initialize   */    ClassInitialize,
    /* class_part_init    */    ClassPartInitialize,
    /* class_inited       */    False,
    /* initialize         */    Initialize,
    /* initialize_hook    */    NULL,
    /* realize            */    XtInheritRealize,
    /* actions            */    NULL,
    /* num_actions        */    0,
    /* resources          */    resources,
    /* num_resources      */    XtNumber(resources),
    /* xrm_class          */    NULLQUARK,
    /* compress_motion    */    True,
    /* compress_exposure  */    True,
    /* compress_enterleave*/    True,
    /* visible_interest   */    False,
    /* destroy            */    NULL,
    /* resize             */    Resize,
    /* expose             */    XtInheritExpose,
    /* set_values         */    SetValues,
    /* set_values_hook    */    NULL,
    /* set_values_almost  */    XtInheritSetValuesAlmost,
    /* get_values_hook    */    NULL,
    /* accept_focus       */    NULL,
    /* version            */    XtVersion,
    /* callback_private   */    NULL,
    /* tm_table           */    NULL,
    /* query_geometry     */    PreferredGeometry,
    /* display_accelerator*/    XtInheritDisplayAccelerator,
    /* extension          */    NULL
  },
  { /* Composite class fields */
    /* geometry_manager   */   GeometryManager,
    /* change_managed     */   ChangeManaged,
    /* insert_child       */   XtInheritInsertChild,
    /* delete_child       */   XtInheritDeleteChild,
    /* extension          */   NULL
  },
  { /* Constraint class fields */
    /* subresources       */   formConstraintResources,
    /* subresource_count  */   XtNumber(formConstraintResources),
    /* constraint_size    */   sizeof(FormConstraintsRec),
    /* initialize         */   ConstraintInitialize,
    /* destroy            */   NULL,
    /* set_values         */   ConstraintSetValues,
    /* extension          */   NULL
  },
  { /* Form class fields  */
    /* layout             */   Layout
  }
};
WidgetClass formWidgetClass = (WidgetClass)&formClassRec;


Note that the Form class is the first widget we have shown that defines a class part field--a method of its own, called layout. Since this method is not known to Xt, Xt will never call it. The widget must invoke this method itself at the appropriate times (you will see this invocation in the methods below). This code is made into a method instead of just a private function only to make it possible for subclasses of this widget to inherit or replace the method. Having such a method requires that the widget have a class_part_init method to handle the inheritance if a subclass specifies the layout method with the symbolic constant XtInheritLayout (also defined in this class's private header file).

Section 12.2.1, "Basic Core Methods in Composite Widgets" described which Core and Composite methods are required for composite widgets, and how to initialize the other Core and Composite fields for a composite widget. The same is true for constraint widgets.

However, the Constraint part is probably new to you. The ConstraintClassPart structure contains seven fields. The first three fields are where the constraint resource list, the number of resources, and the size of the constraint instance structure are entered. This resource list and instance structure were described in the last section. These fields are analogous to the resources, num_resources, and widget_size fields in the Core class part.

The three next fields, initialize, destroy, and set_values are methods defined by the Constraint class. These methods have the same field names as methods of Core, but are fields of a different structure, and contain pointers to different functions that you may need to write. To differentiate Constraint methods from the Core methods, we will precede the names of Constraint fields with the word “Constraint” and the names of Core fields with the word “Core” throughout this chapter.

Two of the three Constraint methods will be described where they fit in below. We'll describe one of them, Constraint destroy, now, because it is not used in Form and is less likely to be needed in the constraint widgets you may write. The Constraint destroy method is called when a child is destroyed, just before the Core destroy method of the child. It is responsible for freeing any memory allocated by the constraint widget that was used to manage that child. However, like the Core destroy method, it does not need to free memory allocated by Xt, such as the constraint data structure for the child.

The Constraint initialize Method

The Constraint initialize method is called when a widget is created, soon after the Core initialize method. It has the same two responsibilities as the Core initialize method, and one additional responsibility. It must:

  • Validate the ranges of resource settings, since they may be user-supplied.

  • Compute the value of any private constraint instance part fields that depend on constraint resource values (public constraint instance part fields).

  • Set child Core geometry fields to match the constraint resources. For example, if a constraint for the maximum height of a widget is set and the initial value set by the child is larger, the Constraint initialize method resets the height field in the Core instance structure.

However, like the Core initialize method, the Constraint initialize method is responsible only for constraint resources and for Core geometry resources. It need not handle any resources of superclasses (other than the Core geometry resources).

The Form widget performs only one of the tasks listed above, initializing constraint resources. In Form's case, the Constraint initialize method (shown in Example 12-11) simply sets the initial values of the XtNvertDistance and XtNhorizDistance constraint resources to the current value of the XtNdefaultDistance Form resource, unless the user has specified a value for either constraint resource. This is done only so that the application can set the Form resource once and have it apply to every child that does not override the value.

Form doesn't validate the values of any user-supplied resource values as it should. For example, the user may supply a negative value for the XtNhorizDistance or XtNvertDistance resources. This would certainly make the layout look bad, but it could also cause the Form widget to go into an infinite loop on geometry negotiations. In general, all initialize methods in Core and Constraint should check for ranges of reasonable values of resources where this makes sense. Range checking eliminates a potential source of bugs. Range checking in set_values is also a good idea to give the programmer good warning messages).

Example 12-11. Form: the Constraint initialize method

#define DEFAULTVALUE -9999
/* ARGSUSED */
static void ConstraintInitialize(request, new)
    Widget request, new;
{
    FormConstraints form = (FormConstraints)new->core.constraints;
    FormWidget fw = (FormWidget)new->core.parent;
    form->form.virtual_width = (int) new->core.width;
    form->form.virtual_height = (int) new->core.height;
    if (form->form.dx == DEFAULTVALUE)
        form->form.dx = fw->form.default_spacing;
    if (form->form.dy == DEFAULTVALUE)
        form->form.dy = fw->form.default_spacing;
}


Note that the Constraint instance part structure (FormConstraints) and the Form widget instance structure (FormWidget) are accessed by casting two different fields of the child's instance structure passed in.

The Constraint initialize method and the child's Core initialize are passed the same two copies of the child's instance structure: request, and new. The request widget is the widget as originally requested. The new widget starts with the values in the request, but it has already been updated by calling all superclass initialize methods.

The class_part_init Method

The class_part_init method should be present in a class that defines new methods in its class part structure. These new methods will never be called by Xt since Xt has no knowledge of when to call them. They can only be invoked directly from the widget code. The purpose of making them methods instead of just functions is to allow subclasses to inherit or replace the functions. The class_part_init method actually resolves this inheritance by setting each method field to the pointer provided by this class (the subclass is inheriting the method) or to the pointer provided by the subclass (the subclass is replacing the method). Example 12-12 shows a class_part_init method for a class that defines only one new method in its class part structure. This method is the Form widget's layout code.

Example 12-12. Form: the class_part_init method

static void ClassPartInitialize(class)
    WidgetClass class;
{
    register FormWidgetClass c = (FormWidgetClass) class;
    if (c->form_class.layout == XtInheritLayout)
        c->form_class.layout = Layout;
}

The XtInheritLayout symbol is defined in the private include file for any class that defines new class part methods (one for each new method). Its value is always _XtInherit.

Form itself sets the layout field to a pointer to its Layout function. When its class_part_init method is called when the first instance of Form is created, it does nothing because the layout field is not XtInheritLayout. When a subclass is defined that sets the layout field to a function, the same thing happens: Form's class_part_init method is called because it is chained downward (the class_part_init methods of all superclasses are called), and it still does nothing because the layout field is not XtInheritLayout. Thus, the subclass has replaced Form's method. But if the subclass sets the layout field to XtInheritLayout, Form's class_part_init method sets the field to its own Layout function. The subclass has inherited Form's method.

Usually, only the class that defines a particular new method resolves the inheritance by checking for the value of that field in its class_part_init method. There is no point in a subclass also checking for an XtInherit value, since the downward chaining means that the superclass will have already processed and replaced the XtInherit value before the subclass class_part_init method is called.

The geometry_manager Method

geometry_manager methods handle requests from the children to be resized. Therefore, they typically use the proposed geometry passed in from the child to calculate a new experimental layout, and actually move and resize the children if the new layout is acceptable. However, when the request is just a query, the method should be able to return the same values without actually moving or resizing anything.

The Form geometry_manager method is shown in Example 12-13. Note that Form uses the allow_resize field (the XtNresizable resource) to determine whether to even consider the resize request. Then, if the request specifies a width and height, Form will accept the change by returning XtGeometryYes. The XtMakeGeometryRequest() call that invoked the geometry_manager will actually make the geometry change before returning to the child's code. If the request specifies any other geometry change (border width, position, or stacking order), Form will deny the request. Finally, if the request was not a query, Form actually does the new layout. Note that Form never returns XtGeometryDone since it never makes the geometry changes itself. Instead it returns XtGeometryYes when it agrees with the changes, and lets Xt make the changes.

Note that the allowed structure in this routine could be replaced by individual width and height variables. Also note that the reply structure is never filled; it is used only when the geometry_manager method wants to suggest a compromise.

Example 12-13. Form: the geometry_manager method

/* ARGSUSED */
static XtGeometryResult GeometryManager(w, request, reply)
    Widget w;
    XtWidgetGeometry *request;
    XtWidgetGeometry *reply;    /* RETURN */
{
    FormConstraints form = (FormConstraints)w->core.constraints;
    XtWidgetGeometry allowed;
    if ((request->request_mode & ~(XtCWQueryOnly |
            CWWidth | CWHeight)) ||
            !form->form.allow_resize)
        return XtGeometryNo;
    if (request->request_mode & CWWidth)
        allowed.width = request->width;
    else
        allowed.width = w->core.width;
    if (request->request_mode & CWHeight)
        allowed.height = request->height;
    else
        allowed.height = w->core.height;
    if (allowed.width == w->core.width && allowed.height ==
            w->core.height)
        return XtGeometryNo;
    if (!(request->request_mode & XtCWQueryOnly)) {
        /* reset virtual width and height. */
        form->form.virtual_width = w->core.width = allowed.width;
        form->form.virtual_height = w->core.height = allowed.height;
        RefigureLocations( (FormWidget)w->core.parent );
    }
    return XtGeometryYes;
}


The RefigureLocations called from the geometry_manager method is a private function analogous to the DoLayout routine used in ScrollBox, except that RefigureLocations calls Form's layout method that contains the actual layout code so that the method can be inherited or replaced by subclasses. The layout method calculates a layout and moves and resizes the children. RefigureLocations is also called from the change_managed method, as described in Section 12.4.9, "The change_managed Method". Example 12-14 shows the RefigureLocations function and Form's layout method, which it calls. (The if statement that branches depending on the value of the no_refigure field allows an application to turn relayout on and off, as described in Section 12.4.11, "Delaying Geometry Recalculation.")

Example 12-14. Form: private functions: RefigureLocations and the layout method

static void RefigureLocations(w)
    FormWidget w;
{
    /* no_refigure supports the relayout recalculation
      delay described later in this chapter */
    if (w->form.no_refigure) {
        w->form.needs_relayout = True;
    }
    else {
        (*((FormWidgetClass)w->core.widget_class)->form_class.layout)
                ( w, w->core.width, w->core.height );
        w->form.needs_relayout = False;
    }
}
/* ARGSUSED */
static Boolean Layout(fw, width, height)
    FormWidget fw;
    Dimension width, height;
{
    int num_children = fw->composite.num_children;
    WidgetList children = fw->composite.children;
    Widget *childP;
    Position maxx, maxy;
    static void LayoutChild();
    Boolean ret_val;
    for (childP = children; childP - children < num_children;
            childP++) {
        FormConstraints form = (FormConstraints)
                (*childP)->core.constraints;
        form->form.layout_state = LayoutPending;
    }
    maxx = maxy = 1;
    /*
     * Layout children one at a time, and determine
     * necessary size for self
     */
    for (childP = children; childP - children
            < num_children; childP++) {
        /*
         * Layout child then find position of bottom right
         * outside corner of child
         */
        if (XtIsManaged(*childP)) {
            Position x, y;
            LayoutChild(*childP);
            x = (*childP)->core.x + (*childP)->core.width
                    + ((*childP)->core.border_width << 1);
            y = (*childP)->core.y + (*childP)->core.height
                    + ((*childP)->core.border_width << 1);
            if (maxx < x) maxx = x;
            if (maxy < y) maxy = y;
        }
    }
    fw->form.preferred_width = (maxx += fw->form.default_spacing);
    fw->form.preferred_height = (maxy += fw->form.default_spacing);
    /* Now ask parent to resize us.  If it says Almost, accept the
     * compromise.  If Almost and parent chose smaller size, or No
     * and we were smaller than necessary, children will be clipped,
     * not laid out again.
     */
    if (fw->form.resize_in_layout
            && (maxx != fw->core.width || maxy != fw->core.height)) {
        XtGeometryResult result;
        result = XtMakeResizeRequest( fw, (Dimension)maxx,
                (Dimension)maxy, (Dimension*)&maxx,
                (Dimension*)&maxy );
        if (result == XtGeometryAlmost)
            result = XtMakeResizeRequest( fw, (Dimension)maxx,
                   (Dimension)maxy, NULL, NULL );
        fw->form.old_width  = fw->core.width;
        fw->form.old_height = fw->core.height;
        ret_val = (result == XtGeometryYes);
    } else ret_val = False;
    return ret_val;
}


The layout method treats one child at a time, first initializing the layout_state private constraint instance field of each child to LayoutPending. The LayoutChild routine will start from this value. Next, it calls LayoutChild for each child, and at the same time keeps a running total of the sizes of the children so that when the loop is finished it knows how big to be to fit all the children. Finally, it requests of its parent that it be just big enough to fit its children. If the parent denies the request, the code makes no attempt to make another request. If the parent offers a compromise, it is accepted. The Form widget, in either case, may be too big or too small to fit its children. If it is too small, some of its children will be clipped.

The LayoutChild routine is shown in Example 12-15. What it does is simple, although it is a little hard to follow because it is called recursively. It moves the child according to the XtNfromHoriz and XtNfromVert constraint resources.[87]These resources specify that a child be placed to the right of or below another particular child.

Example 12-15. Form: the LayoutChild private function

static void LayoutChild(w)
    Widget w;
{
    FormConstraints form = (FormConstraints)w->core.constraints;
    Position x, y;
    Widget ref;
    switch (form->form.layout_state) {
        case LayoutPending:
                form->form.layout_state = LayoutInProgress;
                break;
        case LayoutDone:
                return;
        case LayoutInProgress:
                String subs[2];
                Cardinal num_subs = 2;
                subs[0] = w->core.name;
                subs[1] = w->core.parent->core.name;
                XtAppWarningMsg(XtWidgetToApplicationContext(w),
                        "constraintLoop","xawFormLayout",
                        "XawToolkitError", "constraint loop\
                        detected while laying out child\
                        '%s' in FormWidget '%s'",
                        subs, &num_subs);
                return;
    }
    x = form->form.dx;
    y = form->form.dy;
    if ((ref = form->form.horiz_base) != (Widget)NULL) {
        LayoutChild(ref);
        x += ref->core.x + ref->core.width +
                (ref->core.border_width
                << 1);
    }
    if ((ref = form->form.vert_base) != (Widget)NULL) {
        LayoutChild(ref);
        y += ref->core.y + ref->core.height +
                (ref->core.border_width
                << 1);
    }
    XtMoveWidget( w, x, y );
    form->form.layout_state = LayoutDone;
}


If neither XtNfromHoriz nor XtNfromVert are set for the child, it is simply placed the default distance from the top-left corner of the Form. When one child is set, the next child must be placed relative to that child. However, the other child may be later in the list and not properly positioned yet. Therefore, the code calls LayoutChild to lay out the child that this child is positioned relative to.

The layout_state field catches circular settings for the XtNfromHoriz and XtNfromVert resources. For example, if widget A is specified to the right of widget B, and widget B is specified to the right of widget A, there is no solution. LayoutChild would be caught in an infinite loop of calling itself. When first called from the layout method, the layout_state is LayoutPending. This is changed to LayoutInProgress in the switch statement. If the function is called again for the same child, this state will cause the warning message to be printed and the function to exit. The Form widget does not exit--it just gives up processing the invalid constraint resource setting and prints a warning message.

The resize Method

The resize method calculates a layout to fit in the new dimensions of Form and moves and resizes the children accordingly. Form's resize method is shown in Example 12-16. It consists of a loop that treats each managed child one at a time. The position and dimensions of each child are calculated with the help of the private function TransformCoord (also shown in Example 12-16) and the child is moved and resized. TransformCoord handles one parameter at a time, and uses a position, the size before resizing, the size after resizing, and the constraints settings to arrive at the appropriate value for the parameter. The old width and height of the Form widget are initialized in the Core initialize method and updated at the end of the resize method.

Example 12-16. Form: the resize method

static void Resize(w)
    Widget w;
{
    FormWidget fw = (FormWidget)w;
    WidgetList children = fw->composite.children;
    int num_children = fw->composite.num_children;
    Widget *childP;
    Position x, y;
    Dimension width, height;
    for (childP = children; childP - children < num_children;
            childP++) {
        FormConstraints form = (FormConstraints)
                (*childP)->core.constraints;
        if (!XtIsManaged(*childP)) continue;
        x = TransformCoord( (*childP)->core.x, fw->form.old_width,
                fw->core.width, form->form.left );
        y = TransformCoord( (*childP)->core.y, fw->form.old_height,
                fw->core.height, form->form.top );
        form->form.virtual_width =
                TransformCoord((Position)((*childP)->core.x
                + form->form.virtual_width
                + 2 * (*childP)->core.border_width),
                fw->form.old_width, fw->core.width,
                form->form.right )
                - (x + 2 * (*childP)->core.border_width);
        form->form.virtual_height =
                TransformCoord((Position)((*childP)->core.y
                + form->form.virtual_height
                + 2 * (*childP)->core.border_width),
                fw->form.old_height, fw->core.height,
                form->form.bottom )
                - ( y + 2 * (*childP)->core.border_width);
        width = (Dimension)
                (form->form.virtual_width < 1) ? 1 :
                form->form.virtual_width;
        height = (Dimension)
                (form->form.virtual_height < 1) ? 1 :
                form->form.virtual_height;
        XtConfigureWidget( *childP, x, y, (Dimension)width,
                (Dimension)height, (*childP)->core.border_width);
    }
    fw->form.old_width = fw->core.width;
    fw->form.old_height = fw->core.height;
}
static Position TransformCoord(loc, old, new, type)
    register Position loc;
    Dimension old, new;
    XtEdgeType type;
{
    if (type == XtRubber) {
        if ( ((int) old) > 0)
            loc = (loc * new) / old;
    }
    else if (type == XtChainBottom || type == XtChainRight)
        loc += (Position)new - (Position)old;
    return (loc);
}


This resize method stores the new size of the children in the virtual_width and virtual_height constraint part fields, and uses their previous values to arrive at the new size. This is done because Form's XtNtop, XtNbottom, XtNleft, and XtNright constraints specify the geometry of the child based on its previous geometry.

Notice that the for loop in this particular resize method loops through the children directly, using pointer arithmetic. This is equivalent to using a loop that increments an integer and then uses the integer to index the children array. For example, the first five lines of the loop could also be expressed as:

int i;
for (i = 0; i < num_children; i++) {
    FormConstraints form = (FormConstraints)
            (children[i])->core.constraints;
    if (!XtIsManaged(children[i])) continue;
    x = TransformCoord( (children[i])->core.x,
            fw->form.old_width, fw->core.width, form->form.left );
        .
        .
        .
}

The Core and Constraint set_values Methods

When the application calls XtSetValues() to set the resources of a child of a constraint widget, Xt calls the child's Core set_values method and then the parent's Constraint set_values method. Both methods are passed the same arguments. Constraint set_values validates the ranges of constraint resource settings and computes the value of any private constraint instance part fields that depend on constraint resource values. It should also set child Core geometry fields to match the changes in constraint resources. For example, if a constraint for the maximum height of a widget is changed to a value smaller than the widget's current height, then the Constraint set_values procedure should reset the height field in the widget.

Both Core and Constraint set_values must return True or False to indicate whether redisplay of the widget is necessary. For composite and constraint widgets, this value is usually meaningless because there is nothing to redisplay. But these might be useful if, for some reason, you write a composite widget that does have display semantics.

Form defines both the Core and Constraint set_values methods as empty functions that return False. An easier way to do this is to specify NULL for them in the class structure initialization.

The change_managed Method

The change_managed method is responsible for making the initial layout of an application and changing the layout when any child changes management state. Form's change_managed method (shown in Example 12-17) calls RefigureLocations to actually do a layout. (RefigureLocations is a private routine equivalent to DoLayout in ScrollBox, described in Section 12.4.6, "The geometry_manager Method.") Form's change_managed method also stores the previous size of the children in the virtual_width and virtual_height constraint part fields for use in the resize method as described in Section 12.4.7, "The resize Method."

Example 12-17. Form: the change_managed method

static void ChangeManaged(w)
    Widget w;
{
    FormWidget fw = (FormWidget)w;
    FormConstraints form;
    WidgetList children, childP;
    int num_children = fw->composite.num_children;
    Widget child;
    /*
     * Reset virtual width and height for all children.
     */
    for (children = childP = fw->composite.children;
             childP - children < num_children; childP++) {
        child = *childP;
        if (XtIsManaged(child)) {
            form = (FormConstraints)child->core.constraints;
            if ( child->core.width != 1)
                form->form.virtual_width = (int) child->core.width;
            if ( child->core.height != 1)
                form->form.virtual_height = (int) child->core.height;
        }
    }
    RefigureLocations( (FormWidget)w );
}


The query_geometry Method

Form's query_geometry method (shown in Example 12-18) is the minimal version, almost identical to the one described for simple widgets in Chapter 7, Basic Widget Methods. The preferred_width and preferred_height instance variables are set in the Form class Layout method to the size that just fits the current layout.

Example 12-18. Form: the query_geometry method

static XtGeometryResult PreferredGeometry( widget, request, reply  )
    Widget widget;
    XtWidgetGeometry *request, *reply;
{
    FormWidget w = (FormWidget)widget;
    reply->width = w->form.preferred_width;
    reply->height = w->form.preferred_height;
    reply->request_mode = CWWidth | CWHeight;
    if (  request->request_mode & (CWWidth | CWHeight) ==
            reply->request_mode & CWWidth | CWHeight
            && request->width == reply->width
            && request->height == reply->height)
        return XtGeometryYes;
    else if (reply->width == w->core.width && reply->height ==
            w->core.height)
        return XtGeometryNo;
    else
        return XtGeometryAlmost;
}


Delaying Geometry Recalculation

During an application's initial layout, the change_managed method of a composite widget is called only once even though many children may have been managed. However, after that, change_managed is called once for every child that changes management state. Many composite or constraint widgets, especially ones that have complicated layout code, provide a public function (such as the one shown in Example 12-19) that the application can call to turn off layout recalculation until a group of windows is managed or unmanaged, and then call again to trigger recalculation once the whole group of children has been managed or unmanaged.

To implement this delay, you need an instance variable to hold a Boolean value indicating whether to delay or not (no_refigure, in this case). You set and unset this variable in this public routine and you test it in change_managed.

Example 12-19. Form: the public function for delaying calls to change_managed

void XawFormDoLayout(w, doit)
Widget w;
Boolean doit;   /* False, don't recalculate; True, do */
{
    register FormWidget fw = (FormWidget)w;
    fw->form.no_refigure = !doit;
    if ( XtIsRealized(w) && fw->form.needs_relayout )
        RefigureLocations( fw );
}


Compound Widgets

A compound widget is a combination of widgets put together to make a higher-level, user-interface object. For example, a ScrolledWindow widget is itself a composite widget, but it automatically creates its own ScrollBar children. Similarly, the code for MessageBox can create its own Shell parent, and it creates PushButtonGadget children that read OK, Cancel, and Help.

Compound widgets create their children in their initialize method, and set resources to position them. Often they also provide resources of functions that make it easy for the application to configure some characteristics of their children. The application can manipulate the children only through these resources, because it cannot access the widget IDs of the compound widget's subwidgets without breaking the rules of data hiding. Thus, compound widgets are convenient for programming, but they make it more difficult to take advantage of all the configurable aspects of the subwidgets.

The main widget of the compound widget may be a subclass of Core, Composite, or Constraint. If it is a subclass of Core, the widget manages the positions and sizes of its children manually whenever it is resized. The success of this strategy is dependent on the children never trying to resize themselves and on the application never trying to resize the children directly.[88] The latter will not be a problem unless the application breaks the data-hiding rules by manipulating the child directly. The Text widget is an example of this kind of widget. It creates and manages its own scrollbar.

Compound widgets normally define only a few methods and inherit the rest. Compound widgets based on Core will move and resize their children manually in their resize method. If the widget is a subclass of Composite or Constraint, the normal geometry management facilities manage the position and size of the children. If it is a subclass of Constraint, the main widget sets the constraints of the children to control the geometry management process by providing a Constraint initialize method.

A compound widget always needs a destroy method that destroys the children it created. Compound widgets also need a set_values method to manage their resources.

Stacking Order

We promised earlier to say a bit more about how composite or constraint widgets can control the stacking order of their children. We noted that this must be done manually, because Xt doesn't provide much support for it. This is because most applications do not stack widgets--the whole concept of geometry management is based on each widget trying to lay out its children without stacking them. However, there are applications where it makes sense to stack widgets. For example, an application that provides note cards, where each card is a widget, would want to stack them showing only the corner of hidden cards.

There is no Core resource for stacking order, and therefore it can't be set with XtSetValues() unless you define the resources in your own widget class. Xt provides no call to restack windows; you must use the Xlib functions XConfigureWindow(), XRestackWindows(), XRaiseWindow(), or XLowerWindow(). When a widget suggests a stacking order for itself through its query_geometry method, Xt takes care of making the required Xlib call if the parent agrees with the change. However, stacking requests of unrealized widgets have no effect (so stacking order won't be set this way in the initial geometry negotiation). Therefore, the most robust method to handle stacking order is for your composite widget to make the appropriate Xlib calls directly to change the stacking order of its children. XRestackWindows() is probably the best call to use. Since restacking the windows doesn't change their requirements for screen space, it shouldn't affect either the parent or the children adversely. The appropriate place to call XRestackWindows() depends on when you want to change the stacking order. (Note that the stacking change won't become visible until the next time Xt is waiting for an event.)

You can control the initial stacking order of a group of children by creating them in the desired order. The most recently created widget appears on the bottom. (This is the opposite of what you might expect if you know that newly created X windows appears on top of their siblings. The difference is due to the way a composite widget maintains its list of children.)



[82] You can find out more about the network optimization done by Xlib in Volume One or Volume Zero.

[83] ScrollBox widens the borders of its children, for an unknown reason. It might as well be admitted that there is probably a bug in it somewhere!

[84] It is worth noting that the Box widget fails miserably in managing scrollbars, while Form is adequate but has the annoying characteristic that it resizes the width of the scrollbars as well as their length, sometimes resulting in bloated or minuscule scrollbars.

[85] Incidentally, the children and num_children fields are resources. However, they are read-only from outside the widget code; the application should never set them with XtSetValues().

[86] The fact that Form does not provide individual control over the resizability of each child is a major weakness.

[87] Form resizes children only when it is resized--never during normal layout.

[88] When a child of a simple widget calls XtMakeGeometryRequest() because it wants to change its size, XtMakeGeometryRequest() always makes the requested changes and returns XtGeometryYes. Therefore, a simple widget parent really has no control over its child if the child wants to resize itself. A simple widget cannot even tell that the child has resized itself.