Geometry Management in LessTif

# Introduction

This document describes the way in which LessTif widgets handle their geometry negotiations. As geometry management is a subject that has much to do with Xt, part of this document describes (what I understand of) the Xt geometry model.

All feedback is, as always, welcomed.

# Xt Geometry Management

The Xt geometry model is based on geometry negotiations. This really means that every change that a widget wants to apply to its geometry, must be approved by the widget's parent. The resources in the widget that are part of this geometry are

- x
- y
- width
- height
- border_width

A widget can request a geometry change by using XtMakeGeometryRequest or XtMakeResizeRequest. In LessTif, we've wrappered XtMakeGeometryRequest in _XmMakeGeometryRequest(). This is the preferred method of geometry negotiation, as the result is (or at least should be) binary -- Yes or No. XtMakeResizeRequest only allows a widget to request a different size (width/height), whereas XtMakeGeometryRequest can be used to alter all five resources mentioned above.

LessTif has a convenience function called _XmMakeGeometryRequest which calls XtMakeGeometryRequest.

## XtMakeGeometryRequest

The signature of XtMakeGeometryRequest is

    XtGeometryResult
    XtMakeGeometryRequest(  Widget w,
    			XtWidgetGeometry *desired,
    			XtWidgetGeometry *allowed)

It should be called from the widget which is passed as parameter 1. The second parameter described the geometry that the widget would like to have. The last parameter returns the geometry that the widget got, in some circumstances. Finally, the result of the function is a value indicating whether the request has been granted.

XtWidgetGeometry is a structure which holds the five fields indicated above, and a bitset in which you can indicate which fields have been initialized. In the return parameter, the bitset will also indicate how much information is valid. A common mistake is to assume that the parent widget will always set width and height values, and to just read those fields without looking at the flags.

The bitset field is called request_mode. It can be set using an OR of zero or more of the macros CWX, CWY, CWWidth, CWHeight, CWBorderWidth, each of which has exactly one bit set. A final bit XtCWQueryOnly is not currently used in LessTif. When set, the call to XtMakeGeometryRequest will return a result without changing anything to the widget.

The result of XtMakeGeometryRequest can have four values :

- XtGeometryYes :Means that the request has been granted.
- XtGeometryDone : The request has been granted, also it has been applied to the widget. According to "X Window System Toolkit" by Asente & Swick, a widget set should choose a policy : either use YES in all widgets, or use DONE. In LessTif, we've chosen the YES approach.
- XtGeometryNo : You can guess this by now : the request has not been granted. Many manager widgets (subclasses of XmManager) in LessTif will return this when they get a request to change the X or Y (i.e. the position) of a child widget.
- XtGeometryAlmost : This is a very useful but difficult return value. It means that the request has not been completely granted, and the allowed parameter returns a suggested geometry. If the widget takes the suggestion and calls XtMakeGeometryRequest with that same set of values, the parent widget must allow this request. That's the hard part.

## _XmMakeGeometryRequest

    XtGeometryResult
    _XmMakeGeometryRequest(Widget w, XtWidgetGeometry *g)

This function calls XtMakeGeometryRequest. If the return value is XtGeometryAlmost, it'll call it a second time, with the value just obtained. Then it returns.

Another programming mistake (introduced by Asente & Swick) : given the Xt rule about XtGeometryAlmost, you could program a loop in which you keep calling XtMakeGeometryRequest until the value is different from XtGeometryAlmost. The trouble is that this kind of a loop is only guaranteed to be finite if the parent widget(s) are bug-free. Need I say more?

