Chapter 7. Basic Widget Methods

This chapter describes the initialize, expose, set_values, resize, query_geometry, and destroy methods. It explains when Xt calls each method, and describes in detail what should be in each of these methods. Among other things, these methods prepare for and do the drawing of graphics that appear in a widget. This chapter describes what the Toolkit adds to the graphics model provided by Xlib, but does not describe in detail how to draw using Xlib; this topic is described in Chapters 5, 6 and 7 of Volume One, Xlib Programming Manual. This chapter also describes how to write action routines that work in the widget framework.

This chapter describes the initialize, expose, set_values, resize, query_geometry, and destroy methods. These are the methods you need to write to make a functioning widget (although destroy is optional). The common thread in most of these methods is that they have a role in drawing graphics, although all but the expose method are also responsible for other things. We will describe all the responsibilities of these methods, but focus on the issues involving graphics because they are so important. Additional examples of each method are shown on the reference pages for each method in Section 4 of Volume Five, X Toolkit Intrinsics Reference Manual.

Three of these methods are called by Xt in response to application function calls:

A fourth method, expose, is called in response to Expose events, which occur whenever part or all of a widget's window becomes newly visible on the screen.[49]

When the parent widget, which is usually a geometry-managing Composite or Constraint widget, needs to resize one of its children, it may call the child's query_geometry method to find the child's opinion of the proposed change. Once the parent has actually resized the child, Xt calls the child's resize method, which is responsible for calculating private instance variables, so that the child is prepared to redraw itself in its new window size.

Applications that use the Toolkit should do all their drawing in widgets. Although it is possible to create normal X windows and draw into them from the application code, you lose the advantage of Xt's event dispatching, event filtering, and other features. It's just as easy to draw in a widget, and it's easier to add processing of user input as described in Chapter 8, Events, Translations, and Accelerators.

The X Graphics Model Inside Widgets

Section 4.2.2, "Graphics from the Application" described the X graphics model and how drawing from the application is accomplished. In summary, the process consisted of creating GCs and then drawing from a function that Xt calls on receipt of Expose events. Drawing from inside a widget follows the same general procedure, but the code is organized differently.

Inside a widget, you normally create GCs with the Xt routine XtGetGC() or XtAllocateGC instead of the Xlib routine XCreateGC(). XtGetGC() is similar to the Xlib routine except that it arranges for the sharing of GCs among widgets within an application. This is important because each GC has some overhead and servers can achieve better performance when handling fewer GCs. Because Xt applications often have many instances of the same widget class, each needing the same GC characteristics (unless the user specifies a different color for each one), Xt arranges for them to share GCs when possible. The drawback with XtGetGC() is that no instance is allowed to modify the returned GC, because some other widget instance may be depending on it. XtAllocateGC() supports more flexible sharing. You specify which fields your widget wants the ability to change, and those that it doesn't care about, so that some other widget may get the same GC if it only needs to change the latter fields.

Xt organizes GC creation and drawing into separate methods. Setting initial values for the GC and creating the GC is done in the initialize method, and actual drawing is done in the expose method. This makes it very easy to find this code in existing widgets and straightforward to write this code for new widgets.

Since Xt allows resources to be changed during program execution by calling XtVaSetValues(), the widget code must be prepared to change the GC at any time if the GC components depend on resource values. The set_values method calculates the new values based on resource changes and updates the appropriate GCs.

The next six major sections discuss the code needed to implement the initialize, expose, set_values, resize, query_geometry, and destroy methods.

The initialize Method

As described in Section 6.3.5.2, "Initializing the Core Methods," the initialize method has basically one function: it sets instance structure members (also called instance variables).[50] This job has two parts: setting the initial values of private instance variables, and checking to make sure that the values of the public instance variables are valid (since they are user configurable). Since the initialize method is upward chained (defined in Section 6.3.5.2, "Initializing the Core Methods"), the method for this class needs to initialize only the fields present in the instance part structure for this class. The exceptions are width and height. Even though they are instance variables of Core, they need to be checked by the subclass, because only this class knows its desired initial size.

Example 7-1 shows the initialize method from BitmapEdit.

Example 7-1. The initialize method

