%
% GeoUtils.tex -- Describes what the GeoUtils are good for and how to use
%                 them within one's own widget classes.
%
% (c) 1996 The Hungry Programmers
% (c) 1996 Harald Albrecht (albrecht@igpm.rwth-aachen.de)
% See the file PUBLIC for details.
%
\chapter[Fun and Pain with the GeoUtils]{Fun and Pain with the GeoUtils\\}
\by{Mitch Miers}

\section{Introduction}

\index{GeoUtils}%
\index{SmartMessageBox}%
Recreating the behavior implemented in the GeoUtils was \textbf{a)}
not fun, and \textbf{b)} really, really not fun. They are totally
undocumented (as with almost everthing interesting the \CSF{} ever
did). What really kicked off the implementation of the GeoUtils was my
discovery of John Cwikla's SmartMessageBox -- without this gem, this
work would have been impossible.  A round of applause for this guy,
please, and his intentions of ``Furthering `open software' into
reality\ldots{}''.

\index{BulletinBoard}%
\index{geometry management}
The GeoUtils provide a mechanism by which BulletinBoard subclasses can
automatically inherit geometry management for laying out their
children. The good thing about the GeoUtils is that you get to
specify \code{XtInherit*} for most of the class methods for a
BulletinBoard subclass. The drawback is that you lose some flexibility
(nevertheless a good tradeoff, because many of these functions are
extremely difficult to write).

\codecomposeindex{change\_managed}{method}%
\codecomposeindex{realize}{method}%
\codecomposeindex{resize}{method}%
\codecomposeindex{query\_geometry}{method}%
\codecomposeindex{geometry\_manager}{method}%
\codecomposeindex{set\_values}{method}%
The mechanism provides for
\begin{itemlist}
\item layout changes as a result of a child being managed or unmanaged
(the BulletinBoard \code{change\_managed()} method),
\item layout at realize time (the BulletinBoard \code{realize()} method),
\item layout for a resize (the BulletinBoard \code{resize()} method),
\item a way to generically handle geometry queries from a parent
(the BulletinBoard \code{query\_\-geometry()} method),
\item a way to generically handle geometry change requests from a child
(the BulletinBoard \code{geometry\_manager()} method),
\item and finally a way to re-layout due to changes caused by a call to
\code{Xt[Va]SetValues} (the subclass \code{set\_values()} method).
\end{itemlist}

The GeoUtils functions typically begin with \code{\_XmGeo} (but not
all do), whereas the corresponding BulletinBoard methods, for what the
GeoUtils do normally, begin with \code{\_XmGM}.


\section{The BulletinBoard Class}

\codeindex{geo\_matrix\_create}%
\codeindex{XmGeoCreateProc}%
There is one and only one clue in a BulletinBoard subclass that
indicates that this class wants to use the GeoUtils -- the
\code{geo\_matrix\_create} method in the BulletinBoard class part (see
\code{BulletinBP.h}). If this member is not \code{NULL}, the GeoUtils
are activated for this subclass. The prototype for this method looks
like:

\begin{verbatim}
typedef XmGeoMatrix (*XmGeoCreateProc)(Widget composite,
                                       Widget instigator,
                                       XtWidgetGeometry *desired);
\end{verbatim}

The \code{composite} widget is (obviously) the BulletinBoard subclass. The
\code{instigator} is used in the \code{geometry\_\-manager} and
\code{query\_geometry} methods, as is the \code{desired} geometry
(more on the use of those later).

\subsection{The change\_managed() and realize() Methods}

Let's start with the two most similar cases: \code{change\_managed()} and
\code{realize()}. First take a look at BulletinBoard's \code{change\_managed()}
method (in \code{BulletinBoard.c} in the directory
\code{\textdollar(LESSTIF\_ROOT)/libXm}). Here, in the book, I will
present only lines of concern as I talk about them (otherwise this
book would get even larger).

Note that the first thing we do after we enter this method is look to
see if the class record for the widget has a
\code{geo\_matrix\_create()} member.  If there is one, we call
\code{handle\_change\_\-managed()} and return (more about this on
page \pageref{HandleChangeManaged}).  If there isn't one, we proceed
with generic BulletinBoard rules.

\begin{verbatim}
static void
change_managed(Widget w)
{
    Widget p;
    XmBulletinBoardClassRec *bb = (XmBulletinBoardClassRec *)XtClass(w);

    if (bb->bulletin_board_class.geo_matrix_create) {
        handle_change_managed(w, bb->bulletin_board_class.geo_matrix_create);
        return;
    }
\end{verbatim}

\codeindex{\_XmGMEnforceMargin}%
Next, we call a \code{\_XmGMEnforceMargin()}.  This function ensures
that the default BulletinBoard behavior of forcing children to be
within the BulletinBoard margins is applied.

\begin{verbatim}
    _XmGMEnforceMargin(w, BB_MarginWidth(w), BB_MarginHeight(w), False);
\end{verbatim}

\codeindex{\_XmClearShadowType}%
Then we clear the old shadow, as what we may do could alter the way
the shadow looks.

\begin{verbatim}
    _XmClearShadowType(w, BB_OldWidth(w), BB_OldHeight(w),
                       BB_OldShadowThickness(w), 0);
    BB_OldShadowThickness(w) = 0;
\end{verbatim}

\codeindex{\_XmGMDoLayout}%
If we are realized, or our width or height is zero (usually indicating
that this is the first child to be added), we call
\code{\_XmGMDoLayout()}.  This function implements the generic
BulletinBoard layout method.

\begin{verbatim}
    if (XtIsRealized(w) || XtWidth(w) == 0 || XtHeight(w) == 0) {
        _XmGMDoLayout(w, BB_MarginWidth(w), BB_MarginHeight(w),
                      BB_ResizePolicy(w), False);
    }
\end{verbatim}

If we shrank, redraw the shadow (the \code{expose} method does this
too, but...)

\codeindex{\_XmDrawShadows}%
\begin{verbatim}
    if ((XtWidth(w) < BB_OldWidth(w) || XtHeight(w) < BB_OldHeight(w)) &&
        XtIsRealized(w)) {
        _XmDrawShadows(XtDisplay(w), XtWindow(w),
                       MGR_TopShadowGC(w), MGR_BottomShadowGC(w),
                       0, 0, XtWidth(w), XtHeight(w),
                       MGR_ShadowThickness(w), BB_ShadowType(w));
    }

\end{verbatim}

Then, we record our width/height/shadow thickness.

\begin{verbatim}
    BB_OldWidth(w) = XtWidth(w);
    BB_OldHeight(w) = XtHeight(w);
    BB_OldShadowThickness(w) = MGR_ShadowThickness(w);
\end{verbatim}

\codeindex{\_XmNavigChanged}%
And finally, the required call to \code{\_XmNavigChangedManaged()} that all Manager
subclasses must do.

\begin{verbatim}
    _XmNavigChangeManaged(w);
}
\end{verbatim}

If you read the code for \code{realize()} in \code{BulletinBoard.c},
you'll see almost identical code. The call to
\code{\_XmNavig\-ChangeManaged()} isn't necessary in the
\code{realize()} method, as is the test
\code{XtIsRealized()} either. Instead the \code{rea\-lize()} method must
chain up to its superclass' \code{realize()} method.

\label{HandleChangeManaged}%
\codecomposeindex{handle\_change\_managed}{method}%
Now, let's take a look at the \code{handle\_change\_managed()} method
that is called from BulletinBoard's \code{change\_managed()}
method. We start this function by checking if we are realized, or
if our resize policy allows us to resize (i.e., not \code{XmNONE}).
If either case is true, we set our desired width/height to zero; this
is a cue to the GeoUtils to compute the desired size of this manager.
If either case is false, we set the desired width/height to our
current width/height; this cues the GeoUtils to lay out the manager to
the current geometry (if possible).

\begin{verbatim}
static void
handle_change_managed(Widget w, XmGeoCreateProc mat_make)
{
    Dimension wd, ht, retw, reth;
    XmGeoMatrix geo;
    XtGeometryResult result;

    if (!!XtIsRealized(w))
        wd = ht = 0;
    else if (BB_ResizePolicy(w) !!= XmNONE)
        wd = ht = 0;
    else {
        wd = XtWidth(w);
        ht = XtHeight(w);
    }
\end{verbatim}

We then call the matrix create function.  This function is crucial, as the
data structures created tell the GeoUtils how to layout this particular
widget.

\begin{verbatim}
    geo = mat_make(w, NULL, NULL);
\end{verbatim}

\codeindex{\_XmGeoMatrixGet}%
Next, we call \code{\_XmGeoMatrixGet()}.  This function essentially
iterates through all the children we want to manage, querying each
child (except the instigator) for the geometry the child desires. But
note: this is \emph{not} the same as all the managed children of this
manager. If you forget to represent a child in the data structures
when you create the matrix, that child won't be considered when you
lay out the manager. Instead, at least in the case of SelectionBox and
friends, the results are as specified in those class's documentation
-- usually ``undefined'' (in practice, they'll probably get piled up
in the top lefthand corner of the manager).

\begin{verbatim}
    _XmGeoMatrixGet(geo, XmGET_PREFERRED_SIZE);
\end{verbatim}

\codeindex{\_XmGeoArrangeBoxes}%
Now the real work-horse routine in the GeoUtils is invoked --
\code{\_XmGeoArrangeBoxes()}. This function ``parses'' the data
structure (the GeoMatrix) and lays out the children according to the
rules defined by the matrix. Caveat: this function does \emph{not}
alter the children's geometry, but instead records the new geometry
information in the \code{XmKidGeometry} structure contained by the
GeoMatrix.

\begin{verbatim}
    _XmGeoArrangeBoxes(geo, 0, 0, &wd, &ht);
\end{verbatim}

At this point, \code{\_XmGeoArrangeBoxes()} has computed the size of
the manager as it would ideally like to be.  The next code fragment
checks the \code{BB\_ResizePolicy} for a value of
\code{XmRESIZE\_\-GROW}. If the ideal size is less than the current
size, we reject the change (because that would involve shrinking, and
we should only grow).  We then must re-layout the manager, by calling
\code{\_XmGeo\-ArrangeBoxes()} with our current width and height.

\begin{verbatim}
    if (BB_ResizePolicy(w) == XmRESIZE_GROW) {
        /* check the return against the original.  If the procedure would
         * like the BB to shrink, call again */
        if (wd < XtWidth(w) || ht < XtHeight(w)) {
            wd = XtWidth(w);
            ht = XtHeight(w);
            _XmGeoArrangeBoxes(geo, 0, 0, &wd, &ht);
        }
    }
\end{verbatim}

Now we look to see if any of the above calculations has indicated that
the manager wants to resize (by comparing the computed width and
height with the manager \code{XtWidth} and \code{XtHeight}).  If no
change is forthcoming, we just free the matrix and return.  Otherwise
we continue on.

\codeindex{\_XmGeoMatrixFree}%
\begin{verbatim}
    if (wd == XtWidth(w) && ht == XtHeight(w)) {
        _XmGeoMatrixFree(geo);
        _XmNavigChangeManaged(w);
        return;
    }
\end{verbatim}

Okay, the manager wants to change size.  We call
\code{XtMakeResizeRequest()}, and ask our parent if we can change
size. Eventually, the parent will respond with the size we can be
(hopefully the size the manager wants, but we can compromise here).

\begin{verbatim}
    retw = wd;
    reth = ht;
    do {
        result = XtMakeResizeRequest((Widget)w, retw, reth, &retw, &reth);
    } while (result == XtGeometryAlmost);
\end{verbatim}

The next fragment of code checks if a compromise was necessary, by
evaluating whether the size our parent said we can be is the same as
what we want to be.  If the two don't match, we end up calling
\code{\_XmGeoArrangeBoxes()} yet again, to arrange our children to
suit our parent.

\begin{verbatim}
    if (retw !!= wd || reth !!= ht)
        _XmGeoArrangeBoxes(geo, 0, 0, &retw, &reth);
\end{verbatim}

\codeindex{\_XmGeoMatrixSet}%
Now that all the geometry calculation has been done, our parent is
happy, and the manager is happy, we can go ahead and do
\code{\_XmConfigureObject()} calls on all our children.  That
particular job goes to the function \code{\_XmGeoMatrixSet()}, which
basically processes each \code{XmKidGeometry} box and configures the
widget that the box represents.

\begin{verbatim}
    _XmGeoMatrixSet(geo);
\end{verbatim}

If we've gotten this far, then we are pretty sure the manager's size has
changed.  If the manager has a shadow, now is the time to draw it (after
erasing the old one).

\codeindex{\_XmClearShadowType}%
\codeindex{\_XmDrawShadows}%
\begin{verbatim}
    if (XtIsRealized(w)) {
        _XmClearShadowType(w, BB_OldWidth(w), BB_OldHeight(w),
                           BB_OldShadowThickness(w), 0);

        _XmDrawShadows(XtDisplay(w), XtWindow(w),
                       MGR_TopShadowGC(w), MGR_BottomShadowGC(w),
                       0, 0, XtWidth(w), XtHeight(w),
                       MGR_ShadowThickness(w), BB_ShadowType(w));
    }
\end{verbatim}

We're done with the GeoUtils for now, so we can deallocate the
matrix. Then we record our new width/height/shadow thickness. And
finally, the required call to \code{\_XmNavigChangedManaged} that all
Manager subclasses must do.

\begin{verbatim}
    _XmGeoMatrixFree(geo);
    BB_OldWidth(w) = XtWidth(w);
    BB_OldHeight(w) = XtHeight(w);
    BB_OldShadowThickness(w) = MGR_ShadowThickness(w);
    _XmNavigChangeManaged(w);
}
\end{verbatim}

The \code{realize()} case is identical to this one.


\subsection{The resize() Method}

The \code{resize()} method is \emph{almost} identical to the two
described above.  The most significant difference is that we aren't
supposed to talk back in this method, but accept whatever size we
currently are, and lay ourselves out accordingly.  Thus, that method
is missing the calls that request the manager ideal size, and just
does layout.  Compare the \code{resize()} method to the
\code{change\_managed()} method above:

\codeindex{\_XmGMEnforceMargin}%
\codeindex{\_XmClearShadowType}%
\codeindex{\_XmGMDoLayout}%
\codeindex{\_XmDrawShadows}%
\begin{verbatim}
static void
resize(Widget w)
{
    XmBulletinBoardClassRec *bb = (XmBulletinBoardClassRec *)XtClass(w);
    Widget p;

    if (bb->bulletin_board_class.geo_matrix_create) {
        handle_resize(w, bb->bulletin_board_class.geo_matrix_create);
        return;
    }
    _XmGMEnforceMargin(w, BB_MarginWidth(w), BB_MarginHeight(w), False);
    _XmClearShadowType(w, BB_OldWidth(w), BB_OldHeight(w),
                       BB_OldShadowThickness(w), 0);
    BB_OldShadowThickness(w) = 0;
    if (XtIsRealized(w) || XtWidth(w) == 0 || XtHeight(w) == 0) {
        _XmGMDoLayout(w, BB_MarginWidth(w), BB_MarginHeight(w),
                      BB_ResizePolicy(w), True);
    }
    if ((XtWidth(w) < BB_OldWidth(w) || XtHeight(w) < BB_OldHeight(w)) &&
        XtIsRealized(w)) {
        _XmDrawShadows(XtDisplay(w), XtWindow(w),
                       MGR_TopShadowGC(w), MGR_BottomShadowGC(w),
                       0, 0, XtWidth(w), XtHeight(w),
                       MGR_ShadowThickness(w), BB_ShadowType(w));
    }
    BB_OldWidth(w) = XtWidth(w);
    BB_OldHeight(w) = XtHeight(w);
    BB_OldShadowThickness(w) = MGR_ShadowThickness(w);
}
\end{verbatim}

You can also see the similarities in \code{handle\_resize()} to
\code{handle\_change\_managed()}:

\codeindex{\_XmGeoMatrixGet}%
\codeindex{\_XmGeoArrangeBoxes}%
\codeindex{\_XmGeoMatrixSet}%
\codeindex{\_XmClearShadowType}%
\codeindex{\_XmDrawShadows}%
\codeindex{\_XmGeoMatrixFree}%
\begin{verbatim}
static void
handle_resize(Widget w, XmGeoCreateProc mat_make)
{
    Dimension wd, ht;
    XmGeoMatrix geo;

    wd = XtWidth(w);
    ht = XtHeight(w);
    geo = mat_make(w, NULL, NULL);
    _XmGeoMatrixGet(geo, XmGET_PREFERRED_SIZE);
    _XmGeoArrangeBoxes(geo, 0, 0, &wd, &ht);
    _XmGeoMatrixSet(geo);
    if (XtIsRealized(w)) {
        _XmClearShadowType(w, BB_OldWidth(w), BB_OldHeight(w),
                           BB_OldShadowThickness(w), 0);

        _XmDrawShadows(XtDisplay(w), XtWindow(w),
                       MGR_TopShadowGC(w), MGR_BottomShadowGC(w),
                       0, 0, XtWidth(w), XtHeight(w),
                       MGR_ShadowThickness(w), BB_ShadowType(w));
    }
    _XmGeoMatrixFree(geo);
    BB_OldWidth(w) = XtWidth(w);
    BB_OldHeight(w) = XtHeight(w);
    BB_OldShadowThickness(w) = MGR_ShadowThickness(w);
}
\end{verbatim}

\subsection{The query\_geometry() Method}

For BulletinBoard, the \code{query\_geometry()} method is probably the
simplest -- it does nothing on it's own behalf, but uses either the
GeoMatrix (if a \code{geo\_matrix\_create()} method exists), or the
generic method.  This simplicity is deceiving; the complexity isn't
visible in BulletinBoard -- it's been shifted elsewhere.

\codeindex{\_XmHandleQueryGeometry}%
\begin{verbatim}
static XtGeometryResult 
query_geometry(Widget w, XtWidgetGeometry *proposed,
               XtWidgetGeometry *answer)
{
    XmBulletinBoardWidgetClass bbc = (XmBulletinBoardWidgetClass)XtClass(w);
    XtGeometryResult res;

    if (bbc->bulletin_board_class.geo_matrix_create) {
        return _XmHandleQueryGeometry(w, proposed, answer,
                                      BB_ResizePolicy(w),
                                      bbc->bulletin_board_class.
                                        geo_matrix_create);
    }
    res = _XmGMHandleQueryGeometry(w, proposed, answer,
                                   BB_MarginWidth(w), BB_MarginHeight(w),
                                   BB_ResizePolicy(w));
    return res;
}
\end{verbatim}


\subsection{The geometry\_manager() Method}

Similar to the \code{query\_geometry()} method, the
\code{geometry\_manager()} method passes the buck on complexity.  It
looks somewhat like the \code{change\_managed()}, \code{resize()}, and
\code{rea\-lize()} methods, but does both less and more.  It does less
in terms of code in BulletinBoard, but the code in the GeoUtils is
much more complex, and uses little of the code that the other three
methods use.  Also, the \code{geometry\_\-manager()} method uses the
``cache'' variable in the BulletinBoard Widget instance, for repeated
calls to \code{geometry\_mananger()} when \code{geometry\_\-manager()}
returns \code{XtGeometryAlmost}.

\codeindex{\_XmGMHandleGeometryManager}%
\begin{verbatim}
static XtGeometryResult
geometry_manager(Widget w, XtWidgetGeometry *desired,
                 XtWidgetGeometry *allowed)
{
    Widget bb = XtParent(w);
    XmBulletinBoardWidgetClass bbc = (XmBulletinBoardWidgetClass)XtClass(bb);

    if (bbc->bulletin_board_class.geo_matrix_create) {
        return handle_geometry_manager(w, desired, allowed,
                                       bbc->bulletin_board_class.
                                         geo_matrix_create);
    }
    return _XmGMHandleGeometryManager(bb, w, desired, allowed,
                                      BB_MarginWidth(bb),
                                      BB_MarginHeight(bb),
                                      BB_ResizePolicy(bb),
                                      BB_AllowOverlap(bb));
}
\end{verbatim}

If you read the \code{handle\_geometry\_manager()} code below, you'll
see a similarity to the\linebreak\code{change\_managed()}, \code{realize()},
and \code{resize()} code above.

\codeindex{\_XmClearShadowType}%
\codeindex{\_XmDrawShadows}%
\codeindex{\_XmGMHandleGeometryManager}%
\begin{verbatim}
static XtGeometryResult
handle_geometry_manager(Widget w,
                        XtWidgetGeometry *desired, XtWidgetGeometry *allowed,
                        XmGeoCreateProc mat_make)
{
    Widget bb = XtParent(w);
    XmBulletinBoardWidgetClass bbc = (XmBulletinBoardWidgetClass)XtClass(bb);
    XtGeometryResult res;

    if (!!(desired->request_mode & (CWWidth|CWHeight)))
        return XtGeometryYes;
    if (BB_OldShadowThickness(bb) !!= 0 ||
        BB_ResizePolicy(bb) !!= XmRESIZE_NONE) {
        _XmClearShadowType(bb, BB_OldWidth(bb), BB_OldHeight(bb),
                           BB_OldShadowThickness(bb), 0);
        BB_OldShadowThickness(bb) = 0;
    }
    res = _XmHandleGeometryManager(bb, w, desired, allowed,
                                   BB_ResizePolicy(bb), &BB_GeoCache(bb),
                                   bbc->bulletin_board_class.
                                     geo_matrix_create);
    if (!!BB_InSetValues(bb) ||
        XtWidth(bb) > BB_OldWidth(bb) || XtHeight(bb) > BB_OldHeight(bb)) {
        if (XtIsRealized(bb)) {
            _XmDrawShadows(XtDisplay(bb), XtWindow(bb),
                         MGR_TopShadowGC(bb), MGR_BottomShadowGC(bb),
                         0, 0, XtWidth(bb), XtHeight(bb),
                         MGR_ShadowThickness(bb), BB_ShadowType(bb));
        }
    }
    BB_OldWidth(bb) = XtWidth(bb);
    BB_OldHeight(bb) = XtHeight(bb);
    return res;
}
\end{verbatim}

Finally, we should discuss the \code{set\_values()} method.


\subsection{The set\_values() Method}

The \code{set\_values} method is probably the most straight-forward
BulletinBoard method there is.  We aren't concerned about children
wanting to change (the Intrinsics toolkit will use our
\code{geometry\_manager()} method if that is the case), we're only
concerned with the user changing {\em us}.  There is one issue, though
-- \code{set\_values()} changes which change our geometry.

The reason for all this is that BulletinBoard, and subclasses of
BulletinBoard, have a reasonably high number of children that are
specified in instance variables.  Rather than ``ripple'' the parent's
geometry handler as children are changed, BulletinBoard and subclasses
save them up until the \code{set\_values()} method in the 
\emph{class of the widget being changed} is called. The designers
of \Motif{} couldn't stop the \code{geometry\_manager()} method from
being called, but they added a mechanism that can control when the
negotiation actually occurs.

The BulletinBoard widget has an instance variable called
``\code{in\_set\_values}''.  This is a Boolean that is set when a
\code{set\_values()} method is invoked (keep in mind that the
\code{set\_values()} method is chained in super- to sub-class order),
and cleared \emph{almost} at the exit of the method.  Right before the
exit, the variable is cleared, and a test is used to see if a size
update should be performed.  If you look in BulletinBoard and
subclasses in the \code{set\_values} method, you'll see the following
code fragment:

\codeindex{\_XmBulletinBoardSizeUpdate}%
\begin{verbatim}
    if (need_refresh == True && XtClass(new) == xmBulletinBoardWidgetClass)
    {
        _XmBulletinBoardSizeUpdate(new);
        return False;
    }
\end{verbatim}

For subclasses, replace the class check with a match for the subclass.

\codeindex{\_XmHandleGeometryManager}%
This trigger, in conjunction with an exception procedure (the
\code{no\_geo\_request()} field in the GeoMatrix), keeps the
\code{geometry\_manager()} from handling possibly conflicting changes
in the widget.  During the \code{\_XmHandleGeometryManager()} call,
the \code{no\_geo\_request()} call is made to see if geometry
negotiation should happen. Take a look at the
\code{no\_geo\_request()} in SelectionBox:

\index{\_XmSelectionBoxNoGeoRequest@\code{\_XmSelectionBoxNoGeo-\\Request}}%
\begin{verbatim}
Boolean _XmSelectionBoxNoGeoRequest(XmGeoMatrix _geoSpec)
{
    if (BB_InSetValues(_geoSpec->composite) && 
        XtClass(_geoSpec->composite) == xmSelectionBoxWidgetClass)
        return TRUE;
    return FALSE;
}
\end{verbatim}

\codeindex{BB\_InSetValues}%
Should a geometry request come from a child during
\code{set\_values()}, the flag \code{BB\_InSet-\linebreak{}Values()} will
be \code{True}, and the negotiation will be delayed.

\codeindex{\_XmBulletinBoardSizeUpdate}%
\begin{verbatim}
static Boolean
set_values(Widget old, Widget request, Widget new,
           ArgList args, Cardinal *num_args)
{ 
    BB_InSetValues(new) = True;
!    /*
     * code block to handle set_values changes
     */!
    BB_InSetValues(new) = False;

    if (XtWidth(new) !!= XtWidth(old) || XtHeight(new) !!= XtHeight(old)) {
        need_refresh = True;
    }
    if (need_refresh == True && XtClass(new) == xmBulletinBoardWidgetClass)
    {
        _XmBulletinBoardSizeUpdate(new);
        return False;
    }
    return need_refresh;
}
\end{verbatim}

Note especially the call to \code{\_XmBulletinBoardSizeUpdate()}.
\textbf{This should be done in every BulletinBoard subclass that uses
the GeoUtils}.  This gives the manager class the opportunity to handle
geometry changes in an instance's children that have occurred as a
result of \code{set\_values()}.

\codeindex{\_XmBulletinBoardSizeUpdate}%
The code for \code{\_XmBulletinBoardSizeUpdate} is as follows:

\codeindex{\_XmHandleSizeUpdate}%
\codeindex{\_XmClearShadowType}%
\codeindex{\_XmDrawShadows}%
\begin{verbatim}
void
_XmBulletinBoardSizeUpdate(Widget w)
{
    XmBulletinBoardWidgetClass bbc = (XmBulletinBoardWidgetClass)XtClass(w);

    if (!!XtIsRealized(w))
        return;
    if (bbc->bulletin_board_class.geo_matrix_create == NULL) {
        BB_OldWidth(w) = XtWidth(w);
        BB_OldHeight(w) = XtHeight(w);
        return;
    }
    if (!!BB_OldShadowThickness(w) && BB_ResizePolicy(w) !!= XmRESIZE_NONE) {
        _XmClearShadowType(w, BB_OldWidth(w), BB_OldHeight(w),
                           BB_OldShadowThickness(w), 0);
        BB_OldShadowThickness(w) = 0;
    }
    _XmHandleSizeUpdate(w, BB_ResizePolicy(w),
                        bbc->bulletin_board_class.geo_matrix_create);
    if ((XtWidth(w) < BB_OldWidth(w) || XtHeight(w) < BB_OldHeight(w)) &&
        XtIsRealized(w)) {
        _XmDrawShadows(XtDisplay(w), XtWindow(w),
                       MGR_TopShadowGC(w), MGR_BottomShadowGC(w),
                       0, 0, XtWidth(w), XtHeight(w),
                       MGR_ShadowThickness(w), BB_ShadowType(w));
    }
    BB_OldWidth(w) = XtWidth(w);
    BB_OldHeight(w) = XtHeight(w);
    BB_OldShadowThickness(w) = MGR_ShadowThickness(w);
}
\end{verbatim}

The function \code{\_XmHandleSizeUpdate()} is very similar to the
\code{change\_managed()} method, in that it does layout computation,
and requests a size change from the parent.

\section{The Data Structures}

Now that you have a passingly familiar with the basics, let's digress
for a time and take a look at the data structures involved in the
GeoUtils, as they should be understood before we talk about the
implementation of an example subclass. There are three different data
structures tangled up in the GeoUtils layout mechanism (figure
\ref{geomatrix_layout}): the \code{XmGeoMatrix} controls how the layout
is to be performed, the \code{XmGeoMajorLayout} contains information
about individual rows or columns of childen, and finally the
\code{XmKidGeometry} records the geometry of a single child.

\begin{figure}[h]
\centerline{\includegraphics[scale=1.09]{GeoPicture}}
\caption{\label{geomatrix_layout}Layout structures to mess around with
when using the GeoUtils.}
\end{figure}


\subsection{The GeoMatrix}

\codeindex{XmGeoMatrixRec}%
\codeindex{XmGEO\_ROW\_MAJOR}%
\codeindex{XmGEO\_COLUMN\_MAJOR}%
\codeindex{XmGeoArrangeProc}%
\codeindex{XmGeoExceptProc}%
\codeindex{XmGeoExtDestructorProc}%
\codeindex{XmGeoSegmentFixUpProc}%
The layout of the GeoMatrix structure is as follows:

\begin{verbatim}
typedef struct _XmGeoMatrixRec {
    Widget composite;
    Widget instigator;
    XtWidgetGeometry instig_request;
    XtWidgetGeometry parent_request;
    XtWidgetGeometry *in_layout;
    XmKidGeometry boxes;  /* there is a NULL pointer at the end of each row */
    XmGeoMajorLayout layouts;
    Dimension margin_w;
    Dimension margin_h;
    Boolean stretch_boxes;
    Boolean uniform_border;
    Dimension border;
    Dimension max_major;
    Dimension boxed_minor;
    Dimension fill_minor;
    Dimension width;
    Dimension height;
    XmGeoExceptProc set_except;
    XmGeoExceptProc almost_except;
    XmGeoExceptProc no_geo_request;
    XtPointer extension;
    XmGeoExtDestructorProc ext_destructor;
    XmGeoArrangeProc arrange_boxes;
    unsigned char major_order;
} XmGeoMatrixRec;

typedef struct _XmGeoMatrixRec *XmGeoMatrix;

typedef void (*XmGeoArrangeProc)(XmGeoMatrix matrix,
                                 Position x, Position y,
                                 Dimension *width_inout,
                                 Dimension *height_inout);
typedef Boolean (*XmGeoExceptProc)(XmGeoMatrix matrix);
typedef void (*XmGeoExtDestructorProc)(XtPointer extension);
typedef void (*XmGeoSegmentFixUpProc)(XmGeoMatrix matrix, int command,
                                      XmGeoMajorLayout row_layout,
                                      XmKidGeometry kid_info);

enum {
    XmGEO_ROW_MAJOR,
    XmGEO_COLUMN_MAJOR
};
\end{verbatim}

The GeoMatrix is the mother of all GeoUtils structures.  In it, we
have control information for how the layout is to be performed, info
on each child, margin information, etc.  Also, when you look at the
GeoUtils, keep in mind that the developers intended for it to work
both in row major and column major layout (i.e., up and down rows, and
side to side columns).  The comments in \code{XmP.h} indicate that
they didn't get any further than row major layout, though. Column
major layout hasn't even been implemented in \Motif{}~2.0 -- the comments
are still there.

Let's look at each member:

\begin{itemdescription}
\ITEM[Widget composite;]
The BulletinBoard subclass instance that's currently using the GeoUtils.

\ITEM[Widget instigator;]
If from \code{geometry\_manager}, the child that requested a geometry
change, or \code{NULL}.

\ITEM[XtWidgetGeometry instig\_request;]
From \code{geometry\_manager}, the change that the instigator
requested, or \code{NULL}.

\ITEM[XtWidgetGeometry parent\_request;]
If from \code{query\_geometry}, the way our parent wants us to look,
or \code{NULL}.

\ITEM[XtWidgetGeometry *in\_layout;]
Used in the cases where multiple calls are made to
\code{XtMakeResizeRequest()} or \code{Xt\-MakeGeometryRequest()} from
one of the children.  There is a GeoMatrix ``cache'' instance variable
in the BulletinBoard widget structure that gets used also.  I don't
think \Motif{}'s usage of this variable, and \code{instig\_request}
is \emph{quite} the same as \Motif{}'s.

\ITEM[XmKidGeometry boxes;]
This member is used to keep layout information for each of the
children of this manager.  It is sort of a ``cache'' for the current
and proposed geometry of each child.  It is an array of structures:
one structure for each child, each row of children separated by a
structure whose child pointer is \code{NULL}.

\ITEM[XmGeoMajorLayout layouts;]
This is used to keep layout information for each row of children
(especially things like whether each child in the row should be
the same height, or the same width, or both, etc.  More on this
when we go throught the structure involved).

\ITEM[Dimension margin\_w;]
The margin width of the manager.

\ITEM[Dimension margin\_h;]
The margin height of the manager.

\ITEM[Boolean stretch\_boxes;]
Whether or not children should be stretched to fill voids in the
layout.

\ITEM[Boolean uniform\_border;]
Whether or not the children should have the same \code{XtBorderWidth}.
This can also be controlled on a row basis (the \code{XmGeoMajorLayout}
has a \code{uniform\_border} field, too.  This value, if set, overrides
the Layout structure's variable).

\ITEM[Dimension border;]
If \code{uniform\_border} is true, the value that should be used for
\code{XtBorderWidth}.

\ITEM[Dimension max\_major;]
The maximum value of the major layout dimension.  For row major
layout, this would be the maximum computed width of all rows.

\ITEM[Dimension boxed\_minor;]
For row major layout, this is the cumulative height of all the rows,
not including fill.

\ITEM[Dimension fill\_minor;]
For row major layout, this is the amount of fill space needed.  In
other words, the amount of ``fill space'' needed vertically between
the rows.

\ITEM[Dimension width;]
This will hold the computed width of the manager.

\ITEM[Dimension height;]
This will hold the computed height of the manager.

\ITEM[XmGeoExceptProc set\_except;]
A manager can override how the geometry of children are set by
providing an override method here.

\ITEM[XmGeoExceptProc almost\_except;]
I have no clue.  Maybe a method that can be used if a parent says
\code{XtGeometryAlmost} to a resize request?

\ITEM[XmGeoExceptProc no\_geo\_request;]
There are certain times when you want to avoid geometry negotiation
for a while; usually in \code{set\_values()}.  This function is called
from \code{\_XmHandleGeometryManager} to determine if negotiation should
really happen.

\ITEM[XtPointer extension;]
Extension data for use by the override methods.  The GeoUtils don't
do anything with this member directly.

\ITEM[XmGeoExtDestructorProc ext\_destructor;]
A function that gets invoked when a GeoMatrix is freed, if the matrix
has a non-\code{NULL} extension.

\ITEM[XmGeoArrangeProc arrange\_boxes;]
An override method for arranging the children.  If this is non-\code{NULL}
most of the GeoUtils will not be used.

\ITEM[unsigned char major\_order;]
An indicator for whether this matrix is row- or column-major.
Currently only row-major is implemented. The values allowed here are
\code{XmGEO\_ROW\_MAJOR} and (in principle)
\code{XmGEO\_\-COLUMN\_MAJOR}.
\end{itemdescription}

\subsection{The MajorLayoutRec}

The next level of structures (actually, a union) control how the individual
rows or columns are layed out.

\codeindex{XmGeoMajorLayoutRec}%
\begin{verbatim}
typedef union _XmGeoMajorLayoutRec {
    XmGeoRowLayoutRec row;
    XmGeoColumnLayoutRec col;
} XmGeoMajorLayoutRec;

typedef union _XmGeoMajorLayoutRec *XmGeoMajorLayout;
\end{verbatim}

\codeindex{XmGeoRowLayoutRec}%
\codeindex{XmGeoColumnLayoutRec}%
\codeindex{XmGET\_ACTUAL\_SIZE}%
\codeindex{XmGET\_PREFERRED\_SIZE}%
\codeindex{XmGEO\_PRE\_SET}%
\codeindex{XmGEO\_POST\_SET}%
\codeindex{XmGEO\_EXPAND}%
\codeindex{XmGEO\_CENTER}%
\codeindex{XmGEO\_PACK}%
\codeindex{XmGEO\_PROPORTIONAL}%
\codeindex{XmGEO\_AVERAGING}%
\codeindex{XmGEO\_WRAP}%
The only member of interest is the \code{XmGeoRowLayoutRec}.  Here's
the layout for both; below, I'll describe what the fields mean for the
\code{RowLayoutRec} -- the fields of a \code{ColumnLayoutRec} (should
it ever get implemented) are similar.

\begin{verbatim}
typedef struct {
    Boolean end;
    XmGeoSegmentFixUpProc fix_up;
    Dimension even_width;
    Dimension even_height;
    Dimension min_height;
    Boolean stretch_height;
    Boolean uniform_border;
    Dimension border;
    unsigned char fill_mode;
    unsigned char fit_mode;
    Boolean sticky_end;
    Dimension space_above;
    Dimension space_end;
    Dimension space_between;
    Dimension max_box_height;
    Dimension boxes_width;
    Dimension fill_width;
    Dimension box_count;
} XmGeoRowLayoutRec, *XmGeoRowLayout;

typedef struct {
    Boolean end;
    XmGeoSegmentFixUpProc fix_up;
    Dimension even_height;
    Dimension even_width;
    Dimension min_width;
    Boolean stretch_width;
    Boolean uniform_border;
    Dimension border;
    unsigned char fill_mode;
    unsigned char fit_mode;
    Boolean sticky_end;
    Dimension space_left;
    Dimension space_end;
    Dimension space_between;
    Dimension max_box_width;
    Dimension boxed_height;
    Dimension fill_height;
    Dimension box_count;
} XmGeoColumnLayoutRec, *XmGeoColumnLayout;

enum {
    XmGET_ACTUAL_SIZE = 1,
    XmGET_PREFERRED_SIZE,
    XmGEO_PRE_SET,
    XmGEO_POST_SET
};

/* fill modes for the GeoLayoutRec's below */
enum {
    XmGEO_EXPAND,
    XmGEO_CENTER,
    XmGEO_PACK
};

/* fit modes for the GeoLayoutRec's below */
enum {
    XmGEO_PROPORTIONAL,
    XmGEO_AVERAGING,
    XmGEO_WRAP
};
\end{verbatim}

Now for a description of the \code{XmGeoRowLayoutRec}:

\begin{itemdescription}
\ITEM[Boolean end;]
If we have processed all the rows, this end flag will be true.  In
other words, if your widget has n rows of (child) widgets, your matrix
will have $(n+1)$ row layout records, with the $(n+1)$ row having the
end flag \code{True}.  All other rows will have \code{end} set to
\code{False}.

\ITEM[XmGeoSegmentFixUpProc fix\_up;]
Some rows might need special fixing after they've been laid out.
For example, a separator in the SelectionBox should go the full
width of the SelectionBox (as opposed to going from margin to
margin). This \code{fix\_up()} method allows such special cases to be
handled.  The only other special cases that I know about is a
fixup for the MenuBar in SelectionBox and friends (extending the
width so that it stretches for the full width of the parent,
much like what is done for Separators).

\ITEM[\shortstackl{Dimension even\_width;\\{}Dimension even\_height;}]
These two members are overloaded.  At the beginning of matrix
processing, they are used as Booleans to indicate whether the
children in this row should end up having the same width and
height across the row.  If they are \code{True}, they end up containing
the maximum width and height of all the children in a given row,
and then applied to each child after the max is computed.

\ITEM[Dimension min\_height;]
The minimum height for any given child in a row.

\ITEM[Boolean stretch\_height;]
Indicates if we can stretch (or shrink) the widgets in a row if
the manager isn't quite the size we want.

\ITEM[Boolean uniform\_border;]
Assuming that the GeoMatrix didn't set its \code{uniform\_border} member,
this field indicates that this row should have a uniform border.

\ITEM[Dimension border;]
Assuming that uniform\_border (above) is true, the value of
\code{XtBorderWidth} for the widgets in this row.

\ITEM[unsigned char fill\_mode;]
One of \code{XmGEO\_EXPAND}, \code{XmGEO\_CENTER}, or \code{XmGEO\_PACK}.  The only one
of these I've seen used is \code{XmGEO\_CENTER}.  I suspect that the
other two might be used by RowColumn, and possibly Form.  What
happens if the \code{fill\_mode} is \code{XmGEO\_CENTER} is that extra fill
space is distributed between the children in a row; if not
\code{XmGEO\_CENTER}, the children are resized proportionally.

\ITEM[unsigned char fit\_mode;]
One of \code{XmGEO\_PROPORTIONAL}, \code{XmGEO\_AVERAGING}, or
\code{XmGEO\_WRAP}. \code{XmGEO\_PRO-\linebreak{}PORTIONAL} means
layout the widgets in this row in proportion to the individual sizes
of each widget. \code{XmGEO\_AVERAGING} means layout the children
based on the average dimensions of all children. \code{XmGEO\_WRAP}
means if we can't fit the children on one line, wrap them around to
what is effectively another row.  You can see this behavior when you
resize a \Motif{} Dialog to be taller and narrower than it wants to be.

\ITEM[Boolean sticky\_end;]
Indicates that the last box in the row should be as close to the
right margin as possible.

\ITEM[Dimension space\_above;]
Indicates the amount of space that should be left above this row.
if the top row's \code{space\_\-above} is less than the requested margin,
the margin is used.

\ITEM[Dimension space\_end;]
Indicates the amount of space that should be left at the ends of
the row.

\ITEM[Dimension space\_between;]
Indicates how much space should be between the widgets in a row.

\ITEM[Dimension max\_box\_height;]
Indicates the hight of the largest box in the row.

\ITEM[Dimension boxes\_width;]
Indicates the cumulative width of all the widgets in a row.

\ITEM[Dimension fill\_width;]
Indicates the cumulative fill space in a row, both between widgets
and at the row end.

\ITEM[Dimension box\_count;]
The number of boxes in the row.
\end{itemdescription}

In general, the user is only interested in fields up to (and including)
\code{space\_between}. The remaining fields are used during the calculations.


\subsection{The KidGeometryRec}

\codeindex{XmKidGeometryRec}%
The final structure is the most important one: the
\code{XmKidGeometry} structure.  This structure contains the geometry
for a child, and provides a storage place during the layout
calculations for that geometry while the algorithms proceed.

\begin{verbatim}
typedef struct _XmKidGeometryRec {
    Widget kid;
    XtWidgetGeometry box;
} XmKidGeometryRec, *XmKidGeometry;
\end{verbatim}


\section{The GeoUtils Functions}

Let's examine the published interface, as these are really the
functions that must be understood if you want to understand BulletinBoard,
and its GeoUtils using subclasses.  By the way, in \Motif{}, certain GeoUtils
functions are also used by RowColumn.  I'll indicate these as each function
is discussed (well, at least the ones I know about).


\subsection{The Allocation, Initialization, and Deallocation Functions}

First, let's talk about the functions used when you want to allocate a
GeoMatrix (i.e., the widget method known as \code{geo\_matrix\_create()}).  In that
method, you have to compute the number of rows of children that you are
going to layout, and the number of children (total) involved (more on
this when we actually examine a subclass).

The first function, which is actually called from subclass code, takes the
information you've gathered about your children, and allocates the GeoMatrix,
the \code{XmGeoMajorLayout}(s) structures, the \code{XmKidGeometry}(s) structures, and
mallocs an extra ``\code{extSize}'' bytes for any extension that will be used.
It returns a pointer to the allocated structure.  Note that it doesn't fill
in any information in the matrix -- it just allocates it.

\codeindex{\_XmGeoMatrixAlloc}
\begin{verbatim}
XmGeoMatrix _XmGeoMatrixAlloc(unsigned int numRows,
                              unsigned int numBoxes,
                              unsigned int extSize);
\end{verbatim}

The next function verifies that the child being examined is valid, and
sets up the \code{XmKidGeometry} structure to point at this kid.

\codeindex{\_XmGeoSetupKid}
\begin{verbatim}
Boolean _XmGeoSetupKid(XmKidGeometry geo, Widget kidWid);
\end{verbatim}

For those of you interested in writing subclass widgets, those two functions
are all you really need to know (well, except for knowing how to use them, of
course).  The rest of the functions are either internal, or buried within
the BulletinBoard class.

Finally, when geometry management is complete, the following function
deallocates the matrix.

\codeindex{\_XmGeoMatrixFree}
\begin{verbatim}
void _XmGeoMatrixFree(XmGeoMatrix geo_spec);
\end{verbatim}

\subsection{Layout Management Functions}

Layout management is essentially a five phase process:
\begin{itemlist}
\item We ask our children how they want to look.
\item We figure out how that would make us look.
\item We ask our parent if we can look that way.
\item We take how our parent says we can look, and recompute how
we want to look.
\item We apply the recomputed look to our children.
\end{itemlist}

\subsubsection{Querying the Children}

This first function queries all the manager's children for their
geometry.  The parameter \code{geoType} can actually be several
different values, but in practice I've never seen the GeoUtils use
anything other than \code{XmGET\_PREFERRED\_SIZE}.

\codeindex{\_XmGeoMatrixGet}%
\begin{verbatim}
void _XmGeoMatrixGet(XmGeoMatrix geoSpec, int geoType);
\end{verbatim}

The pseudo code for this function is as follows:

\begin{verbatim}
_XmGeoMatrixGet()
{
    while (°rows remaining°) {
        if (°end of row°)
            °advance to next row°
        else
            _XmGeoLoadValues(kid);
}
\end{verbatim}

\codeindex{\_XmGeoLoadValues}
\code{\_XmGeoMatrixGet()} uses a lower level function to ask children
their prefered goemetry: \code{\_Xm\-GeoLoadValues()}. The behavior of the
function is slightly different when the child is the instigator of a
geometry management conversation.

\begin{verbatim}
void _XmGeoLoadValues(Widget wid, int geoType, Widget instigator, 
                      XtWidgetGeometry *request,
                      XtWidgetGeometry *geoResult);
\end{verbatim}

Testing indicates that this function is used by the RowColumn in \Motif{}.

\subsubsection{Computing the Desired Size}

The next function is the real workhorse in layout computation.
Basically, it takes the information that was recorded from the
previous step and determines how that would make us look.  It uses
values in the \code{GeoMatrix}, the \code{MajorLayoutRec}, and the
\code{KidGeometryRec} to determine this.  It uses this information, in
combination with the input parameters, to determine how the total
composite should look.

\codeindex{\_XmGeoArrangeBoxes}%
\begin{verbatim}
void
_XmGeoArrangeBoxes(XmGeoMatrix geoSpec, Position x, Position y,
                   Dimension *pW, Dimension *pH);
\end{verbatim}

The pseudo code for this function is as follows:

\codeindex{\_XmGeoAdjustBoxes}%
\codeindex{\_XmGeoGetDimensions}%
\codeindex{\_XmGeoArrangeList}%
\codeindex{\_XmGeoStretchVertical}%
\codeindex{\_XmGeoFillVertical}%
\begin{verbatim}
_XmGeoArrangeBoxes()
{
    if (°user specified an arrange procedure°) {
        °call user's arrange°
        return
    }
    _XmGeoAdjustBoxes()
    _XmGeoGetDimensions()

    °adjust the overall layout based on the input parameters°

    while (°rows remaining°)
        _XmGeoArrangeList(row);
    if (°height needs adjusting°) {
        if (°user allows stretching°)
            _XmGeoStretchVertical()
        else
            _XmGeoFillVertical()
    }
}
\end{verbatim}

\codeindex{\_XmGeoArrangeBoxes}%
\code{\_XmGeoArrangeBoxes()} calls this next function to determine the
overall layout. In the current implementation,
\code{\_XmGeoAdjustBoxes()} loops through the rows in the composite,
figuring out how each row would look.

\begin{verbatim}
void _XmGeoAdjustBoxes(XmGeoMatrix geoSpec);
\end{verbatim}

The pseudo code for this function is as follows:

\begin{verbatim}
_XmGeoAdjustBoxes()
{
    while (°rows remaining°) {
        if (°children in row should be even width°)
            _XmGeoBoxesSameWidth();
        if (°children in row should be even height°)
            _XmGeoBoxesSameHeight();
        if (°children in row should have the same border°)
            °adjust the border°
    }
}
\end{verbatim}

If the relevant flags are set in the \code{MajorLayoutRec},
\code{\_XmGeoAdjustBoxes()} invokes the following two functions:

\codeindex{\_XmGeoBoxesSameWidth}%
\codeindex{\_XmGeoBoxesSameHeight}%
\begin{verbatim}
Dimension
_XmGeoBoxesSameWidth(XmKidGeometry rowPtr, Dimension width);
Dimension
_XmGeoBoxesSameHeight(XmKidGeometry rowPtr, Dimension height);
\end{verbatim}

Next, \code{\_XmGeoArrangeBoxes()} calls this next function to compute the
total picture of the desired geometry.  This function takes the overal
results computed above, and adjusts values in the Matrix and Layout
data structures.

\codeindex{\_XmGeoGetDimensions}%
\begin{verbatim}
void
_XmGeoGetDimensions(XmGeoMatrix geoSpec);
\end{verbatim}

\subsubsection{Computing the Layout}

This next function is responsible for actually laying out each row.
It is in this function that things like the \code{fit\_mode} and
\code{fill\_mode} in the \code{Layout} structure are evaluated.

\codeindex{\_XmGeoArrangeList}%
\begin{verbatim}
Position
_XmGeoArrangeList(XmKidGeometry boxes, XmGeoRowLayout layout,
                  Position x, Position y,
                  Dimension width, Dimension margin);
\end{verbatim}

The pseudo code for this function is as follows:

\codeindex{\_XmGeoLayoutWrap}%
\codeindex{\_XmGeoCalcFill}%
\codeindex{\_XmGeoLayoutSimple}%
\begin{verbatim}
_XmGeoArrangeList()
{
    °figure out the width of our children°
    °figure out the "fill" space wanted°
    °figure out the amount of adjusting necessary°
    °figure out the starting height of this row°

    if (°things aren't going to fit, and layout fit_mode is° XmGEO_WRAP) {
        _XmGeoLayoutWrap()
        return
    }
    else if (°things aren't going to fit°) {
        if (°fit_mode is° Xm_GEO_AVERAGING)
            FitBoxesAveraging()
        else
            FitBoxesProportional()
    }
    else if (°the wanted width is wider than necessary°) {
        if (°fill_mode is° XmGEO_CENTER)
            _XmGeoCalcFill()
        else
            FitBoxesProporitional()
    }
    _XmGeoLayoutSimple()
}
\end{verbatim}

Finally, after the rows have been laid out, the y offsets or the
widget heights in each row may need adjusting, based on the actual
height of the widget, and the value of
\code{stretch\_height}. \code{\_XmGeoArrangeBoxes()} takes care of
that with the following two functions.  The first stretches the rows
to fit; the second inserts filler space.

\codeindex{\_XmGeoStretchVertical}%
\codeindex{\_XmGeoFillVertical}%
\begin{verbatim}
Dimension
_XmGeoStretchVertical(XmGeoMatrix geoSpec, Dimension height, Dimension maxh);
Dimension
_XmGeoFillVertical(XmGeoMatrix geoSpec, Dimension height, Dimension maxh);
\end{verbatim}

I'll stop at this level.  If you want to know more, you'll need to
delve into the code in \code{GeoUtils.c}. What I've given should be
enough for you to find your way around.


\subsubsection{Applying the Changes}

If after all the above computations have happened, and our parent has agreed
to our resize request, we call the following function:

\begin{verbatim}
void _XmGeoMatrixSet(XmGeoMatrix geoSpec);
\end{verbatim}

The pseudo code for this is as follows:

\codeindex{\_XmGeoMatrixSet}
\begin{verbatim}
_XmGeoMatrixSet()
{
    for (°each row°) {
        for (°each child in row°)
            _XmSetKidGeo()
    }
}
\end{verbatim}

\codeindex{\_XmSetKidGeo}%
\codeindex{\_XmConfigureObject}%
The lower level function \code{\_XmSetKidGeo()} usually calls
\code{\_XmConfigureObject()}. The behavior is slightly different
during geometry management conversations.

\begin{verbatim}
void _XmSetKidGeo(XmKidGeometry kg, Widget instigator);
\end{verbatim}


\subsection{The Method Functions}

The method functions basically implement most of the behavior for certain
Xt required methods;  the GeoUtils provide default implementations for
\code{set\_values()}, \code{query\_geometry()}, and \code{geometry\_manager()}.

\codeindex{\_XmBulletinBoardSizeUpdate}%
\codeindex{\_XmHandleSizeUpdate}%
The \code{set\_values()} case (really, the implementation of
\code{\_XmBulletinBoardSizeUpdate()} -- see the section on BulletinBoard \code{set\_values()})
is handled by \code{\_XmHandleSizeUpdate()}.  This function is much like the
\code{change\_managed()} method in BulletinBoard (see the relevant section for
details).

\begin{verbatim}
void
_XmHandleSizeUpdate(Widget wid, unsigned char policy,
                    XmGeoCreateProc createMatrix);
\end{verbatim}

The \code{query\_geometry()} method is handled by the following function.  Essentially,
this function implements the geometry calculation without doing the layout
common to the other methods.

\codeindex{\_XmHandleQueryGeometry}%
\begin{verbatim}
XtGeometryResult
_XmHandleQueryGeometry(Widget wid,
                       XtWidgetGeometry *intended,
                       XtWidgetGeometry *desired, 
                       unsigned char policy,
                       XmGeoCreateProc createMatrix);
\end{verbatim}

The next function handles the \code{geometry\_manager()} method.  It is truly
a nasty function, and was very difficult to figure out.  This is the only
place in the entire GeoUtils functionality where the cache is used (and
really, it's the only place where it needs to be used).  There are some
pretty good reasons for this -- manager children tend to loop around
\code{XtMakeResizeRequest()}, or \code{XtMakeGeometryRequest()}, until their parent
says yes or no.  By using a cache, you can eliminate at least one iteration
of the negotiation (which is relatively expensive).  I don't really think
most readers of this document are interested in the gory details.  If you
are, reading through the code should give you the necessary information.

\codeindex{\_XmHandleGeometryManager}%
\begin{verbatim}
XtGeometryResult
_XmHandleGeometryManager(Widget wid, Widget instigator,
                         XtWidgetGeometry *desired, 
                         XtWidgetGeometry *allowed,
                         unsigned char policy, 
                         XmGeoMatrix *cachePtr,
                         XmGeoCreateProc createMatrix);
\end{verbatim}

These next two functions do most of the default behavior for the BulletinBoard,
when the widget class does not use the GeoUtils (unless, of course, the
subclass overrides them).

This first function is invoked when a parent queries a BulletinBoard widget
for its prefered size.

\codeindex{\_XmGMHandleQueryGeometry}%
\begin{verbatim}
XtGeometryResult
_XmGMHandleQueryGeometry(Widget w,
                         XtWidgetGeometry *proposed, XtWidgetGeometry *answer,
                         Dimension margin_width, Dimension margin_height,
                         unsigned char resize_policy)
\end{verbatim}

The second function is invoked when a child queries a BulletinBoard parent
for a resize.

\codeindex{\_XmGMHandleGeometryManager}%
\begin{verbatim}
XtGeometryResult
_XmGMHandleGeometryManager(Widget w, Widget instigator, 
                           XtWidgetGeometry *desired,
                           XtWidgetGeometry *allowed,
                           Dimension margin_width, Dimension margin_height,
                           unsigned char resize_policy, Boolean allow_overlap)
\end{verbatim}

\subsection{Miscellaneous Functions}

This function determines if two widget geometries are identical.

\codeindex{\_XmGeometryEqual}%
\begin{verbatim}
Boolean
_XmGeometryEqual(Widget wid, XtWidgetGeometry *geoA,
                             XtWidgetGeometry *geoB);
\end{verbatim}

The next function checks the desired geometry \code{desired} of the
widget \code{wid} against the parent's proposal in \code{response}. It
returns \code{True} only if the position (x, y), as well as the width
and height, and finally the border width are equal in the desired and
proposed geometry. If any one of these five geometry characteristics
is of no concern (the corresponding bit in
\code{response->request\_mode} is unset) then it will be ignored.

\codeindex{\_XmGeoReplyYes}%
\begin{verbatim}
Boolean
_XmGeoReplyYes(Widget wid, XtWidgetGeometry *desired,
                           XtWidgetGeometry *response);
\end{verbatim}

\codeindex{XtMakeGeometryRequest}%
\codeindex{\_XmMakeGeometryRequest}%
This function asks the parent of the widget \code{w} for a new desired
geometry \code{geom} of \code{w}. If the parent answers
\code{XtGeometryAlmost} then we ask him a second time with the
proposed geometry, so he can accept and set the new geometry. If the
parent refuses in any way then we'll inform the user of our parent's
impudent habbits (because he doesn't conform to the geometry
negotiation protocol, see O'Reilly, Vol. 5, pp. 264 for details about
\code{XtMakeGeometryRequest}). This function is at least only a convenience
function but according to informed sources, \Motif{} never issues a
bare \code{XtMakeGeometryRequest} but uses always
\code{\_XmMakeGeometryRequest}.

\begin{verbatim}
XtGeometryResult
_XmMakeGeometryRequest(Widget w, XtWidgetGeometry *geom);
\end{verbatim}

\codeindex{\_XmGeoClearRectObjAreas}%
The next function erases the background at both the old and the new
position of a rectangle object. Whereas the old position must be
explicitely specified in \code{old} the new position is determined
from \code{w}. Redrawing events will be triggered (and queued) for the
affected areas.
\begin{verbatim}
void
_XmGeoClearRectObjAreas(RectObj r, XWindowChanges *old);
\end{verbatim}

\codeindex{\_XmGMReplyToQueryGeometry}%
\codeindex{XmeReplyToQueryGeometry}%
The next one reappears as \code{XmeReplyToQueryGeometry} in
\Motif{}~2.0. This function is a shortcut for simple \code{query\_geometry}
methods which are interested only in their width and height but
neither their position nor border width. To use it, first compute your
desired width and height in your widget's \code{query\_geometry}
method and then return the result from the call to
\code{\_XmGMReplyToQueryGeometry}.

\begin{verbatim}
void
_XmGMReplyToQueryGeometry(void);
\end{verbatim}

These next two functions are ``fixup'' functions.  They are invoked by
\code{\_XmGeoMatrixSet()}, to override the generic geometry computation with
specific behavior.  \code{\_XmMenuBarFix()} forces a menu bar to be the full
width of its composite parent.  \code{\_XmSeparatorFix()} does the same for
separators.

\codeindex{\_XmMenuBarFix}%
\codeindex{\_XmSeparatorFix}%
\begin{verbatim}
void
_XmMenuBarFix(XmGeoMatrix geoSpec, int action,
              XmGeoMajorLayout layoutPtr, XmKidGeometry rowPtr);
void
_XmSeparatorFix(XmGeoMatrix geoSpec, int action,
                XmGeoMajorLayout layoutPtr, XmKidGeometry rowPtr);
\end{verbatim}

\subsection{BulletinBoard Helper Functions}

The next several functions implement bits of BulletinBoard behavior.

This next function ensures that a BulletinBoard child is constrained within
the margins of the BulletinBoard.

\codeindex{\_XmGMEnforceMargin}%
\begin{verbatim}
void
_XmGMEnforceMargin(Widget w,
                   Dimension margin_width, Dimension margin_height,
                   Boolean useSetValues)
\end{verbatim}

\codeindex{\_XmGMOverlap}%
The next function implements the \code{XmNallowOverlap} behavior (or
rather, if \code{XmNallowOver\-lap} is \code{False}, makes sure that
children do not overlap).

\begin{verbatim}
Boolean
_XmGMOverlap(Widget w, Widget instigator,
             Position x, Position y, Dimension width, Dimension height)
\end{verbatim}

The next function computes the desired size of a BulletinBoard.

\codeindex{\_XmGMCalcSize}%
\begin{verbatim}
void
_XmGMCalcSize(Widget w, Dimension margin_w, Dimension margin_h,
              Dimension *retw, Dimension *reth)
\end{verbatim}

The next function performs the BulletinBoard layout behavior.

\codeindex{\_XmGMDoLayout}%
\begin{verbatim}
void
_XmGMDoLayout(Widget w, Dimension margin_w, Dimension margin_h,
              unsigned char resize_policy, short adjust)
\end{verbatim}

\subsection{RowColumn Specific Functions}

The following functions are specific to RowColumn functionality, but reside
in the GeoUtils implementation.

I don't know what this first function does, but it looks suspiciously like
a \code{GeoMatrix} allocation function, specialized for \code{RCKidGeometry}.

\codeindex{\_XmGetKidGeo}%
\begin{verbatim}
XmKidGeometry
_XmGetKidGeo(Widget wid, Widget instigator,
             XtWidgetGeometry *request, 
             int uniform_border, Dimension border,
             int uniform_width_margins, 
             int uniform_height_margins,
             Widget help, int geo_type);
\end{verbatim}

\codeindex{\_XmRCGetKidGeo}%
There is an undocumented function, \code{XmRCGetKidGeo()}, that I
believe is similar to \code{\_XmGeo\-MatrixGet()}.  In \LessTif{}, I
believe this is implemented as \code{initialize\_boxes()} in
\code{Row\-Column.c}.

That function calls this next function, as well as \code{\_XmGeoLoadValues()}.

\codeindex{\_XmGetCount\_kids}%
\begin{verbatim}
int _XmGeoCount_kids(CompositeWidget c);
\end{verbatim}

Anybody out there who knows that this function does?


\section{How to Build a Subclass Using the GeoUtils}

\codeindex{XmTrivial}%
At this point, we've come full circle. Now that you know something
about how the GeoUtils work, let's examine how a subclass can use
them. I'll now talk about the \code{XmTrivial} widget class. It
doesn't implement anything more than an even layout of the button
children as if they were action buttons in a dialog. The order of the
buttons in the layout is the same as the creation order. In order to
keep it simple, there are no new Xt or synthetic resources beyond
those covered by BulletinBoard. If you are thinking about using the
GeoUtils, the \code{Trivial} class makes a pretty good template.  The
tested implementation lives in
\code{\textdollar(LESSTIF\_ROOT)/testXm/geometry}.

\subsection{The Header Files}

There really isn't much to say about the header files.  They are
pretty much standard headers for a widget implementation (same
procedure as every widget\ldots). Here's the public header
(from the file \code{Trivial.h}):

\begin{verbatim}
#ifndef TRIVIAL_H
#define TRIVIAL_H

#include <Xm/Xm.h>
#include <Xm/BulletinB.h>

extern WidgetClass xmTrivialWidgetClass;

typedef struct _XmTrivialRec *XmTrivialWidget;
typedef struct _XmTrivialConstraintRec *XmTrivialConstraint;

#ifndef XmIsTrivial
#define XmIsTrivial(a) (XtIsSubclass(a, xmTrivialWidgetClass))
#endif

Widget XmCreateCreateTrivial(Widget _p, char *_n, ArgList _a,
                             Cardinal _narg);

#endif
\end{verbatim}

Now, the private header (\code{TrivialP.h}):

\begin{verbatim}
#ifndef TRIVIAL_P_H
#define TRIVIAL_P_H

#include <Xm/XmP.h>
#include <Xm/BulletinBP.h>
#include "Trivial.h"

typedef struct _XmTrivialClassPart {
        int duh;
} XmTrivialClassPart;

typedef struct _XmSmartMessageBoxClassRec {
        CoreClassPart            core_class;
        CompositeClassPart       composite_class;
        ConstraintClassPart      constraint_class;
        XmManagerClassPart       manager_class;
        XmBulletinBoardClassPart bulletin_board_class;
        XmTrivialClassPart       trivial_class;
} XmTrivialClassRec, *XmTrivialWidgetClass;

typedef struct _XmTrivialPart {
        int gaah;
} XmTrivialPart;

typedef struct _XmTrivialRec {
        CorePart            core;
        CompositePart       composite;
        ConstraintPart      constraint;
        XmManagerPart       manager;
        XmBulletinBoardPart bulletin_board;
        XmTrivialPart       trivial;
} XmTrivialRec, *XmTrivialPtr;

#endif
\end{verbatim}

\subsection{The Implementation}

In this section I'll describe the various sections in \code{Trivial.c}
that are important to the subclass. The new sections are printed in a
\code{\textbf{bold typewriter}} font. Important parts of the new
sections are typeset in an \code{\textbf{\textit{italic bold
typewriter}}} font.

\begin{verbatim}
#include <LTconfig.h>
#include <Xm/XmP.h>
#include <Xm/XmI.h>
#include <Xm/BulletinBP.h>
#include <Xm/PushBP.h>
#include <Xm/PushBGP.h>
#include <Xm/DebugUtil.h>
#include "TrivialP.h"

/*
 * Forward Declarations
 */
static void class_initialize();
static void class_part_initialize(WidgetClass class);
static void initialize(Widget request, Widget new,
                       ArgList args, Cardinal *num_args);
static void destroy(Widget w);
static Boolean set_values(Widget current, Widget request, Widget new,
                          ArgList args, Cardinal *num_args);

!XmGeoMatrix trivial_matrix_create(Widget _w, Widget _from,
                                  XtWidgetGeometry *_pref);
Boolean trivial_NoGeoRequest(XmGeoMatrix _geoSpec);!

static XmBaseClassExtRec _XmTrivialCoreClassExtRec = {
    /* next_extension               */ NULL,
    /* record_type                  */ NULLQUARK,                             
    /* version                      */ XmBaseClassExtVersion,
    /* size                         */ sizeof(XmBaseClassExtRec),
    /* initialize_prehook           */ NULL,
    /* set_values_prehook           */ NULL,
    /* initialize_posthook          */ NULL,
    /* set_values_posthook          */ NULL,
    /* secondary_object_class       */ NULL,
    /* secondary_object_create      */ NULL,
    /* get_secondary_resources      */ NULL,
    /* fast_subclass                */ { 0 },
    /* get_values_prehook           */ NULL,
    /* get_values_posthook          */ NULL,
    /* class_part_init_prehook      */ NULL,
    /* class_part_init_posthook     */ NULL,
    /* ext_resources                */ NULL,
    /* compiled_ext_resources       */ NULL,
    /* num_ext_resources            */ 0,
    /* use_sub_resources            */ FALSE,
    /* widget_navigable             */ NULL,
    /* focus_change                 */ NULL,
    /* wrapper_data                 */ NULL
};

static XmManagerClassExtRec _XmTrivialMClassExtRec = {
    /* next_extension               */ NULL,
    /* record_type                  */ NULLQUARK,
    /* version                      */ XmManagerClassExtVersion,
    /* record_size                  */ sizeof(XmManagerClassExtRec),
    /* traversal_children           */ NULL /* FIXME */
};

XmTrivialClassRec xmTrivialClassRec = {
    /* Core class part */
    {
    /* superclass                   */ (WidgetClass)
                                         &xmBulletinBoardClassRec,
    /* class_name                   */ "XmTrivial",
    /* widget_size                  */ sizeof(XmBulletinBoardRec),
    /* class_initialize             */ class_initialize,
    /* class_part_initialize        */ class_part_initialize,
    /* class_inited                 */ FALSE,
    /* initialize                   */ initialize,
    /* initialize_hook              */ NULL,
!    /* realize                      */ XtInheritRealize,!
    /* actions                      */ NULL,
    /* num_actions                  */ 0,
    /* resources                    */ NULL,
    /* num_resources                */ 0,
    /* xrm_class                    */ NULLQUARK,
    /* compress_motion              */ TRUE,
    /* compress_exposure            */ XtExposeCompressMaximal,
    /* compress_enterleave          */ TRUE,
    /* visible_interest             */ FALSE,
    /* destroy                      */ destroy,
!    /* resize                       */ XtInheritResize,!
    /* expose                       */ XtInheritExpose,
    /* set_values                   */ set_values,
    /* set_values_hook              */ NULL,
    /* set_values_almost            */ XtInheritSetValuesAlmost,
    /* get_values_hook              */ NULL,
    /* accept_focus                 */ NULL,
    /* version                      */ XtVersion,
    /* callback offsets             */ NULL,
    /* tm_table                     */ NULL,
!    /* query_geometry               */ XtInheritQueryGeometry,!
    /* display_accelerator          */ NULL,
    /* extension                    */ (XtPointer)
                                         &_XmTrivialCoreClassExtRec
    },
    /* Composite class part */
    {
!    /* geometry_manager             */ XtInheritGeometryManager, 
    /* change_managed               */ XtInheritChangeManaged,!
    /* insert_child                 */ XtInheritInsertChild,
    /* delete_child                 */ XtInheritDeleteChild,
    /* extension                    */ NULL
    },
    /* Constraint class part */
    {
    /* subresources                 */ NULL,
    /* subresource_count            */ 0,
    /* constraint_size              */ 0,
    /* initialize                   */ NULL,
    /* destroy                      */ NULL,
    /* set_values                   */ NULL,
    /* extension                    */ NULL
    },
    /* XmManager class part */
    {
    /* translations                 */ XtInheritTranslations,
    /* syn_resources                */ NULL,
    /* num_syn_resources            */ 0,
    /* syn_constraint_resources     */ NULL,
    /* num_syn_constraint_resources */ 0,
    /* parent_process               */ XmInheritParentProcess,
    /* extension                    */ (XtPointer)
                                         &_XmTrivialMClassExtRec
    },
    /* XmBulletinBoard Area part */
    {
    /* always_install_accelerators  */ False,
!    /* geo_matrix_create            */ trivial_matrix_create,!
    /* focus_moved_proc             */ XmInheritFocusMovedProc,
    /* extension                    */ NULL

    },
    /* XmTrivial Class Part */
    {
    /* extension                    */ 0
    }
};

WidgetClass xmTrivialWidgetClass = (WidgetClass)&xmTrivialClassRec;

static void 
class_initialize()
{
    _XmTrivialCoreClassExtRec.record_type = XmQmotif;
}

static void
class_part_initialize(WidgetClass widget_class)
{
}

static void
initialize(Widget request,
           Widget new,
           ArgList args,
           Cardinal *num_args)
{
}

static void
destroy(Widget w)
{
}

static Boolean
set_values(Widget old,
           Widget request,
           Widget new,
           ArgList args,
           Cardinal *num_args)
{
    Boolean refresh_needed = False;

!    BB_InSetValues(new) = True;

    /* do any class specific stuff */

    BB_InSetValues(new) = False;!

!    if (refresh_needed && (XtClass(new) == xmTrivialWidgetClass))
    {
        _XmBulletinBoardSizeUpdate(new);
        return False;
    }!
    return refresh_needed;
}

!XmGeoMatrix
trivial_matrix_create(Widget _w, Widget _from, XtWidgetGeometry *_pref)
{
    XmGeoMatrix geoSpec;
    register XmGeoRowLayout layoutPtr;
    register XmKidGeometry boxPtr;
    Cardinal numKids;
    int i, nrows;
    Widget child;

    numKids = MGR_NumChildren(_w);

    °/* compute the number of rows you want here. */
    nrows = 1; /* Trivial only has one */°

    °geoSpec = _XmGeoMatrixAlloc(nrows, numKids, 0);
    geoSpec->composite = (Widget)_w;
    geoSpec->instigator = (Widget)_from;
    if (_pref)
        geoSpec->instig_request = *_pref;
    geoSpec->margin_w = BB_MarginWidth(_w) + MGR_ShadowThickness(_w);
    geoSpec->margin_h = BB_MarginHeight(_w) + MGR_ShadowThickness(_w);
    geoSpec->no_geo_request = trivial_NoGeoRequest;°

    layoutPtr = &(geoSpec->layouts->row);
    boxPtr = geoSpec->boxes;

    °/* row 1 */
    layoutPtr->fill_mode = XmGEO_CENTER;
    layoutPtr->fit_mode = XmGEO_WRAP;
    layoutPtr->even_width = 1;
    layoutPtr->even_height = 1;
    layoutPtr->space_above = BB_MarginHeight(_w);
    for (i = 0; i < numKids; i++) {
        child = MGR_Children(_w)[i];
        if ((XmIsPushButton(child) || XmIsPushButtonGadget(child)) &&
            XtIsManaged(child) && _XmGeoSetupKid(boxPtr, child))
        {
            boxPtr++;
        }
    }
    layoutPtr++;
    /* end marker */
    layoutPtr->space_above = 0;
    layoutPtr->end = TRUE;°

    return(geoSpec);
}

Boolean
trivial_NoGeoRequest(XmGeoMatrix geo)
{
    if (BB_InSetValues(geo->composite) &&
        XtClass(geo->composite) == xmTrivialWidgetClass)
        return TRUE;

    return FALSE;
}!
\end{verbatim}

Not bad, only around 350 lines of code.  This is about the mininum you
can get away with if you write a manager widget anyway. But now let us
go straight into the details.

\subsubsection{Extra Prototypes}

You'll need to provide two extra prototypes for a GeoUtils subclass -
one for the \code{geo\_matrix\_\-create()} method, and one for the
\code{no\_geo\_request()} method.  These should match the types
specified in \code{XmP.h}. From \code{Trivial.c}:

\begin{verbatim}
XmGeoMatrix trivial_matrix_create(Widget _w, Widget _from,
                                  XtWidgetGeometry *_pref);
Boolean trivial_NoGeoRequest(XmGeoMatrix _geoSpec);
\end{verbatim}

\subsubsection{The Class Structure}

The first thing to know is how to type \code{XtInherit}.  Unless you
really know what you are doing, and want to override specific
behaviors, you should definitely specify \code{XtInherit} in the class
structure of your subclass for the following methods:

\begin{itemlist}
\item \code{realize()},
\item \code{resize()},
\item \code{expose()},
\item \code{query\_geometry()},
\item \code{geometry\_manager()},
\item \code{change\_managed()}.
\end{itemlist}

~\\
Unless you are implementing a fairly trivial widget (such as
\code{XmTrivial}), you'll probably have to provide your own
\code{set\_values()} method.  That's okay, just make sure you follow
the rules outlined in the BulletinBoard section above.

\subsubsection{The set\_values() Method}

In any interesting widget, the \code{set\_values()} method will
probably do something (but it doesn't in \code{Trivial.c}).  The code
below can be considered boilerplate; you should probably base a
subclass's \code{set\_values()} method on this code.  Note especially
the region reserved for setting class specific instance variables.

\begin{verbatim}
static Boolean
set_values(Widget old,
           Widget request,
           Widget new,
           ArgList args,
           Cardinal *num_args)
{
    Boolean refresh_needed = False;

!    BB_InSetValues(new) = True;!
    /* do any class specific stuff HERE */
!    BB_InSetValues(new) = False;!

!    if (refresh_needed && (XtClass(new) == xmTrivialWidgetClass))
    {
        _XmBulletinBoardSizeUpdate(new);
        return False;
    }!
    return refresh_needed;
}
\end{verbatim}

\subsubsection{The NoGeoRequest Method}

This method actually doesn't get placed in the class structure, but
rather in the GeoMatrix during its creation.  Again, the
implementation in \code{Trivial.c} is boilerplate; the only thing a
subclass needs to do is change the tested widget class:

\begin{verbatim}
Boolean
trivial_NoGeoRequest(XmGeoMatrix geo)
{
    if (BB_InSetValues(geo->composite) &&
        XtClass(geo->composite) == xmTrivialWidgetClass)
        return TRUE;

    return FALSE;
}
\end{verbatim}


\subsubsection{The GeoMatrixCreate Method}

Now we get to the interesting part of the implementation.  The
\code{geo\_matrix\_create()} method in \code{Trivial.c} is \emph{not}
boilerplate, but it does show you what you need to do (well, actually,
one small portion is boilerplate). Instead of repeating the code
section here you can look up the method in the listing on the
preceding pages -- but take note that the method in \code{Trivial.c}
is called \code{trivial\_matrix\_create()}.

Note that the function has essentially three sections.  In the first section,
you need to loop through your children (or evaluate instance variables, as
is done in SelectionBox), deciding on how many rows of children that need
to be controlled.  Basically, what you are doing is evaluating how many
\code{Major\-Layout} structures you are going to need.  You can also choose to
count the number of managed children you have (this may or may not be the
same as the number of children you have); this is optional, as the wasted
space is not very large, and it eventually gets deallocated anyway.

In the second section, we have a small piece of boilerplate: it is
very important to duplicate this code exactly. While the \code{\_pref}
and \code{\_from} fields are often \code{NULL}, they are \emph{not}
when this method is called from
\code{\_XmHandleGeometryManager()}.  Make sure you copy this right.

\begin{verbatim}
    geoSpec = _XmGeoMatrixAlloc(nrows, numKids, 0);
    geoSpec->composite = (Widget)_w;
    geoSpec->instigator = (Widget)_from;
    if (_pref)
        geoSpec->instig_request = *_pref;
    geoSpec->margin_w = BB_MarginWidth(_w) + MGR_ShadowThickness(_w);
    geoSpec->margin_h = BB_MarginHeight(_w) + MGR_ShadowThickness(_w);
    geoSpec->no_geo_request = trivial_NoGeoRequest;
\end{verbatim}

You can be a little creative when you calculate the \code{margin\_w}
and \code{margin\_h} variables.  Also, make sure that you hook up the
\code{no\_geo\_request()} method as shown in the last line of the
code excerpt above.

The third section of code is basically where the subclass needs to
setup the \code{MajorLayout} structures with the desired information
for controlling the layout, and setting the \code{KidGeometry}
structures to point at the widget children that should appear.

\code{XmTrivial}'s implementation of this method is \emph{very} simplistic.
Now for a little more demanding example. The following code is
SelectionBox's version -- look for the boilerplate above to find the
separation between the sections:

\index{\_XmSelectionBoxGeoMatrixCreate@\code{\_XmSelectionBoxGeoMatrix-\\Create}}%
\begin{verbatim}
XmGeoMatrix
_XmSelectionBoxGeoMatrixCreate(Widget _w, Widget _from,
                               XtWidgetGeometry *_pref)
{
    XmGeoMatrix geoSpec;
    register XmGeoRowLayout layoutPtr;
    register XmKidGeometry boxPtr;
    Cardinal numKids;
    Boolean newRow;
    int nrows, i, nextras;
    Widget *extras;

    numKids = MGR_NumChildren(_w);

    nextras = 0;
    extras = NULL;
    for (i = 0; i < numKids; i++)
    {
        if (XtIsManaged(MGR_Children(_w)[i]) &&
            MGR_Children(_w)[i] !!= SB_ListLabel(_w) &&
            (SB_List(_w)
                ? MGR_Children(_w)[i] !!= XtParent(SB_List(_w))
                : True) &&
            MGR_Children(_w)[i] !!= SB_SelectionLabel(_w) &&
            MGR_Children(_w)[i] !!= SB_Text(_w) &&
            MGR_Children(_w)[i] !!= SB_Separator(_w) &&
            MGR_Children(_w)[i] !!= SB_OkButton(_w) &&
            MGR_Children(_w)[i] !!= SB_ApplyButton(_w) &&
            MGR_Children(_w)[i] !!= SB_HelpButton(_w) &&
            MGR_Children(_w)[i] !!= BB_CancelButton(_w))
        {
            nextras++;
        }
    }

    if (nextras)
        extras = (Widget *)XtMalloc(sizeof(Widget) * nextras);

    nextras = 0;
    for (i = 0; i < numKids; i++)
    {
        if (XtIsManaged(MGR_Children(_w)[i]) &&
            MGR_Children(_w)[i] !!= SB_ListLabel(_w) &&
            (SB_List(_w)
                ? MGR_Children(_w)[i] !!= XtParent(SB_List(_w))
                : True) &&
            MGR_Children(_w)[i] !!= SB_SelectionLabel(_w) &&
            MGR_Children(_w)[i] !!= SB_Text(_w) &&
            MGR_Children(_w)[i] !!= SB_Separator(_w) &&
            MGR_Children(_w)[i] !!= SB_OkButton(_w) &&
            MGR_Children(_w)[i] !!= SB_ApplyButton(_w) &&
            MGR_Children(_w)[i] !!= SB_HelpButton(_w) &&
            MGR_Children(_w)[i] !!= BB_CancelButton(_w))
        {
            extras[nextras] = MGR_Children(_w)[i];
            nextras++;
        }
    }

    nrows = 0;

    /* note the starting from one.  The zero'th child is the "work area" */
    if (nextras > 0) {
        for (i = 1; i < nextras; i++) {
            if (XmIsMenuBar(extras[i]) && XtIsManaged(extras[i]))
                nrows++;
        }
        if (extras[0] && XtIsManaged(extras[0]))
            nrows++;
    }

    if (SB_ListLabel(_w) && XtIsManaged(SB_ListLabel(_w)))
        nrows++;

    if (SB_List(_w) && XtIsManaged(SB_List(_w)))
        nrows++;

    if (SB_SelectionLabel(_w) && XtIsManaged(SB_SelectionLabel(_w)))
        nrows++;

    if (SB_Text(_w) && XtIsManaged(SB_Text(_w)))
        nrows++;

    if (SB_Separator(_w) && XtIsManaged(SB_Separator(_w)))
        nrows++;

    if ((BB_CancelButton(_w) && XtIsManaged(BB_CancelButton(_w))) ||
        (SB_OkButton(_w)     && XtIsManaged(SB_OkButton(_w))) ||
        (SB_ApplyButton(_w)  && XtIsManaged(SB_ApplyButton(_w))) ||
        (SB_HelpButton(_w)   && XtIsManaged(SB_HelpButton(_w))))
        nrows++;
    else {
        for (i = i; i < nextras; i++) {
            if (extras[i] && XtIsManaged(extras[i]) &&
                (XmIsPushButton(extras[i]) || 
                 XmIsPushButtonGadget(extras[i])))
            {
                nrows++;
                break;
            }
        }
    }

    geoSpec = _XmGeoMatrixAlloc(nrows, numKids, 0);
    geoSpec->composite = (Widget)_w;
    geoSpec->instigator = (Widget)_from;
    if (_pref)
        geoSpec->instig_request = *_pref;
    geoSpec->margin_w = BB_MarginWidth(_w) + MGR_ShadowThickness(_w);
    geoSpec->margin_h = BB_MarginHeight(_w) + MGR_ShadowThickness(_w);
    geoSpec->no_geo_request = _XmSelectionBoxNoGeoRequest;

    layoutPtr = &(geoSpec->layouts->row);
    boxPtr = geoSpec->boxes;

    for (i = 1; i < nextras; i++) {
        if (XmIsMenuBar(extras[i]) && XtIsManaged(extras[i]))
        {
            layoutPtr->fix_up = _XmMenuBarFix;
            layoutPtr->space_above = 0;
            boxPtr += 2;
            layoutPtr++;
        }
    }

    if (SB_ChildPlacement(_w) == XmPLACE_TOP && nextras &&
        extras[0] && XtIsManaged(extras[0]) &&
        _XmGeoSetupKid(boxPtr, extras[0]))
    {
            layoutPtr->stretch_height = 1;
            layoutPtr->fill_mode = XmGEO_EXPAND;
            layoutPtr->even_width = 1;
            layoutPtr->even_height = 1;
            layoutPtr->space_above = BB_MarginHeight(_w);
            layoutPtr++;
            boxPtr += 2;
            nrows++;
    }

    if (SB_DialogType(_w) == XmDIALOG_PROMPT &&
        SB_ChildPlacement(_w) == XmPLACE_ABOVE_SELECTION && nextras &&
        extras[0] && XtIsManaged(extras[0]) &&
        _XmGeoSetupKid(boxPtr, extras[0]))
    {
            layoutPtr->stretch_height = 1;
            layoutPtr->fill_mode = XmGEO_EXPAND;
            layoutPtr->even_width = 1;
            layoutPtr->even_height = 1;
            layoutPtr->space_above = BB_MarginHeight(_w);
            layoutPtr++;
            boxPtr += 2;
            nrows++;
    }

    newRow = False;
    if (SB_ListLabel(_w) && XtIsManaged(SB_ListLabel(_w)) &&
        _XmGeoSetupKid(boxPtr, SB_ListLabel(_w)))
    {
        layoutPtr->fill_mode = XmGEO_EXPAND;
        layoutPtr->fit_mode = XmGEO_PROPORTIONAL;
        layoutPtr->even_width = 1;
        layoutPtr->even_height = 1;
        layoutPtr->space_above = BB_MarginHeight(_w);
        layoutPtr->space_between = BB_MarginWidth(_w);
        newRow = TRUE;
        boxPtr++;
    }

    if (newRow)
    {
        layoutPtr++;
        boxPtr++;
    }

    if (SB_DialogType(_w) == XmDIALOG_COMMAND &&
        SB_ChildPlacement(_w) == XmPLACE_ABOVE_SELECTION && nextras &&
        extras[0] && XtIsManaged(extras[0]) &&
        _XmGeoSetupKid(boxPtr, extras[0]))
    {
            layoutPtr->stretch_height = 1;
            layoutPtr->fill_mode = XmGEO_EXPAND;
            layoutPtr->even_width = 1;
            layoutPtr->even_height = 1;
            layoutPtr->space_above = BB_MarginHeight(_w);
            layoutPtr++;
            boxPtr += 2;
            nrows++;
    }

    newRow = FALSE;
    if (SB_List(_w) && XtIsManaged(SB_List(_w)) &&
        _XmGeoSetupKid(boxPtr, XtParent(SB_List(_w))))
    {
        layoutPtr->stretch_height = 1;
        layoutPtr->fill_mode = XmGEO_EXPAND;
        layoutPtr->fit_mode = XmGEO_PROPORTIONAL;
        layoutPtr->even_width = 1;
        layoutPtr->even_height = 1;
        layoutPtr->space_above = 0; /* BB_MarginHeight(_w); */
        layoutPtr->space_between = BB_MarginWidth(_w);
        newRow = TRUE;
        boxPtr++;
    }

    if (newRow)
    {
        layoutPtr++;
        boxPtr++;
    }

    if (SB_DialogType(_w) !!= XmDIALOG_COMMAND &&
        SB_DialogType(_w) !!= XmDIALOG_PROMPT &&
        SB_ChildPlacement(_w) == XmPLACE_ABOVE_SELECTION && nextras &&
        extras[0] && XtIsManaged(extras[0]) &&
        _XmGeoSetupKid(boxPtr, extras[0]))
    {
            layoutPtr->stretch_height = 1;
            layoutPtr->fill_mode = XmGEO_EXPAND;
            layoutPtr->even_width = 1;
            layoutPtr->even_height = 1;
            layoutPtr->space_above = BB_MarginHeight(_w);
            layoutPtr++;
            boxPtr += 2;
            nrows++;
    }

    if (SB_SelectionLabel(_w) && XtIsManaged(SB_SelectionLabel(_w)) &&
        _XmGeoSetupKid(boxPtr, SB_SelectionLabel(_w)))
    {
        layoutPtr->fill_mode = XmGEO_EXPAND;
        layoutPtr->even_width = 0;
        layoutPtr->even_height = 1;
        layoutPtr->space_above = BB_MarginHeight(_w);
        layoutPtr++;
        boxPtr += 2;
    }

    if (SB_Text(_w) && XtIsManaged(SB_Text(_w)) &&
        _XmGeoSetupKid(boxPtr, SB_Text(_w)))
    {
        layoutPtr->fill_mode = XmGEO_EXPAND;
        layoutPtr->stretch_height = 0;
        layoutPtr->even_height = 1;
        layoutPtr->even_width = 0;
        layoutPtr->space_above = 0; /* BB_MarginHeight(_w); */
        boxPtr += 2;
        layoutPtr++;
    }

    if (SB_ChildPlacement(_w) == XmPLACE_BELOW_SELECTION && nextras &&
        extras[0] && XtIsManaged(extras[0]) &&
        _XmGeoSetupKid(boxPtr, extras[0]))
    {
            layoutPtr->stretch_height = 1;
            layoutPtr->fill_mode = XmGEO_EXPAND;
            layoutPtr->even_width = 1;
            layoutPtr->even_height = 1;
            layoutPtr->space_above = BB_MarginHeight(_w);
            layoutPtr++;
            boxPtr += 2;
            nrows++;
    }

    if (SB_Separator(_w) && XtIsManaged(SB_Separator(_w)) &&
        _XmGeoSetupKid( boxPtr, SB_Separator(_w)))
    {
        layoutPtr->fix_up = _XmSeparatorFix;
        layoutPtr->space_above = BB_MarginHeight(_w);
        boxPtr += 2;
        layoutPtr++;
    }

    newRow = False;
    if (SB_OkButton(_w) && XtIsManaged(SB_OkButton(_w)) &&
        _XmGeoSetupKid(boxPtr++, SB_OkButton(_w))) {
        layoutPtr->fill_mode = XmGEO_CENTER;
        layoutPtr->fit_mode = XmGEO_WRAP;
        layoutPtr->even_width = 1;
        layoutPtr->even_height = 1;
        layoutPtr->space_above = BB_MarginHeight(_w);
        newRow = True;
    }
    for (i = 1; i < nextras; i++)
    {
        if (extras[i] && XtIsManaged(extras[i]) &&
            (XmIsPushButton(extras[i]) || XmIsPushButtonGadget(extras[i])) &&
            _XmGeoSetupKid(boxPtr++, extras[i]))
        {
            layoutPtr->fill_mode = XmGEO_CENTER;
            layoutPtr->fit_mode = XmGEO_WRAP;
            layoutPtr->even_width = 1;
            layoutPtr->even_height = 1;
            layoutPtr->space_above = BB_MarginHeight(_w);
            newRow = True;
        }
    }

    if (SB_ApplyButton(_w)  && XtIsManaged(SB_ApplyButton(_w)) &&
        _XmGeoSetupKid(boxPtr++, SB_ApplyButton(_w))) {
        layoutPtr->fill_mode = XmGEO_CENTER;
        layoutPtr->fit_mode = XmGEO_WRAP;
        layoutPtr->even_width = 1;
        layoutPtr->even_height = 1;
        layoutPtr->space_above = BB_MarginHeight(_w);
        newRow = True;
    }
    if (BB_CancelButton(_w) && XtIsManaged(BB_CancelButton(_w)) &&
        _XmGeoSetupKid(boxPtr++, BB_CancelButton(_w))) {
        layoutPtr->fill_mode = XmGEO_CENTER;
        layoutPtr->fit_mode = XmGEO_WRAP;
        layoutPtr->even_width = 1;
        layoutPtr->even_height = 1;
        layoutPtr->space_above = BB_MarginHeight(_w);
        newRow = True;
    }
    if (SB_HelpButton(_w)   && XtIsManaged(SB_HelpButton(_w)) &&
        _XmGeoSetupKid(boxPtr++, SB_HelpButton(_w))) {
        layoutPtr->fill_mode = XmGEO_CENTER;
        layoutPtr->fit_mode = XmGEO_WRAP;
        layoutPtr->even_width = 1;
        layoutPtr->even_height = 1;
        layoutPtr->space_above = BB_MarginHeight(_w);
        newRow = True;
    }

    if (newRow)
    {
        layoutPtr++;
        boxPtr++;
    }

    layoutPtr->space_above = 0; /* BB_MarginHeight(_w); */
    layoutPtr->end = TRUE;
    if (nextras)
        XtFree((char *)extras);
    return(geoSpec);
}
\end{verbatim}

While it may look scary, once you understand what it is doing, it
really isn't.  You can see the advantage of using the GeoUtils in
SelectionBox: other than the code above, there really isn't any trace
of geometry management in SelectionBox -- it's all taken care of
automagically.

Another point to note is SelectionBox's \code{no\_geo\_request()}
method -- it's slightly different, as the Command widget class doesn't
even \emph{have} a \code{geo\_matrix\_create()} method -- instead, it
inherits SelectionBox's.

\index{\_XmSelectionBoxNoGeoRequest@\code{\_XmSelectionBoxNoGeo-\\Request}}%
\begin{verbatim}
Boolean
_XmSelectionBoxNoGeoRequest(XmGeoMatrix _geoSpec)
{
    if (BB_InSetValues(_geoSpec->composite) && 
        (XtClass(_geoSpec->composite) == xmSelectionBoxWidgetClass ||
         XtClass(_geoSpec->composite) == xmCommandWidgetClass))
        return TRUE;
    return FALSE;
}
\end{verbatim}

\section{Conclusion and Credits}

Please keep in mind when reading this document that I'm still
discovering new things in the GeoUtils, and I may not be accurate in
some places. I'd dearly like feedback from those of you who really
know the \Motif{} implementation to point out where I'm wrong.

I'd like to thank John Cwikla (again), for providing sample code about
how to subclass using the GeoUtils; Chris, for starting this project
in the first place; Danny, for motivating me to write this chapter
(thin as it is) -- I guess it really \emph{is} a pain to have
significant portions of your widgets based on somebody else's
undocumented code (I think there may be two or three informational
comments in GeoUtils); and the rest of the core team (Rob, Peter,
perhaps a few more) for helping out; the team as a whole, for putting
up with my mess (what? me opinionated? Nah).

% End of GeoUtils.tex