_XmMakeGeometryRequest detects this problem and is verbose about it (if you set DEBUGSOURCES to print information about GeoUtils.c). It is known to happen with XmForm (i.e. XmForm currently doesn't always grant a geometry that it just suggested).

If anybody wants a good exercise in understanding this document, he or she is invited to find this bug. Really. I'll only start tracking it myself when I have no serious bugs to attend to. A couple of beers can be had in Leuven, Belgium, by the first person to fix this.

## XtQueryGeometry

You can ask the preferred size of a widget.

# Geometry and Widget Methods

The basic cycle involved is PreferredSize/XtMakeGeometryRequest/Layout.

## For both primitives and composites:

- initialize() - Don't do anything if you're composite. You don't know enough yet (and you probably don't have any kids yet, unless the user created you with XtNchildren: if the user does something like this, they're on their own). If a primitive, you should know enough to do a basic layout.

- set_values() - If a value changed that should cause a layout change, go ahead and recompute the PreferredSize. Then, just set your width/height to the computed values: the intrinisics will automatically see a change made and call XtMakeGeometryRequest on your behalf, so (ordinarily) don't do a Layout.

  If the request is granted, the intrinsics will automatically call your resize() method; that should be where the Layout is done.

  Rebuttal: There may be times where this isn't true. Some resources may require a composite to re-Layout. When doing so, there are a few warnings that should be noted.

  - If the user set a resource that triggered a size adjustment, the resize request may not be honored by the parent. If it is not, and you re-Layout, then be aware that you may have layed out to a geometry that isn't honored. This can cause (grossly understated) unexpected results. This case can (and does) happen in Label[G]; that's why there is a call to the resize() method in expose() -- to make sure the widget is displayed correctly. The same is true of CascadeB[G].

  - A superclass may have polluted the widget dimensions, or a resource that changed in a superclass may have altered the widget dimensions to cause a request that may not be honored. Laying out to this geometry may not be valid either.

  Unfortunately, the intrinsics do not notify a widget if the resize request wasn't honored, so there's no way to do a proper job of it unless the expose method calls Layout. Needless to say, this is not good for performance. One optimization that can be made is due to the nature of XtMakeGeometryRequest() (which will be discussed later); if widget is not managed, or the parent isn't realized, then we can be sure that the resize request WILL be honored. In this case, we can blithely call Layout and be sure that the request will be honored.

- resize() - You can't do any geometry negotiation here. You must take the size you currently have, and lay yourself out to this geometry. This method, in combination with set_values(), show the requirements of the two basic algorithms: one to compute the PreferredSize, and one to Layout to a given size.

- realize() - For primitive subclasses, you probably want to realize your window, and then Layout to it's geometry. For managers, it doesn't hurt to go through the PreferredSize/XtMakeGeometryRequest/Layout cycle again, as things may have changed.

- query_geometry() - Call the PreferredSize routine, and return the result if the request_mode is 0. Otherwise, return the usual rules of the request versus what you've computed.

## For composites only:

- geometry_manager() - The trickiest one. The PreferredSize routine should take two parameters, instigator and instig_request, and use the values specified in instig_request when treating the instigator. There are two additional rules that you should keep in mind: geometry_manager() doesn't get called if the parent isn't realized; it also doesn't get called if the child isn't managed (in both cases, the request is automatically granted). The method should then call _XmMakeGeometryRequest(); doing this guarantees that either XtGeometryYes or XtGeometryNo is returned -- you'll never see XtGeometryAlmost unless a composite has a bug. Finally the Layout function should be called. The Layout function should take two additional parameters, the instigator and the instig_request. What to do next depends on the value that's going to be returned by the geometry_manager():
  - if the result of the Layout on the instigator is XtGeometryAlmost, then no change should be made to the instigator (OR FOR THAT MATTER, TO ANY OTHER CHILDREN IN THE COMPOSITE; this is an important point, and often alters the behavior of the layout method -- doing a Configure on a child when the geometry didn't change just wastes cycles). Instead, the reply geometry should reflect what Layout computed for the instigator. IMPORTANT: if the instigator calls back with the geometry that you computed, the geometry_manager MUST return XtGeometryYes.

  - if the return is XtGeometryYes, the child's rectangle should be modified to reflect the geometry. THIS IS IMPORTANT. You must do this so that XtMakeGeometryRequest will reconfigure the requesting child's window. This is different from XtGeometryDone in that XtGeometryDone implies that the window change was made by the parent, and we don't do that in LessTif. The GeoUtils and RowColumn do this now.

  In the layout function, if the child being manipulated is NOT the instigator, then the child should be configured (normally _XmConfigureObject()), if the return is XtGeometryYes.

- change_managed() - You've got to go through the complete cycle of PreferredSize/XtMakeGeometryRequest/Layout. The intrinsics don't help with any of this, so it's got to be explicit. Remember that you've no instigator here, so all managed children should be configured if a size change took place.

  insert_child()/delete_child() - These methods are called when a child is added to a manager widget, or when a child is destroyed. Their use is particularly important in those manager widget which keep information about their children in private data structures.

  Note that these are unchained methods, which means they are not automatically called for all the superclasses of a manager widget. XmRowColumn's insert_child needs to call XmManager's insert_child, which in turn calls the one in its superclass.

NB: If a composite, and you either a) have no children, or b) have no managed children (BTW, _XmGeoCount_kids() will return zero for case b), then you probably shouldn't bother in either method to compute the preferred size or the layout; mostly because you don't have anything to operate on. Instead you should probably return the current geometry in query_geometry().