/* ARGSUSED */
static void
Initialize(treq, tnew, args, num_args)
Widget treq, tnew;
ArgList args;
Cardinal *num_args;
{
    BitmapEditWidget new = (BitmapEditWidget) tnew;
    new->bitmapEdit.cur_x = 0;
    new->bitmapEdit.cur_y = 0;
    /*
      *  Check instance values set by resources that may be invalid.
      */
    if ((new->bitmapEdit.pixmap_width_in_cells < 1) ||
                (new->bitmapEdit.pixmap_height_in_cells < 1))  {
        XtWarning("BitmapEdit: pixmapWidth and/or pixmapHeight\
                is too small (using 10 x 10).");
        new->bitmapEdit.pixmap_width_in_cells = 10;
        new->bitmapEdit.pixmap_height_in_cells = 10;
    }
    if (new->bitmapEdit.cell_size_in_pixels < 5) {
        XtWarning("BitmapEdit: cellSize is too small (using 5).");
        new->bitmapEdit.cell_size_in_pixels = 5;
    }
    if ((new->bitmapEdit.cur_x < 0) ||
                (new->bitmapEdit.cur_y < 0)) {
        XtWarning("BitmapEdit: cur_x and cur_y must be\
                non-negative (using 0, 0).");
        new->bitmapEdit.cur_x = 0;
        new->bitmapEdit.cur_y = 0;
    }
    /*
     * Allocate memory to store array of cells in bitmap, if not
     * already done by application.
     */
    if (new->bitmapEdit.cell == NULL)
        new->bitmapEdit.cell =
                XtCalloc(new->bitmapEdit.pixmap_width_in_cells
                * new->bitmapEdit.pixmap_height_in_cells,
                sizeof(char));
    else
        new->primitive.user_data = True; /* user supplied cell array */
    /* Calculate useful values */
    new->bitmapEdit.pixmap_width_in_pixels =
            new->bitmapEdit.pixmap_width_in_cells
            * new->bitmapEdit.cell_size_in_pixels;
    new->bitmapEdit.pixmap_height_in_pixels =
            new->bitmapEdit.pixmap_height_in_cells
            * new->bitmapEdit.cell_size_in_pixels;
    /*
     * Motif Primitive sets width and height to provide enough room
     * for the highlight and shadow around a widget.  (This means
     * it does not honor user-specified sizes for widgets.)
     * BitmapEdit doesn't use these features.  A widget that did use
     * these features would *add* its desired dimensions to those set
     * by Primitive.  To use this widget with another widget set,
     * remove the following two lines.
     */
    new->core.width = 0;
    new->core.height = 0;
    /* set initial size of window */
    if (new->core.width == 0) {
        if (new->bitmapEdit.showAll == False)
            new->core.width =
                    (new->bitmapEdit.pixmap_width_in_pixels >
                    DEFAULTWIDTH) ? DEFAULTWIDTH :
                    (new->bitmapEdit.pixmap_width_in_pixels);
        else
            new->core.width =
                    new->bitmapEdit.pixmap_width_in_pixels;
    }
    if (new->core.height == 0) {
        if (new->bitmapEdit.showAll == False)
            new->core.height =
                    (new->bitmapEdit.pixmap_height_in_pixels >
                    DEFAULTWIDTH) ? DEFAULTWIDTH :
                    (new->bitmapEdit.pixmap_height_in_pixels);
        else
            new->core.height =
                    new->bitmapEdit.pixmap_height_in_pixels;
    }
    /* Tell Primitive not to allow tabbing to this widget,
     * since there is no keyboard interface for using it. */
    XtVaSetValues(new,
        XmNtraversalOn, False,
        NULL);
    CreateBigPixmap(new);
    GetDrawGC(new);
    GetUndrawGC(new);
    GetCopyGC(new);
    DrawIntoBigPixmap(new);
}


Even though the specific instance variables initialized here are particular to BitmapEdit, the techniques are common to all initialize methods. Some private instance variables, such as cur_x and cur_y, do not depend on resource settings and are simply initialized to a fixed value. Public instance variables are checked; if their values are out of range, XtWarning() is called to print a message on the standard output and they are instead initialized to a fixed value. Some private instance variables are set based on public instance variables. GCs are the most common example.

As you should recall from Chapter 6, Inside a Widget, all the variables set in initialize are declared in the BitmapEditPart instance structure in the private header file.

Note that most of the arguments of initialize are rarely used. When initialize is called, the request argument is an instance structure containing the same values as new. But as new is changed, request provides a sometimes useful record of the resource settings originally made by the user. The args and num_args arguments contain the resource settings provided in the call to XtVaCreateManagedWidget(). These are sometimes useful in distinguishing between settings made by the application (args) from settings made by the user (which already appear in new and request). The code in the initialize_hook method, described in Chapter 10, Resource Management and Type Conversion, can optionally be placed in the initialize method.

Creating GCs

In any widget that does drawing, some of the private instance variables will hold the IDs of GCs created in the initialize method. These variables will be read when the GCs are needed in the expose method, and will be reset if necessary in the set_values method. Example 7-1 called separate routines, GetDrawGC, GetUndrawGC, and GetCopyGC to create the GCs. Example 7-2 shows these routines.

A program prepares for creating a GC by setting the desired characteristics of the GC into members of a large structure called XGCValues (defined by Xlib), and specifying which members of XGCValues it has provided by setting a bitmask. This bitmask is made by ORing the GC mask symbols defined in <X11/X.h>. Each bitmask symbol represents a member of the XGCValues structure. Every GC field has a default value, so only those values that differ from the default need to be set. The default GC values are discussed in Volume One, Xlib Programming Manual.

Widget code normally creates a GC with the XtGetGC() call, not the Xlib analogue XCreateGC(). XtGetGC() keeps track of requests to get GCs by all the widgets in an application, and creates new server GCs only when a widget requests a GC with different values. In other words, after the first widget creates a GC with XtGetGC(), any subsequent widget that calls XtGetGC() to create a GC with the same values will get the same GC, not a new one. Using this client-side caching also reduces the number of requests to the server.

Xlib provides XChangeGC() to change the values in an existing GC. Xt provides no analogue. Because of the sharing of GCs allocated with XtGetGC(), you must treat them as read-only. If you don't, you may confuse or break other widgets. In cases where your widget creates a GC that needs changing in such a way that it is impractical to create a new GC each time, it may be appropriate to use the Xlib call XCreateGC() instead of the Xt call XtGetGC().

BitmapEdit must use XCreateGC() for two of its three GCs because these GCs are for drawing into pixmaps of depth 1, while XtGetGC() always creates GCs for drawing into windows or pixmaps of the default depth of the screen. In other words, XtGetGC() would work on a monochrome screen, but not on color. However, this is an unusual situation. Most widgets can use XtGetGC() exclusively.

Example 7-2. Creating GCs from the initialize method

static void
GetDrawGC(w)
Widget w;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    XGCValues values;
    XtGCMask mask = GCForeground | GCBackground | GCDashOffset |
            GCDashList | GCLineStyle;
    /*
     * Setting foreground and background to 1 and 0 looks like a
     * kludge but isn't.  This GC is used for drawing
     * into a pixmap of depth one.  Real colors are applied with a
     * separate GC when the pixmap is copied into the window.
     */
    values.foreground = 1;
    values.background = 0;
    values.dashes = 1;
    values.dash_offset = 0;
    values.line_style = LineOnOffDash;
    cw->bitmapEdit.draw_gc = XCreateGC(XtDisplay(cw),
            cw->bitmapEdit.big_picture, mask, &values);
}
static void
GetUndrawGC(w)
Widget w;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    XGCValues values;
    XtGCMask mask = GCForeground | GCBackground;
    /* Looks like a kludge but isn't--see comment in GetDrawGC */
    values.foreground = 0;
    values.background = 1;
    cw->bitmapEdit.undraw_gc = XCreateGC(XtDisplay(cw),
            cw->bitmapEdit.big_picture, mask, &values);
}
static void
GetCopyGC(w)
Widget w;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    XGCValues values;
    XtGCMask mask = GCForeground | GCBackground;
    values.foreground = cw->primitive.foreground;
    values.background = cw->core.background_pixel;
    /* This GC is the same depth as screen */
    cw->bitmapEdit.copy_gc = XtGetGC(cw, mask, &values);
}

As shown in Example 7-2, some GC components are set based on other instance variables, and some are just hardcoded. In this example, the foreground pixel value (color) in the GC is set to be the value of the foreground instance variable, which is derived from a resource of Primitive.[51] The background pixel value is derived from a resource of the Core widget. (Core defines a background resource but no foreground.) On the other hand, the line_style member of XGCValues is set to LineOnOffDash regardless of resource settings. In general, you hardcode the GC components that you don't want to be user customizable. (All these instance variables are defined in the yourwidgetPart structure defined in yourwidgetP.h.)

Certain GC parameters are traditionally handled as resources rather than being hardcoded. Colors and fonts are the most basic examples. In the X Window System you can't use a color until you allocate a pixel value for it, and you can't use a font until you load it. The code that processes resources called by XtAppInitialize() automatically allocates and loads the colors and fonts specified as resources, and sets the instance variables to the right representation type as specified in the resource table. Therefore, it is actually easier to provide user customizability than to convert values to the representation types required to hardcode them.

How the resource conversion process works is described in more detail in Chapter 10, Resource Management and Type Conversion. To decide which GC settings you need for your application, see Chapter 5, The Graphics Context, in Volume One, Xlib Programming Manual.

The expose Method

The expose method is responsible for initially drawing into a widget's window and for redrawing the window every time a part of the window becomes exposed. This redrawing is necessary because the X server does not maintain the contents of windows when they are obscured. When a window becomes visible again, it must be redrawn.

The expose method usually needs to modify its drawing based on the geometry of the window and other instance variables set in other methods. For example, the Label widget will left-justify, center, or right-justify its text according to the XmNalignment resource, and the actual position to draw the text depends on the widget's current size. The Label widget has an instance structure field called alignment, which is set initially in the initialize method, and is read in Label's expose method.

Another factor to consider when writing the expose method is that many widgets also draw from action routines, in response to user events. For example, BitmapEdit toggles bitmap cells in action routines. The expose method must be capable of redrawing the current state of the widget at any time. This means that action routines usually set instance variables when they draw so that the expose method can read these instance variables and draw the right thing.

Most widgets keep track of what they draw in some form of arrays or display lists. When they need to redraw, they simply replay the saved drawing commands in the original order to redraw the window. For example, BitmapEdit keeps track of the state of each bitmap cell in a character array. It could easily traverse this array and redraw each cell that is set in the array.

However, BitmapEdit does not use this strategy. In order to improve its scrolling performance, the expose method copies an off-screen pixmap into the window whenever redisplay is required. The actions draw into this off-screen pixmap (in addition to updating the character array), and then call the expose method directly to have the correct portion of the pixmap copied to the window.

The expose method is passed an event that contains the bounding box of the area exposed. To achieve maximum performance it copies only this area from the pixmap to the window. The BitmapEdit actions take advantage of this, too. They manufacture an artificial event containing the bounding box of the cell to be toggled, and pass it when they call expose. This causes the expose method to copy that one cell that was just updated to the window. Example 7-3 shows the expose method from the BitmapEdit widget.

Example 7-3. The expose method

/* ARGSUSED */
static void
Redisplay(w, event, region)
Widget cw;
XExposeEvent *event;
Region region;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    register int x, y;
    unsigned int width, height;
    if (!XtIsRealized(cw))
        return;
    if (event) {  /* called from btn-event */
        x = event->x;
        y = event->y;
        width = event->width;
        height =  event->height;
    }
    else {/* called because of expose */
        x = 0;
        y = 0;
        width = cw->bitmapEdit.pixmap_width_in_pixels;
        height = cw->bitmapEdit.pixmap_height_in_pixels;
    }
    if (DefaultDepthOfScreen(XtScreen(cw)) == 1)
        XCopyArea(XtDisplay(cw), cw->bitmapEdit.big_picture,
                XtWindow(cw), cw->bitmapEdit.copy_gc, x +
                cw->bitmapEdit.cur_x, y + cw->bitmapEdit.cur_y,
                width, height, x, y);
    else
        XCopyPlane(XtDisplay(cw), cw->bitmapEdit.big_picture,
                XtWindow(cw), cw->bitmapEdit.copy_gc, x +
                cw->bitmapEdit.cur_x, y + cw->bitmapEdit.cur_y,
                width, height, x, y, 1);
}


Note that the expose method first checks to see that the widget is realized using XtIsRealized(). This is a precaution against the unlikely event that an instance of this widget is suddenly destroyed or unrealized by an application while Expose events are still pending. If this did happen, drawing on the nonexistent window would cause an X Protocol error.

Next, BitmapEdit's expose method sets the rectangle it will redraw based on the event passed in by Xt. We also call this method directly from the action that processes button presses. That action routine creates a pseudo-event to pass to expose to describe the area to be drawn.