## For constraints only

- constraint_initialize() - Often, this method (if present) will not cause any geometry changes, but does offer an excellent time to capture information that will affect geometry management in the future. This includes things like XmNpositionIndex (RowColumn), or XmNpaneMinimum/ XmNpaneMaximum (PanedW).

- constraint_set_values() - Tricky. In this method, there can be so many interactions that the mind boggles. Quite often, resources that are set here may have *serious* implications for geometry management (like XmNpositionIndex), but it is difficult to know when a change should cause a Layout. In general, ALL OF THE WARNINGS FOR set_values() APPLY.

# The Geometry Management Helper Interfaces

There are two sets of two basic functions, that roughly have the signatures For primitives:

        PreferredSize( Widget w /* input */ );
        Layout( Widget w /* input with side effects */ );

and for composites:

         PreferredSize( Widget w, /* input */
                        Widget instig, /* input */
                        XtWidgetGeometry *instig_request, /* input */
                        XtWidgetGeometry *preferred_geom /* output */ );
         Layout( Widget w, /* input */
                 Widget instig, /* input */
                 XtWidgetGeometry *instig_request, /* input/output */
                 XtWidgetGeometry *preferred_geom /* input */ );

For primitives, an example of PreferredSize is XmCalcLabelDimensions(); the equivalent Layout() would be the resize() method. Regardless, you're Layout function or PreferredSize function can be modified for different behavior as appropriate to your class.

# LessTif Geometry

We're discussing geometry of many of LessTif's manager widgets here. One widget (and many of its subclasses) is not treated here, though : XmBulletinBoard has functionality based on XmGeoMatrix which allows you to build dialogs with it in a simple way. This is all described in the document $LESSTIF/doc/GeoUtils.txt, `Fun and Pain with the GeoUtils'.

## Generic layout functions in complicated widgets (deprecated)

Many of the LessTif manager widgets have one layout function with a fairly large number of parameters. This one layout function is built such that it can be called from all the geometry-related widget methods described above.

Here's an example of the signature of such a function :

    static void
    _XmMainWindowLayout(Widget w,
    		    Boolean ParentResize,
                        Widget child,
                        Boolean TestMode,
                        XtWidgetGeometry *cg,
                        XtWidgetGeometry *mwg)

The parameters are used as follows :

w
  this is the manager widget
ParentResize
  is a boolean which indicates whether the widget is allowed to try to resize itself
child
  is the instigator: it is the widget that requests geometry changes
TestMode
  is another boolean; if it is True, no changes are actually done to any widget resources
cg
  is the geometry request for the child widget
mwg
  optionally returns information about the main widget's size

The structure of these functions is such that they can be called in a number of circumstances. A high level description of what they do is :

1. declare lots of local variables to store geometries (for the widget itself, and all its children). This is necessary to implement test mode.
2. initialise all local variables to reflect the current geometry of the relevant widget
3. initialise the specific variables that reflect the instigator with its geometry
4. see how big the manager widget must be
5. if we're allowed to (ParentResize), ask whether we can resize ourselves to the geometry we need
6. resize ourselves (if applicable)
7. layout all children widgets
8. return information to the caller