If the compress_exposure field of the class structure is initialized to XtExposeCompressMultiple (or one of a few other constants, described in Chapter 9, More Input Techniques), as it is in BitmapEdit, Xt automatically merges the multiple Expose events that may occur because of a single user action into one Expose event. In this case, the Expose event contains the bounding box of the areas exposed. BitmapEdit redraws everything in this bounding box. For widgets that are very time-consuming to redraw, you might want to use the third argument of the expose method, which is a region. The Region type is opaquely defined by Xlib (internally a linked list of rectangles). The Region passed into expose describes the union of all the areas exposed by a user action. You can use this region to clip output to the exposed region, and possibly calculate which drawing primitives affect this area. Xlib provides region mathematics routines (such as XRectInRegion()) to compare the regions in which your widget needs to draw with the region needing redrawing. If certain areas do not require redrawing, you can skip the code that redraws them, thereby saving valuable time. However, if this calculation is complicated, its cost/benefit ratio should be examined. Consider the arrangement of windows shown in Figure 7-1.

Window 1 and Window 3 are other applications, and Widget 2 is an application consisting solely of our widget.

Initially, Window 3 is on top, Window 1 is behind it, and Widget 2 is hidden completely behind Window 1. When Window 1 is lowered, Widget 2 becomes visible, except where it is still overlapped by Window 3. The newly-exposed area can be described by two rectangles; A and B. If compress_exposure is False, Widget 2's expose method will be called twice and passed an Expose event first describing Rectangle A, then Rectangle B. But if compress_exposure is True, Widget 2's expose method will be called just once, passed an Expose event describing the bounding box of all the original Expose events (which would be the entire widget in this case), and passed a Region which is the union of the rectangles described by all of the Expose events. The region argument of the expose method is unused unless compress_exposure is True. Each of these exposure-handling techniques may be the best for certain widgets. For a widget like BitmapEdit, any of the three methods will work, but the bounding box method is the most efficient and convenient. For a complete description of Expose event handling strategies, see Chapter 8, Events, in Volume One, Xlib Programming Manual.

The remainder of BitmapEdit's expose method shown in Example 7-3 consists of a single Xlib call to copy from a pixmap into the widget's window. As described in Chapter 4, An Example Application, BitmapEdit makes a large pixmap that is one plane deep and draws the

Figure 7-1. compress_exposure: 2 rectangles if XtExposeNoCompress; bounding box and region if XtExposeCompressSeries or XtExposeCompressMultiple

current bitmap into it. When needed in the expose method, this pixmap just has to be copied into the window. This approach was chosen for its simplicity. When scrollbars are added, the widget is able to pan around in the large bitmap quickly and efficiently. Note that one of two Xlib routines is called based on the depth of the screen. This is because XCopyArea() is slightly more efficient than XCopyPlane() and should be used when running on a monochrome screen.

Note that instance variables are used for the arguments of the Xlib routines in Example 7-3. Don't worry about exactly what each Xlib routine does or the meaning of each argument. See the reference page for each routine in Volume Two, Xlib Reference Manual, when you need to call them in your code.

See Chapters 5, 6, and 7 in Volume One, Xlib Programming Manual, for more information on the GC, drawing graphics, and color, respectively. There is another example of an expose method on the expose reference page in Section 4 of Volume Five, X Toolkit Intrinsics Reference Manual.

The set_values Method

When the application calls XtVaSetValues() (or XtSetValues()) to change widget resources during run time, Xt calls the set_values method. The set_values method is where a widget responds to changes in its public instance variables. It should validate the values of the public variables, and recalculate any private variables that depend on public variables that have changed.

Example 7-4 shows the set_values method for BitmapEdit.

Example 7-4. The set_values method

/* ARGSUSED */
static Boolean
SetValues(current, request, new, args, num_args)
Widget current, request, new;
ArgList args;
Cardinal *num_args;
{
    BitmapEditWidget curcw = (BitmapEditWidget) current;
    BitmapEditWidget newcw = (BitmapEditWidget) new;
    Boolean do_redisplay = False;
    if (curcw->primitive.foreground != newcw->primitive.foreground) {
        XtReleaseGC(curcw, curcw->bitmapEdit.copy_gc);
        GetCopyGC(newcw);
        do_redisplay = True;
    }
    if ((curcw->bitmapEdit.cur_x != newcw->bitmapEdit.cur_x) ||
            (curcw->bitmapEdit.cur_y != newcw->bitmapEdit.cur_y))
        do_redisplay = True;
    if (curcw->bitmapEdit.cell_size_in_pixels !=
            newcw->bitmapEdit.cell_size_in_pixels) {
        ChangeCellSize(curcw, newcw->bitmapEdit.cell_size_in_pixels);
        do_redisplay = True;
    }
    if (curcw->bitmapEdit.pixmap_width_in_cells !=
            newcw->bitmapEdit.pixmap_width_in_cells)  {
        newcw->bitmapEdit.pixmap_width_in_cells =
                curcw->bitmapEdit.pixmap_width_in_cells;
        XtWarning("BitmapEdit: pixmap_width_in_cells cannot\
                be set by XtSetValues. \n");
    }
    if (curcw->bitmapEdit.pixmap_height_in_cells !=
            newcw->bitmapEdit.pixmap_height_in_cells) {
        newcw->bitmapEdit.pixmap_height_in_cells =
                curcw->bitmapEdit.pixmap_height_in_cells;
        XtWarning("BitmapEdit: pixmap_height_in_cells cannot\
                be set by XtSetValues. \n");
    }
    return do_redisplay;
}


The set_values method is called with three copies of the widget's instance structure as arguments: current, request, and new. The current copy includes the current settings of the instance variables, and request includes the settings made through XtVaSetValues() but not yet changed by the superclasses' set_values methods. The new copy is the same as request except that it has already been processed by the set_values methods of all superclasses.

For each public variable, set_values compares the current value and the new value, and if they are different, validates the new value or changes any private values that depend on it. The new copy is the only one that the method changes; request and current are only for reference. As in the initialize method, you have to deal only with the instance variables for your subclass, and perhaps with width and height, because the set_values method is downward chained. Superclass set_values methods take care of setting all the superclass instance fields. However, if desired, you can change superclass fields in your method since it is called last. For example, this might be useful if your class has different criteria for determining valid values or dependencies.

The request copy of the instance variables is used only if your class needs to decide between a superclass setting and your widget's setting; disagreements about this usually occur over the size of the widget. For more information on when to use request, see the reference page for set_values in Volume Five, X Toolkit Intrinsics Reference Manual.

It is also important to notice that the set_values method, if it exists at all (that is, if it is not specified as NULL in the class structure), must return True or False to indicate whether the changes made to the state variables require the widget to be redrawn. If it is True for this class or any superclass, Xt calls the Xlib routine XClearArea() with the exposures argument set to True (to force the background of the widget to be redrawn, which normally occurs only after Expose events), and then Xt calls the expose method. In other words, you should make set_values return True whenever the changes to the instance variables will change what is drawn in the widget.

Note, however, that set_values should not return True if width and height change, because the X server automatically generates Expose events for a window when it is resized. If you do return True in this case, your expose method will be called twice.

Since the application may call XtVaSetValues() before the widget is realized, it is important not to assume in the set_values method that the widget has a window. In other words, the code must use XtIsRealized() to check whether the widget is realized before using the XtWindow() macro, such as in a call to set window attributes.

As mentioned earlier, some of the private variables are usually GCs. Whenever the public variable for a color or font is changed through XtVaSetValues(), a GC also has to be changed. This is an example of a private variable depending on a public one. GCs allocated with XtGetGC() should not be changed since they may be shared by other widgets. Therefore, the normal response to a change in one of the resources on which a GC depends is to release the GC with XtReleaseGC() and request a new one with XtGetGC().

If some of your widget's GCs are unlikely to be shared by other widget instances in an application (either because each instance will use different values for the GC components or there will be only one instance), and the widget needs to be able to make changes to them, and the changes are not predictable enough or few enough to reasonably create a GC for each variation, then the XtGetGC()/XtReleaseGC() approach is not ideal. What happens in this situation is that XtGetGC() creates a new GC and XtReleaseGC() frees the old GC every time a change is made, even if the changes are small. It is more efficient in this situation to change the affected values in the existing GC. But this can be done only with an Xlib routine--there is no Toolkit routine for changing a GC. To implement this approach, you create, modify, and free the unusual GC using Xlib routines only. You call XCreateGC() in the initialize method to create the GC, XChangeGC() in the set_values method to change the GC, and XFreeGC() in the destroy method to free the GC. All of these Xlib routines are described in Chapter 5, The Graphics Context, in Volume One, Xlib Programming Manual.

XtGetGC() always creates a GC that can only be used on drawables (windows and pixmaps) of the default depth of the screen. Drawing into a pixmap of depth one using a GC created with XtGetGC() works on a monochrome display, but does not work on a color display. (The depth is the number of bits per pixel used to represent colors on the screen.) This is another situation in which you will need to call XCreateGC() instead of XtGetGC().

Note also that BitmapEdit does not allow two of its resources to be changed by XtVaSetValues(); they can be set only until the widget is created. To disallow changes to a resource, the set_values method must actively set the value in the new widget (newcw) to the value in the current widget (curcw), wiping out the setting made through XtVaSetValues(). It is also advisable that the widget print a message describing that the resource cannot be set through XtVaSetValues().

The XtWarning() call used in the example is simply an alternative to calling fprintf, that helps to make all error messages uniform, and is described in Chapter 14, Miscellaneous Toolkit Programming Techniques. The code in the set_values_hook method, described in Chapter 10, Resource Management and Type Conversion, can optionally be placed in the set_values method.

The resize Method

When a widget is used in an application, it is created with a parent that will manage its geometry. Depending on the layout policy of this parent widget in the application, the widget's window may change size when the application is resized.[52] Most widgets need to recalculate the position and size of their graphics when their window changes size. This is the job of the resize method.

The resize method is passed only one argument, the widget instance structure pointer (of type Widget). This structure contains the new position, size, and border width of the widget's window. The method changes any instance part fields that depend on the size or position of the widget. When the resize method returns, Xt calls the expose method, regardless of whether or not the contents need redrawing. (It is a basic characteristic of the X server that it generates Expose events when a window is resized.)

A Label widget whose text is centered would reset the starting position of its text in the resize method.

In some widgets, it takes some thought to determine the correct response to resizing. Take BitmapEdit, for example. BitmapEdit can be configured to show only a portion of the bitmap, so that scrollbars can pan around in the complete bitmap. When the application is resized, should BitmapEdit show more cells or increase the cell size? Up to the point where the entire bitmap is shown, it is easier to increase the number of cells shown. When BitmapEdit is resized larger than necessary to show the entire bitmap, it should probably increase the cell size. We will use this strategy in the resize method of the BitmapEdit widget, which is shown in Example 7-5.[53]

Example 7-5. BitmapEdit: the resize method

/* ARGSUSED */
static void
Resize(w)
Widget w;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
             /* resize does nothing unless new
              * size is bigger than entire pixmap */
    if ((cw->core.width > cw->bitmapEdit.pixmap_width_in_pixels) &&
            (cw->core.height >
            cw->bitmapEdit.pixmap_height_in_pixels)) {
         /* Calculate the maximum cell size that will
          * allow the entire bitmap to be displayed. */
        Dimension w_temp_cell_size_in_pixels,
                h_temp_cell_size_in_pixels;
        Dimension new_cell_size_in_pixels;
        w_temp_cell_size_in_pixels = cw->core.width /
                cw->bitmapEdit.pixmap_width_in_cells;
        h_temp_cell_size_in_pixels = cw->core.height /
                cw->bitmapEdit.pixmap_height_in_cells;
        if (w_temp_cell_size_in_pixels < h_temp_cell_size_in_pixels)
            new_cell_size_in_pixels = w_temp_cell_size_in_pixels;
        else
            new_cell_size_in_pixels = h_temp_cell_size_in_pixels;
        /* if size change mandates a new pixmap, make one */
        if (new_cell_size_in_pixels
                != cw->bitmapEdit.cell_size_in_pixels)
            ChangeCellSize(cw, new_cell_size_in_pixels);
    }
}
static void
ChangeCellSize(w, new_cell_size)
Widget w;
int new_cell_size;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    int x, y;
    cw->bitmapEdit.cell_size_in_pixels = new_cell_size;
    /* recalculate variables based on cell size */
    cw->bitmapEdit.pixmap_width_in_pixels =
            cw->bitmapEdit.pixmap_width_in_cells *
            cw->bitmapEdit.cell_size_in_pixels;
    cw->bitmapEdit.pixmap_height_in_pixels =
            cw->bitmapEdit.pixmap_height_in_cells *
            cw->bitmapEdit.cell_size_in_pixels;
    /* destroy old and create new pixmap of correct size */
    XFreePixmap(XtDisplay(cw), cw->bitmapEdit.big_picture);
    CreateBigPixmap(cw);
    /* draw lines into new pixmap */
    DrawIntoBigPixmap(cw);
    /* draw current cell array into pixmap */
    for (x = 0; x < cw->bitmapEdit.pixmap_width_in_cells; x++) {
        for (y = 0; y < cw->bitmapEdit.pixmap_height_in_cells; y++) {
            if (cw->bitmapEdit.cell[x + (y *
                    cw->bitmapEdit.pixmap_width_in_cells)] == DRAWN)
                DoCell(cw, x, y, cw->bitmapEdit.draw_gc);
            else
                DoCell(cw, x, y, cw->bitmapEdit.undraw_gc);
        }
    }
}
static void
DoCell(w, x, y, gc)
Widget w;
int x, y;
GC gc;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    /* otherwise, draw or undraw */
    XFillRectangle(XtDisplay(cw), cw->bitmapEdit.big_picture, gc,
            cw->bitmapEdit.cell_size_in_pixels * x + 2,
            cw->bitmapEdit.cell_size_in_pixels * y + 2,
            (unsigned int)cw->bitmapEdit.cell_size_in_pixels - 3,
            (unsigned int)cw->bitmapEdit.cell_size_in_pixels - 3);
}


Because of the two-phase resize strategy used by BitmapEdit, and because BitmapEdit uses a pixmap in its repaint strategy, this resize method is more complicated than most. When the widget is resized larger that necessary to show the entire bitmap in the current cell_size, it destroys the current pixmap and creates a new one. Since this should not happen very often and because resizing does not require extremely fast response, the time it takes to recreate the pixmap and draw into it is acceptable.

One difficulty in writing the resize method is that you do not have access to the old size or position of the window. The widget instance structure passed in has already been updated with the current size and position. If you need the old information, you can cache the old size in an instance part field, set first in the initialize method, and again at the end of the resize method.

It is also important to note that the resize method is not allowed to request that the parent resize the widget again to get a better size. (How to suggest properly to the parent that your widget be resized is described in Chapter 12, Geometry Management.)

The query_geometry Method

When your widget is used in an application, its parent widget will be a composite or constraint widget that manages its size and position. Parent widgets need to know the preferred size of a widget so they can make good decisions about the size of each child. The query_geometry method is called when the parent is about to make a size change to some of its children but is not yet sure which ones and by how much. (How geometry management works is described in Chapter 12, Geometry Management. For now, we are concentrating on what you need to do to write a simple widget.)

If your widget specifies NULL in the class structure for the query_geometry method, the parent will be told that your widget's current geometry is its preferred geometry. This is often wrong information. For example, if your widget has already been resized to be one-pixel-by-one-pixel because the user has resized an application to be very small, the parent would receive the message that your widget prefers to be that small. When the application is resized to be larger again, the parent will have no information on which to base its resizing decisions. Even if your widget has no particular preference for size, it is a good idea to specify the widget's default size in query_geometry. Then, at least, the parent has a ballpark figure for typical sizes for your widget. The parent could at least find out that BitmapEdit is intended to be larger than a Label widget.

The query_geometry method is passed pointers to two copies of the XtWidgetGeometry structure, one containing the parent's intended size for your widget, and the other to contain your reply to the parent's suggestion. The XtWidgetGeometry structure is shown in Example 7-6.

Example 7-6. The XtWidgetGeometry structure

typedef struct {
    XtGeometryMask request_mode;
    Position x, y;
    Dimension width, height;
    Dimension border_width;
    Widget sibling;
    int stack_mode;
} XtWidgetGeometry;

The request_mode field is a mask that indicates which other fields in the structure are set. It is a bitwise OR of any or all of the symbolic constants shown in Table 7-1.

Table 7-1. XtWidgetGeometry request_mode Symbols

Symbol

Description

CWX

The x coordinate of the widget's top left corner is specified.

CWY

The y coordinate of the widget's top left corner is specified.

CWWidth

The widget's width is specified.

CWHeight

The widget's height is specified.

CWBorderWidth

The widget's borderwidth is specified.

CWSibling

A sibling widget is specified, relative to which this widget's stacking order should be determined.

CWStackMode

A stack_mode value is present, specifying how this widget should be stacked relative to the widget identified as sibling .

The sibling and stack_mode fields are used together to indicate where in the stacking order of its siblings your widget will be placed. The symbols for stack_mode are Above Below, TopIf, BottomIf, Opposite, and XtSMDontChange. (These symbols are used singly, not combined with OR.) Their meanings are summarized in Table 7-2.

Table 7-2. XtWidgetGeometry stack_mode Symbols

Stacking Flag

Position

Above

w is placed just above sibling . If no sibling is specified, w is placed at the top of the stack.

Below

w is placed just below sibling . If no sibling is specified, w is placed at the bottom of the stack.

TopIf

If sibling obscures w , then w is placed at the top of the stack. If no sibling is specified, then if any sibling obscures w , w is placed at the top of the stack.

BottomIf

If w obscures sibling , then w is placed at the bottom of the stack. If no sibling is specified, then if w obscures any sibling , w is placed at the bottom of the stack.

Opposite

If sibling occludes w , w is placed at the top of the stack. If w occludes sibling , w is placed at the bottom of the stack. If no sibling is specified, then if any sibling occludes w , w is placed at the top of the stack, or if w occludes any sibling , w is placed at the bottom of the stack.

XtSMDontChange

Current position in stacking order is maintained.

Note that Xt's handling of stacking order is currently incomplete, and these symbols might not be honored. By default, the most recently created widget appears on the bottom.

One more issue about the query_geometry method must be raised before showing an example: the method's return value. The query_geometry method must return one of the three enum values XtGeometryYes, XtGeometryAlmost, or XtGeometryNo. XtGeometryResult is an enum name, and it is the returned type of query_geometry.

  • If the proposed geometry is acceptable without modification, query_geometry returns XtGeometryYes.

  • If the proposed geometry is not acceptable, your widget returns XtGeometryAlmost and sets its suggested changes to the proposed geometry back in the reply structure.

  • If the proposed geometry is the same as the current geometry, query_geometry returns XtGeometryNo. This symbol is slightly misleading--think of it as XtGeometryNoChange (a symbol that is not defined). The symbol is XtGeometryNo because all three of these symbols are used in another context within composite and constraint widgets, as is described in Chapter 12, Geometry Management.

Note that, in all three cases, query_geometry must set any fields in the reply structure that it potentially cares about, even if it is only accepting the proposed geometry.

Example 7-7 shows the query_geometry method from BitmapEdit. All widgets should have at least this code in the method, substituting their initial size, or their current preferred size if known. (For example, a Label widget would set its preferred size based on the width and height of the current string.)

Example 7-7. BitmapEdit: the query_geometry method

static XtGeometryResult QueryGeometry(w, proposed, answer)
Widget w;
XtWidgetGeometry *proposed, *answer;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    /* set fields we care about */
    answer->request_mode = CWWidth | CWHeight;
    /* suggest our default width or full size, whichever smaller */
    answer->width = (cw->bitmapEdit.pixmap_width_in_pixels >
            DEFAULTWIDTH) ? DEFAULTWIDTH :
            cw->bitmapEdit.pixmap_width_in_pixels;
    answer->height = (cw->bitmapEdit.pixmap_height_in_pixels >
            DEFAULTHEIGHT) ? DEFAULTHEIGHT :
            cw->bitmapEdit.pixmap_height_in_pixels;
    if (  ((proposed->request_mode & (CWWidth | CWHeight))
            == (CWWidth | CWHeight)) &&
            proposed->width == answer->width &&
            proposed->height == answer->height)
        return XtGeometryYes;
    else if (answer->width == cw->core.width &&
            answer->height == cw->core.height)
        return XtGeometryNo;
    else
        return XtGeometryAlmost;
}


The destroy Method

When a widget is destroyed by the application, its destroy methods are invoked in subclass to superclass order. Therefore, the destroy method for any given class needs to free only the memory allocated by itself; it need not worry about memory allocated by superclasses.

Any server resources created by Xt (such as GCs requested through XtGetGC()) should be freed in the destroy method. In addition, if you called any Xlib routines, such as XCreateGC(), that allocate server- or client-side resources, be sure to free them here. BitmapEdit creates pixmaps for use in the drawing process, so it must free them. It must also free the GCs it allocated.

If this is not done, then the server resources allocated for the widget will not be freed until the application exits. This is not a fatal problem. It matters only in applications that destroy widgets and then continue running for a while before they exit, which is unusual. Example 7-8 shows the destroy method code from the BitmapEdit widget. It frees the pixmaps created in the initialize method shown in Example 7-1.

Example 7-8. The destroy method

static void
Destroy(w)
Widget w;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    if (cw->bitmapEdit.big_picture)
        XFreePixmap(XtDisplay(cw), cw->bitmapEdit.big_picture);
    if (cw->bitmapEdit.draw_gc)
        XFreeGC(XtDisplay(cw), cw->bitmapEdit.draw_gc);
    if (cw->bitmapEdit.undraw_gc)
        XFreeGC(XtDisplay(cw), cw->bitmapEdit.undraw_gc);
    if (cw->bitmapEdit.copy_gc)
        XtReleaseGC(cw, cw->bitmapEdit.copy_gc);
    /* Free memory allocated with Calloc.  This was done
     * only if application didn't supply cell array.
     */
    if (!cw->primitive.user_data)
        XtFree(cw->bitmapEdit.cell);
}


If your widget allocated memory for any of its instance variables (or other global variables) using the Toolkit routines XtMalloc() or XtCalloc() (which operate just like the C library but add error checking), then it should free that memory here with XtFree(). Many widgets, including BitmapEdit, allow the application to supply the working memory, or the widgets can allocate it themselves. BitmapEdit maintains a flag (user_allocated) to indicate who originally allocated the memory.

If your widget called XtAddEventHandler() or XtAddTimeOut(), then you should call XtRemoveEventHandler() and XtRemoveTimeOut(), respectively (these routines are described in Chapter 9, More Input Techniques).

Actions in the Widget Framework

Although actions, strictly speaking, are not methods, writing them is part of the process of writing a simple widget. Fortunately, you have already seen action routines added from the application. Action routines look and work the same in the widget framework as in the application, except that they use instance structure fields as data instead of the application data structure fields. Example 7-9 shows the actions of BitmapEdit.

Example 7-9. BitmapEdit: action routines

/*ARGUSED*/
static void
DrawCell(w, event)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    DrawPixmaps(cw->bitmapEdit.draw_gc, DRAW, cw, event);
}
/*ARGUSED*/
static void
UndrawCell(w, event)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    DrawPixmaps(cw->bitmapEdit.undraw_gc, UNDRAW, cw, event);
}
/*ARGUSED*/
static void
ToggleCell(w, event)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    static int oldx = -1, oldy = -1;
    GC gc;
    int mode;
    int newx, newy;
    /* This is strictly correct, but doesn't
      * seem to be necessary */
    if (event->type == ButtonPress) {
        newx = (cw->bitmapEdit.cur_x + ((XButtonEvent *)event)->x) /
                cw->bitmapEdit.cell_size_in_pixels;
        newy = (cw->bitmapEdit.cur_y + ((XButtonEvent *)event)->y) /
                cw->bitmapEdit.cell_size_in_pixels;
    }
    else  {
        newx = (cw->bitmapEdit.cur_x + ((XMotionEvent *)event)->x) /
                cw->bitmapEdit.cell_size_in_pixels;
        newy = (cw->bitmapEdit.cur_y + ((XMotionEvent *)event)->y) /
                cw->bitmapEdit.cell_size_in_pixels;
    }
    if ((mode = cw->bitmapEdit.cell[newx + newy *
            cw->bitmapEdit.pixmap_width_in_cells]) == DRAWN) {
        gc = cw->bitmapEdit.undraw_gc;
        mode = UNDRAW;
    }
    else {
        gc = cw->bitmapEdit.draw_gc;
        mode = DRAW;
    }
    if (oldx != newx || oldy != newy) {
        oldx = newx;
        oldy = newy;
        DrawPixmaps(gc, mode, cw, event);
    }
}
static void
DrawPixmaps(gc, mode, w, event)
GC gc;
int mode;
Widget w;
XButtonEvent *event;
{
    BitmapEditWidget cw = (BitmapEditWidget) w;
    int newx = (cw->bitmapEdit.cur_x + event->x) /
            cw->bitmapEdit.cell_size_in_pixels;
    int newy = (cw->bitmapEdit.cur_y + event->y) /
            cw->bitmapEdit.cell_size_in_pixels;
    XExposeEvent fake_event;
    /* if already done, return */
    if (cw->bitmapEdit.cell[newx + newy *
            cw->bitmapEdit.pixmap_width_in_cells] == mode)
        return;
    /* otherwise, draw or undraw */
    XFillRectangle(XtDisplay(cw), cw->bitmapEdit.big_picture, gc,
            cw->bitmapEdit.cell_size_in_pixels*newx + 2,
            cw->bitmapEdit.cell_size_in_pixels*newy + 2,
            (unsigned int)cw->bitmapEdit.cell_size_in_pixels - 3,
            (unsigned int)cw->bitmapEdit.cell_size_in_pixels - 3);
    cw->bitmapEdit.cell[newx + newy *
            cw->bitmapEdit.pixmap_width_in_cells] = mode;
    info.mode = mode;
    info.newx = newx;
    info.newy = newy;
    fake_event.x = cw->bitmapEdit.cell_size_in_pixels *
            newx - cw->bitmapEdit.cur_x;
    fake_event.y = cw->bitmapEdit.cell_size_in_pixels *
            newy - cw->bitmapEdit.cur_y;
    fake_event.width = cw->bitmapEdit.cell_size_in_pixels;
    fake_event.height = cw->bitmapEdit.cell_size_in_pixels;
    Redisplay(cw, &fake_event);
    XtCallCallbacks(cw, XtNcallback, &info);
}


Notice that as in methods, the widget instance pointer passed in is declared as type Widget, and then, in the first line of the action, cast to the desired type. This is necessary for ANSI C conformance.

An action routine in widget code can set and read fields in the widget instance structure passed in, while an action added from the application can access its own application data structure but not the widget's internal data structure because of the encapsulation rule (see Section 2.1.6.4, "Encapsulation"). This is one of the advantages of moving this code into a widget.

You should now be ready to go back near the end of Chapter 6, Inside a Widget, and follow the directions to write your first widget. You will need to review parts of both Chapter 6 and this chapter as the process of actually writing a widget raises new questions in your mind.



[49] Note that Expose events are also handled through the translation mechanism like any other event. When they are present in the translation table, the action registered for Expose events will be called in addition to the expose method. Widgets do not normally include the Expose event in their translation table, because they already have the expose method. Applications might, conceivably, have Expose in their translation table to add drawing capability to a widget, although it is difficult to calculate where to do this drawing from the application since the widget's size may change.

[50] As described in Chapter 12, Geometry Management, the initialize method can also be used for creating child widgets, to build a compound widget. This is a way of getting around Xt's geometry management scheme, and is not frequently done.

[51] To work with the Athena widget set, BitmapEdit would have to define its own XtNforeground resource.

[52] Also note that the application may be resized when it is first mapped on the screen, when the user sizes the rubber-band outline of the application provided by most window managers. (You may not think of this as resizing, but it is.) When this happens, the application has already created its widgets and the widgets have created windows. Therefore, the resize method of a widget will be called if its parent widget is forced to resize it.

[53] This resize strategy does have one problem; it never reduces the cell size. This is not a serious problem because BitmapEdit has a resource that controls the cell size. The application that uses this widget could provide a user interface for setting the cell size if the application writer was concerned about this problem.