![]() X-Designer - The Leading X/Motif GUI Builder - Click to download a FREE evaluation |
In this chapter:
Chapter: 8
Manager Widgets
This chapter provides detailed descriptions of the various classes of Motif manager widgets. Examples explore the various methods of positioning children within the BulletinBoard, Form, and RowColumn widgets.As their name implies, manager widgets manage other widgets, which means that they control the size and location (geometry) and input focus policy for one or more widget children. The relationship between managers and the widgets that they manage is commonly referred to as the parent-child model. The manager acts as the parent and the other widgets are its children. Since manager widgets can also be children of other managers, this model produces the widget hierarchy, which is a framework for how widgets are laid out visually on the screen and how resources are specified in the resource database.
While managers are used and explained in different contexts throughout this book, this chapter discusses the details of the different manager widget classes. Chapter 3, Overview of the Motif Toolkit, discusses the general concepts behind manager widgets and how they fit into the broader application model. You are encouraged to review the material in this and other chapters for a wider range of examples, since it is impossible to deal with all of the possibilities here. For an in-depth discussion of the X Toolkit Composite and Constraint widget classes, from which managers are subclassed, see Volume 4, X Toolkit Intrinsics Programming Manual.
Types of Manager Widgets
The Manager widget class is a metaclass for a number of functional subclasses. The Manager widget class is never instantiated; the functionality it provides is inherited by each of its subclasses. In this chapter, we describe the general-purpose Motif manager widgets, which are introduced below:The BulletinBoard is the most basic of the manager widgets. The geometry management is, as the class name implies, like a bulletin board. A child is pinned up on the BulletinBoard in a particular location and remains there until it moves itself or someone else moves it. The BulletinBoard widget does not impose any layout policy on its children, but it does support keyboard traversal. The BulletinBoard is a superclass for more sophisticated and useful managers. The BulletinBoard is also designed to be used as the container for dialog boxes, so it has translation tables and callback routines for this purpose. The predefined Motif dialogs use the BulletinBoard widget class to handle all of their input mechanisms; each dialog widget class handles its own geometry management. See Chapter 5, Introduction to Dialogs, for a complete discussion of dialogs.
Form
The Form widget is subclassed from the BulletinBoard. The Form extends the capabilities of the BulletinBoard by introducing a sophisticated geometry management policy that involves both absolute and relative positioning and sizing of its children. For example, a Form may lay out its children in a grid-like manner, anchoring the edges of each child to specific positions on the grid, or it may attach the children to one another in a chain-like fashion.
RowColumn
The RowColumn widget lays out its children in rows and columns. Resources control the number of rows or columns and the packing of widgets into those rows and columns. The Motif toolkit uses the RowColumn internally to implement many objects that are not implemented as individual widgets, such as PopupMenus, PulldownMenus, MenuBars, RadioBoxes, and CheckBoxes. There are a number of RowColumn resources that are specific to these objects.
Frame
The purpose of the Frame widget is to provide a visible, three-dimensional border for objects such as RowColumns or Labels that do not provide a border for themselves. The Frame widget may have two children: a work area child and a label child. The Frame sizes itself just big enough to contain its children.
PanedWindow
The PanedWindow manages its children in a vertically or horizontally1 tiled format. In a vertical orientation, the widget takes its width from the widest widget in its list of children. When horizontally oriented, the PanedWindow takes its height from the height of the tallest child. The PanedWindow also provides control sashes or grips that enable the user to adjust the individual heights of the PanedWindow's children. Constraint resources for the PanedWindow allow each child to specify its desired maximum and minimum height and whether it may be resized.
DrawingArea
Although the DrawingArea widget is subclassed from the Manager widget class, it is not generally used in the way that conventional managers are used. The widget does not do any drawing itself, and it doesn't define any keyboard or mouse behavior, although it does provide callbacks for user input. It is basically a free-form widget that can be used for application-specific purposes. The widget provides callback resources to handle keyboard, mouse, exposure, and resize events. While the DrawingArea widget can have children, it does not manage them in any defined way. Since the DrawingArea widget is typically used for application drawing, rather than for managing other widgets, it is discussed separately in Chapter 11, The DrawingArea Widget.
ScrolledWindow
The ScrolledWindow widget provides a viewing area into another widget. The user can adjust the viewing area using ScrollBars that are attached to the ScrolledWindow. The ScrolledWindow can handle scrolling automatically, so that the application does not have to do any work. The widget also has an application-defined mode, which allows an application to control all of the aspects of scrolling. Since the operation of the ScrolledWindow is tied to the operation of ScrollBars, the two widgets are discussed together in Chapter 10, ScrolledWindows and ScrollBars.
MainWindow
The MainWindow widget is subclassed from the ScrolledWindow widget. The MainWindow is the standard layout manager for the main application window in a Motif application. The widget is designed to lay out a MenuBar, a work area, ScrollBars, a command area, and a message area. Since the MainWindow is central to many Motif applications, it is discussed separately in Chapter 4, The Main Window.
Scale
The Scale widget displays a slider object that has a specific value in a range of values. The user can adjust the value of the widget by moving the slider. The Scale creates and manages its own widgets. In Motif 1.2, the only sensible children that you could add to a Scale were Label widgets that represent tick marks, and these would have to be laid out by the programmer. However, in Motif 2.0, the function XmScaleSetTicks() was introduced which automatically places marks at calculated positions along the Scale edge. The widget class is therefore not meant to be a general-purpose manager, so it is described separately in Chapter 16, The Scale Widget.The MessageBox, SelectionBox, FileSelectionBox, and Command widgets are also Motif manager widgets. These widgets are used for predefined Motif dialogs and are discussed in Chapter 5, Introduction to Dialogs, Chapter 6, Selection Dialogs, and Chapter 7, Custom Dialogs.
Creating Manager Widgets
A manager widget may be created and destroyed like any other widget. The main difference between using a manager and other widgets involves when the widget is declared to be managed in the creation process. We normally suggest that you create manager widgets using the appropriate convenience function or XtVaCreateWidget(), rather than using XtVaCreateManagedWidget(), and then manage it later using XtManageChild(). To understand why this technique can be important, you need to understand how a manager widget manages its children.A manager widget manages its children by controlling the sizes and positions of the children. The process of widget layout only happens when the child and the parent are both in the managed state. If a child is created as an unmanaged widget, the parent skips over that widget when it is determining the layout until such time as the child is managed. However, if a manager widget is not itself managed, it does not perform geometry management on any of its children regardless of whether those children are managed.2
To demonstrate the problems that you are trying to avoid, consider creating a manager as a managed widget before any of its children are created. The manager is going to have a set of PushButtons as its children. When the first child is added using XtVaCreateManagedWidget(), the manager widget negotiates the size and position of the PushButton. Depending on the type of manager widget being used, the parent either changes its size to accommodate the new child or it changes the size of the child to its own size. In either case, these calculations are not necessary because the geometry needs to change as more buttons are added. The problem becomes complicated by the fact that when the manager's size changes, it must also negotiate its new size with its own parent, which causes that parent to negotiate with its parent all the way up to the highest-level shell. If the new size is accepted, the result goes back down the widget tree with each manager widget resizing itself on the way down. Repeating this process each time a child is added almost certainly affects performance.
Because of the different geometry management methods used by the different manager widgets, there is the possibility that all of this premature negotiation can result in a different layout than you intended. For example, as children are added to a RowColumn widget, the RowColumn checks to see if there is enough room to place the new child on the same row or column. If there isn't, then a new row or column is created. This behavior depends heavily on whether the RowColumn is managed and also on whether its size has been established by being realized. If the manager parent is not managed when the children are added, the whole process can be avoided, yet you still have the convenience of using XtVaCreateManagedWidget() for all of the widget children should you so wish. When the manager is itself managed, it queries its children for their size and position requests, calculates its own size requirements, and communicates that size back up the widget tree.
For best results, you should use the appropriate Motif convenience function, XtCreateWidget() or XtVaCreateWidget() to create manager widgets, reserving XtVaCreateManagedWidget() for primitive widgets. Creating a primitive widget as an unmanaged widget serves no purpose, unless you explicitly want the widget's parent to ignore it for some reason. If you are adding another manager as a child, the same principle applies; you should also create it as an unmanaged widget until all its children are added as well. The idea is to descend as deeply into the widget tree and create as many children as possible before managing the manager parents as you ascend back up. Once all the children have been added, XtManageChild() can be called for the managers so that they only have to negotiate with their parents once, thus saving time, improving performance, and probably producing better results.
Despite all we've just said, realize that the entire motivating factor behind this principle is to optimize the method by which managers negotiate sizes and positions of their children. If a manager only has one child, it does not matter if you create the manager widget as managed or not. Also, the geometry management constraints of some widgets are such that no negotiation is required between the parent and the children. In these situations, it is not necessary to create the manager as an unmanaged widget, even though it has children. We will explain these cases as they arise.
In the rest of this chapter, we examine the basic manager widget classes and present examples of how they can be used. While geometry management is the most obvious and widely used aspect of the widget class, managers are also responsible for keyboard traversal, gadget display, and gadget event handling. Many of the resources of the Manager metaclass are inherited by each of its subclasses for handling these tasks.
The BulletinBoard Widget
The BulletinBoard is the most basic of the manager widget subclasses. The BulletinBoard widget does not enforce position or size policies on its children, so it is rarely used by applications as a general geometry manager for widgets. The BulletinBoard is the superclass for the Form widget and all of the predefined Motif dialog widgets. To support these roles, the BulletinBoard has a number of resources that are used specifically for communicating with DialogShells.The BulletinBoard has callback resources for FocusIn, FocusOut, and MapNotify events. These callbacks are invoked when the user moves the mouse or uses the TAB key to traverse the widget hierarchy. The events do not require much visual feedback and they only require application-specific callback routines when an application needs to set internal states based on the events. The XmNfocusCallback and XmNmapCallback resources are used extensively by DialogShells.
Despite the low profile of the BulletinBoard as a manager widget, there is a lot to be learned from it, since the principles also apply to most other manager widgets. In this spirit, let's take a closer look at the BulletinBoard widget and examine the different things that can be done with it as a manager widget. If you want to use a BulletinBoard directly in an application, you must include the file <Xm/BulletinB.h>. The following code fragment shows the two recommended ways to create a BulletinBoard:
The parent parameter is the parent of the BulletinBoard, which may be another manager widget or a shell widget. You can specify any of the resources that are specific to the BulletinBoard, but unless you are using the widget as a dialog box, your choices are quite limited.Widget bboard = XtVaCreateWidget ("name", xmBulletinBoardWidgetClass, parent, resource-value-list, NULL); /* Create children */ ... XtManageChild (bboard); Widget bboard = XmCreateBulletinBoard (parent, "name", resource-value-array, resource-value-count); /* Create children */ ... XtManageChild (bboard);
Resources
Of the few BulletinBoard resources not tied to DialogShells, the only visual one is XmNshadowType. When used in conjunction with the XmNshadowThickness resource, you can control the three-dimensional appearance of the widget. There are four possible values for XmNshadowType:The default value for XmNshadowThickness is 0, except when the BulletinBoard is the child of a DialogShell, in which case the default value is 1. In either case, the value can be changed by the application or by the user.XmSHADOW_IN XmSHADOW_OUT XmSHADOW_ETCHED_IN XmSHADOW_ETCHED_OUTThe XmNbuttonRenderTable3 resource may be set to a render table as described in Chapter 24, Render Tables. This render table is used for each of the button children of the BulletinBoard, when the button does not specify its own render table. If the resource is not specified, its value is taken from the XmNbuttonRenderTable of the nearest ancestor which holds the XmQTspecifyRenderTable Trait. BulletinBoard, VendorShell, and MenuShell hold this Trait. Similarly, the XmNlabelRenderTable and XmNtextRenderTable resources can be set for Label and Text widgets, respectively, that are direct children of the BulletinBoard.
Geometry Management
Since the BulletinBoard does not provide any geometry management by default, you must be prepared to manage the positions and sizes of the widgets within a BulletinBoard. As a result, you must set the XmNx and XmNy resources for each child. You may also have to set the XmNwidth and XmNheight resources if you need consistent or predetermined sizes for the children. In order to maintain the layout, you must add an event handler for resize (ConfigureNotify) events, so that the new sizes and positions of the children can be calculated. Example 8-1 shows the use of an event handler with the BulletinBoard.4
The program uses four widgets, labelled Top Left, Top Right, Bottom Left, and Bottom Right. The positions of the buttons in the BulletinBoard correspond to their names. Since the widgets are not positioned when they are created, the geometry management only happens when the widget is resized. Figure 8-1 shows the application before and after a resize event./* corners.c -- demonstrate widget layout management for a ** BulletinBoard widget. There are four widgets each labelled ** top-left, top-right, bottom-left and bottom-right. Their ** positions in the bulletin board correspond to their names. ** Only when the widget is resized does the geometry management ** kick in and position the children in their correct locations. */ #include <Xm/BulletinB.h> #include <Xm/PushB.h> char *corners[] = { "Top Left", "Top Right", "Bottom Left", "Bottom Right" }; static void resize(Widget, XEvent *, String *, Cardinal *); main (int argc, char *argv[]) { Widget toplevel, bboard, button; XtAppContext app; XtActionsRec rec; int i; XtSetLanguageProc (NULL, NULL, NULL); /* Initialize toolkit and create toplevel shell */ toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); /* Create your standard BulletinBoard widget */ bboard = XmCreateBulletinBoard (toplevel, "bboard", NULL, 0); /* Set up a translation table that captures "Resize" events ** (also called ConfigureNotify or Configure events). If the ** event is generated, call the function resize(). */ rec.string = "resize"; rec.proc = resize; XtAppAddActions (app, &rec, 1); XtOverrideTranslations (bboard, XtParseTranslationTable ("<Configure>: resize()")); /* Create children of the dialog -- a PushButton in each corner. */ for (i = 0; i < XtNumber (corners); i++) { button = XmCreatePushButton (bboard, corners[i], NULL, 0); XtManageChild (button); } XtManageChild (bboard); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* resize(), the routine that is automatically called by Xt upon the ** delivery of a Configure event. This happens whenever the widget ** gets resized. */ static void resize ( Widget w, /* Widget that resized */ XEvent *event, String args[], /* unused */ Cardinal *num_args) /* unused */ { WidgetList children; Dimension w_width, w_height; short margin_w, margin_h; XConfigureEvent *cevent = (XConfigureEvent *) event; int width = cevent->width; int height = cevent->height; /* get handle to BulletinBoard's children and marginal spacing */ XtVaGetValues (w, XmNchildren, &children, XmNmarginWidth, &margin_w, XmNmarginHeight, &margin_h, NULL); /* place the top left widget */ XtVaSetValues (children[0], XmNx, margin_w, XmNy, margin_h, NULL); /* top right */ XtVaGetValues (children[1], XmNwidth, &w_width, NULL); XtVaSetValues (children[1], XmNx, width - margin_w - w_width, XmNy, margin_h, NULL); /* bottom left */ XtVaGetValues (children[2], XmNheight, &w_height, NULL); XtVaSetValues (children[2], XmNx, margin_w, XmNy, height - margin_h - w_height, NULL); /* bottom right */ XtVaGetValues (children[3], XmNheight, &w_height, XmNwidth, &w_width, NULL); XtVaSetValues (children[3], XmNx, width - margin_w - w_width, XmNy, height - margin_h - w_height, NULL); }
Figure 8-1 Output of the corners program before and after a resize event
When a resize event occurs, X generates a ConfigureNotify event. This event is interpreted by Xt and the translation table of the widget corresponding to the resized window is searched to see if the application is interested in being notified of the event. We have indicated interest in this event by calling XtAppAddActions() and XtOverrideTranslations(), as shown below:
As described in Volume 4, X Toolkit Intrinsics Programming Manual, a translation table pairs a sequence of one or more events with a sequence of one or more functions that are called when the event sequence occurs. In this case, the event is a ConfigureNotify event and the function is resize(). Translations are specified as strings and then parsed into an internal format with the function XtParseTranslationTable(). The routine creates an internal structure of events and the functions to which they correspond. Xt provides the table for translating event strings such as <Configure> to the actual ConfigureNotify event, but Xt cannot convert the string resize() to an actual function unless we provide a lookup table.The XtActionsRec type performs this task. The structure is defined as follows:XtActionsRec rec; ... rec.string = "resize"; rec.proc = resize; XtAppAddActions (app, &rec, 1); XtOverrideTranslations (bboard, XtParseTranslationTable ("<Configure>: resize()"));The action list is initialized to map the string resize to the actual function resize() using XtAppAddActions(). We install the translation table on the widget using XtOverrideTranslations() so that when a ConfigureNotify event occurs, the resize() function is called.typedef struct { String string; XtActionProc proc; } XtActionsRec;The resize() function takes four arguments. The first two arguments are a pointer to the widget in which the event occurred and the event structure. The args and num_args parameters are ignored because we did not specify any extra parameters to be passed to the function when we installed it. Since the function is called as a result of the event happening on the BulletinBoard widget, we know that we are dealing with a composite widget. We also know that there is only one event type that could have caused the function to be called, so we cast the event parameter accordingly.
The task of the function is to position the children so that there is one per corner in the BulletinBoard. We get a handle to all of the children of the BulletinBoard. Since we are going to place the children around the perimeter of the widget, we also need to know how far from the edge to place them. This distance is taken from the values for XmNmarginWidth and XmNmarginHeight. All three resource values are retrieved in the following call:
The remainder of the function simply places the children at the appropriate positions within the BulletinBoard. The routine uses a very simple method for geometry management, but it does demonstrate the process.XtVaGetValues (w, XmNchildren, &children, XmNmarginWidth, &margin_w, XmNmarginHeight, &margin_h, NULL);The general issue of geometry management for composite widgets is not trivial. If you plan on doing your own geometry management for a BulletinBoard or any other composite widget, you should be very careful to consider all the resources that could possibly affect layout. In our example, we considered the margin width and height, but there is also XmNallowOverlap, XmNborderWidth (which is a general Core widget resource), XmNshadowThickness (a general manager widget resource) and the same values associated with the children of the BulletinBoard.
There are also issues about what to do if a child decides to resize itself, such as if a Label widget gets wider. In this case, you must first evaluate what the geometry layout of the widgets would be if you were to grant the Label permission to resize itself as it wants. This evaluation is done by asking each of the children how big they want to be and calculating the hypothetical layout. The BulletinBoard either accepts or rejects the new layout. Of course, the BulletinBoard may have to make itself bigger too, which requires asking its parent for a new size, and so on. If the BulletinBoard cannot resize itself, then you have to decide whether to force other children to be certain sizes or to reject the resize request of the child that started all the negotiation. Geometry management is by no means a simple task; it is explained more completely in Volume 4, X Toolkit Intrinsics Programming Manual.
The Form Widget
The Form widget is subclassed from the BulletinBoard class, so it inherits all of the resources that the BulletinBoard has to offer. Accordingly, the children of a Form can be placed at specific x, y coordinates and geometry management can be performed as in Example 8-1. However, the Form provides additional geometry management features that allow its children to be positioned relative to one another and relative to specific locations in the Form.In order to use a Form, you must include the file <Xm/Form.h>. A Form is created in a similar way to other manager widgets, either through a convenience routine or using the general purpose Xt mechanisms, as shown below:
Widget form = XtVaCreateWidget ("name", xmFormWidgetClass, parent, resource-value-list, NULL); /* create children */ XtManageChild (form); Widget form = XmCreateForm ( parent, "name", resource-value-array, resource-value-count); /* create children */ XtManageChild (form);
Form Attachments
Geometry management in a Form is done using attachment resources. These resources are constraint resources, which means that they are specified for the children of the Form. The resources provide various ways of specifying the position of a child of a Form by attaching each of the four sides of the child to another entity. The side of a widget can be attached to another widget, to a fixed position in the Form, to a flexible position in the Form, to the Form itself, or to nothing at all. These attachments can be considered hooks, rods, and anchor points, as shown in Figure 8-2.
In this figure, there are three widgets. The sizes and types of the widgets are not important. What is important is the relationship between the widgets with respect to their positions in the Form. Widget 1 is attached to the top and left sides of the Form by creating two attachments. The top side of the widget is hooked to the top of the Form. It can slide from side to side, but it cannot be moved up or down (just like a shower curtain). The left side can slide up and down, but not to the right or to the left. Given these two attachment constraints, the top and left sides of the widget are fixed. The right and bottom edges of the widget are not attached to anything, but other widgets are attached to those edges.
The left side of Widget 2 is attached to the right side of Widget 1. Similarly, the top side of Widget 2 is attached to the top side of Widget 1. As a result, the top and left sides of the widget cannot be moved unless Widget 1 moves. The same kind of attachments hold for Widget 3. The top side of this widget is attached to the bottom of Widget 1 and its left side is attached to the left side of Widget 1. Given these constraints, no matter how large each of the widgets may be, or how the Form may be resized, the positional relationship of the widgets is maintained.
In general, you must attach at least two adjacent edges of a widget to keep it from moving unpredictably. If you only attach one edge of a widget, you are only specifying relative position: both opposing sides must be attached for potential resize behavior.
The following resources represent the four sides of a widget:
For example, if we want to specify that the top of a widget is attached to something, we use the XmNtopAttachment resource. Each of the four resources can be set to one of the following values:XmNtopAttachment XmNbottomAttachment XmNrightAttachment XmNleftAttachmentXmATTACH_FORM XmATTACH_OPPOSITE_FORM XmATTACH_WIDGET XmATTACH_OPPOSITE_WIDGET XmATTACH_NONE XmATTACH_SELF XmATTACH_POSITION
XmATTACH_FORM
When an attachment is set to XmATTACH_FORM, the specified side is attached to the Form as shown in Figure 8-3. If the resource that has this value is XmNtopAttachment, then the top side of the widget is attached to the top of the Form. The top attachment does not guarantee that the widget will not move from side to side. If XmNbottomAttachment is also set to XmATTACH_FORM, the bottom of the widget is attached to the bottom side of the Form. With both of these attachments, the widget is resized to the height of the Form itself. The same would be true for the right and left edges of the widget if they were attached to the Form.
XmATTACH_OPPOSITE_FORM
When an attachment is set to XmATTACH_OPPOSITE_FORM, the specified side of the widget is attached to the opposite side of the Form. For example, if XmNtopAttachment is set to XmATTACH_OPPOSITE_FORM, the top side of the widget is attached to the bottom side of the Form. This value must be used with a negative offset value (discussed in the next section) or the widget is placed off of the edge of the Form and it is not visible. While it may seem confusing, this value is the only one that can be applied to an attachment resource that allows you to specify a constant offset from the edge of a Form.XmATTACH_WIDGET
The XmATTACH_WIDGET value indicates that the side of a widget is attached to another widget. The other widget must be specified using the appropriate resource from the following list:The value for one of these resources must be the widget ID. For example, Figure 8-4 shows how to attach the right side of Widget 1 to the left side of Widget 2. This attachment method is commonly used to chain together a series of adjacent widgets. Chaining widgets horizontally does not guarantee that the widgets will be aligned vertically, or vice versa.XmNtopWidget XmNbottomWidget XmNleftWidget XmNrightWidget
XmATTACH_OPPOSITE_WIDGET
The XmATTACH_OPPOSITE_WIDGET value is just like XmATTACH_WIDGET, except that the widget is attached to the same edge of the specified widget, as shown in Figure 8-5. In this case, the right side of Widget 1 is attached to the right side of Widget 3. This attachment method allows you to align the edges of a group of widgets. As with XmATTACH_WIDGET, the other widget must be specified using XmNtopWidget, XmNbottomWidget, XmNleftWidget, or XmNrightWidget.
Figure 8-5 XmNrightAttachment set to XmATTACH_OPPOSITE_WIDGET
XmATTACH_NONE
XmATTACH_NONE specifies that the side of a widget is not attached to anything, which is the default value5. This case could be represented by a dangling hook that is not attached to anything. If the entire widget moves because another side is attached to something, then this side gets dragged along with it so that the widget does not need resizing. Unless a particular side of a widget is attached to something, that side of the widget is free-floating and moves proportionally with the other parts of the widget.XmATTACH_POSITION
When the side of a widget is attached using XmATTACH_POSITION, the side is anchored to a relative position in the Form. This value works by segmenting the Form into a fixed number of equally-spaced horizontal and vertical positions, based on the value of the XmNfractionBase resource. The position of the side must be specified using the appropriate resource from the following list:Later in this chapter there is a complete discussion of position attachments.XmNtopPosition XmNbottomPosition XmNleftPosition XmNrightPositionXmATTACH_SELF
When an attachment is set to XmATTACH_SELF, the side of the widget is attached to its initial position in the Form. You position the widget initially by specifying its x,y location in the Form. After the widget has been placed in the Form, the attachment for the side reverts to XmATTACH_POSITION, with the corresponding position resource set to the relative position of the x,y coordinate in the Form.Some Examples
Now that we have explained the concept of Form attachments, we can reimplement the four corners example from the previous section. Unlike in the previous version, we no longer need a resize procedure to calculate the positions of the widgets. By specifying the correct attachments, as shown in Example 8-2, the widgets are placed and managed correctly by the Form when it is resized.6
In this example, two sides of each widget are attached to the Form. It is not necessary to attach the other sides of the widgets to anything else. If we attach the other sides to each other, the widgets would have to be resized so that they could stretch to meet each other. With the specified attachments, the output of the program looks just like the output in Figure 8-1./* form_corners.c -- demonstrate form layout management. Just as ** in corners.c, there are four widgets each labelled top-left, ** top-right, bottom-left and bottom-right. Their positions in the ** form correspond to their names. As opposed to the BulletinBoard ** widget, the Form manages this layout management automatically by ** specifying attachment types for each of the widgets. */ #include <Xm/PushB.h> #include <Xm/Form.h> char *corners[] = {"Top Left", "Top Right", "Bottom Left", "Bottom Right"}; main (int argc, char *argv[]) { Widget toplevel, form, button; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL,sessionShellWidgetClass, NULL); form = XmCreateForm (toplevel, "form", NULL, 0); /* Attach the edges of the widgets to the Form. Which edge of ** the widget that's attached is relative to where the widget is ** positioned in the Form. Edges not attached default to having ** an attachment type of XmATTACH_NONE. */ button = XmCreatePushButton (form, corners[0], NULL, 0); XtVaSetValues (button, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, NULL); XtManageChild (button); button = XmCreatePushButton (form, corners[1], NULL, 0); XtVaSetValues (button, XmNtopAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, NULL); XtManageChild (button); button = XmCreatePushButton (form, corners[2], NULL, 0); XtVaSetValues (button, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, NULL); XtManageChild (button); button = XmCreatePushButton (form, corners[3], NULL, 0); XtVaSetValues (button, XmNbottomAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, NULL); XtManageChild (button); XtManageChild (form); XtRealizeWidget (toplevel); XtAppMainLoop (app); }A more complex example of Form attachments is shown in Example 8-3. This example implements the layout shown in Figure 8-27
The example uses three PushButton gadgets inside of a Form widget. The output of the program is shown in Figure 8-6./* attach.c -- demonstrate how attachments work in Form widgets. */ #include <Xm/PushB.h> #include <Xm/Form.h> main (int argc, char *argv[]) { Widget toplevel, parent, one, two, three; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); parent = XmCreateForm (toplevel, "form", NULL, 0); one = XmCreatePushButton (parent, "One", NULL, 0); two = XmCreatePushButton (parent, "Two", NULL, 0); three = XmCreatePushButton (parent, "Three", NULL, 0); XtVaSetValues (one, XmNtopAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, NULL); XtVaSetValues (two, XmNleftAttachment, XmATTACH_WIDGET, /* attach top to same y coordinate as top of "one" */ XmNleftWidget, one, XmNtopAttachment, XmATTACH_OPPOSITE_WIDGET, XmNtopWidget, one, NULL); XtVaSetValues (three, XmNtopAttachment, XmATTACH_WIDGET, /* attach left to same x coordinate as left of "one" */ XmNtopWidget, one, XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, XmNleftWidget, one, NULL); XtManageChild (one); XtManageChild (two); XtManageChild (three); XtManageChild (parent); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
You should notice that the widgets are packed together quite tightly, which might not be how you expected them to appear. In order to space the widgets more reasonably, we need to specify some distance between them using attachment offsets.
Attachment Offsets
Attachment offsets control the spacing between widgets and the objects to which they are attached. The following resources represent the attachment offsets for the four sides of a widget:Figure 8-7 shows the graphic representation of attachment offsets.XmNleftOffset XmNrightOffset XmNtopOffset XmNbottomOffset
By default, offsets are set to 0 (zero), which means that there is no offset8, as shown in the output forExample 8-3. To make the output more reasonable, we need only to set the left offset between widgets One and Two and the top offset to between widgets One and Three. The resources values can be hard-coded in the application or set in a resource file, using the following specification:
Our choice of the value 10 was arbitrary. The widgets are now spaced more appropriately, as shown in Figure 8-8.*form.One.leftOffset: 10 *form.One.topOffset: 10 *form.Two.leftOffset: 10 *form.Three.topOffset: 10
Figure 8-8 Output of the attach program with offset resources set to 10
While the layout of the widgets can be improved by setting offset resources, it is also possible to disrupt the layout. Consider the following resource specifications:
While it might seem that these resource values are simply a terser way to specify the offsets shown earlier, Figure 8-9 makes it clear that these specifications do not produce the desired effect.*form*leftOffset: 10 *form*topOffset: 10
Figure 8-9 Output of the attach program with inappropriate offset resources
An application should hard-code whatever resources may be necessary to prevent the user from setting values that would make the application non-functional or aesthetically unappealing. Offset resource values can be tricky because they apply individually to each side of each widget in a Form. The problem with the resource specifications used to produce Figure 8-9 is that the offsets are being applied to each side of every widget, when some of the alignments need to be precise. In order to prevent this problem, we need to hard-code the offsets for particular attachments, as shown in the following code fragment:
The use of zero-length offsets guarantees that the widgets they are associated with are aligned exactly with the widgets to which they are attached, regardless of any resource specifications made by the user. A general rule of thumb is that whenever you use XmATTACH_OPPOSITE_WIDGET, you should also set the appropriate offset to zero so that the alignment remains consistent.two = XmCreatePushButton (parent, "Two", NULL, 0); XtVaSetValues (two, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, one, XmNtopAttachment,XmATTACH_OPPOSITE_WIDGET, XmNtopWidget, one, XmNtopOffset, 0, NULL); three = XmCreatePushButton (parent, "Three", NULL, 0); XtVaSetValues (three, XmNtopAttachment, XmATTACH_WIDGET, XmNtopWidget, one, XmNleftAttachment, XmATTACH_OPPOSITE_WIDGET, XmNleftWidget, one, XmNleftOffset, 0, NULL);In some situations it is necessary to use negative offsets to properly arrange widgets in a Form. The most common example of this situation occurs when using the XmATTACH_OPPOSITE_FORM attachment. Unless you use a negative offset, as shown in Figure 8-10, the widgets are placed off the edge of the Form and are not visible.
Figure 8-10 XmNleftAttachment of XmATTACH_OPPOSITE_WIDGET with negative offset
Position Attachments
Form positions provide another way to position widgets within a Form. The concept is similar to the hook and rod principle described earlier, but in this case the widgets are anchored on at positions that are based on imaginary longitude and latitude lines that are used to segment the Form into equal pieces. The resource used to partition the Form into segments is XmNfractionBase. Although the name of this resource may suggest complicated calculations, you just need to know that the Form is divided horizontally and vertically into the number of partitions represented by its value. For example, Figure 8-11 shows how a Form is partitioned if XmNfractionBase is set to 5.
As you can see, there are an equal number of horizontal and vertical partitions, but the size of the horizontal partitions is not the same as the size of the vertical partitions. It is currently not possible to set the number of horizontal partitions separately from the number of vertical ones, although it is possible to work around this shortcoming, as we will describe shortly.
Widgets are placed at the coordinates that represent the partitions by specifying XmATTACH_POSITION for the attachment resource and by specifying a coordinate value for the corresponding position resource. The position resources are XmNtopPosition, XmNbottomPosition, XmNleftPosition, and XmNrightPosition. For example, if we wanted to attach the top and left sides of a PushButton to position 1, we could use the following code fragment:
The right and bottom attachments are left unspecified, so those edges of the widget are not explicitly positioned by the Form. If attachments had been specified for these edges, the widget would have to be resized by the Form in order to satisfy all the attachment constraints.button = XmCreatePushButton (form, "name", NULL, 0); XtVaSetValues (button, XmNtopAttachment, XmATTACH_POSITION, XmNtopPosition, 1, XmNleftAttachment, XmATTACH_POSITION, XmNleftPosition, 1, NULL);One obvious example of using position attachments is to create a tic-tac-toe board layout, as is done in Example 8-4.9
The output of this program is shown in Figure 8-12/* tictactoe.c -- demonstrate how fractionBase and XmATTACH_POSITIONs ** work in Form widgets. */ #include <Xm/PushB.h> #include <Xm/Form.h> main (int argc, char *argv[]) { XtAppContext app; Widget toplevel, parent, w; int x, y, n; Arg args[10]; /* callback for each PushButton */ extern void pushed(Widget, XtPointer, XtPointer); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); n = 0; XtSetArg (args[n], XmNfractionBase, 3); n++; parent = XmCreateForm (toplevel, "form", args, n); for (x = 0; x < 3; x++) for (y = 0; y < 3; y++) { n = 0; XtSetArg (args[n], XmNtopAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNtopPosition, y); n++; XtSetArg (args[n], XmNleftAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNleftPosition, x); n++; XtSetArg (args[n], XmNrightAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNrightPosition, x+1); n++; XtSetArg (args[n], XmNbottomAttachment, XmATTACH_POSITION); n++; XtSetArg (args[n], XmNbottomPosition, y+1); n++; w = XmCreatePushButton (parent, " ", args, n); XtAddCallback (w, XmNactivateCallback, pushed, NULL); XtManageChild (w); } tManageChild (parent); XtRealizeWidget (toplevel); XtAppMainLoop (app); } void pushed (Widget w, /* PushButton that got activated */ XtPointer client_data, /* unused */ XtPointer call_data) { char buf[2]; XmString str; XmPushButtonCallbackStruct *cbs = (XmPushButtonCallbackStruct *) call_data; /* Shift key gets an O. (xbutton and xkey happen to be similar) */ if (cbs->event->xbutton.state & ShiftMask) buf[0] = '0'; else buf[0] = 'X'; buf[1] = 0; str = XmStringCreateLocalized (buf); XtVaSetValues (w, XmNlabelString, str, NULL); XmStringFree (str); }.
As you can see, the children of the Form are equally sized because their attachment positions are segmented equally. If the user resizes the Form, all of the children maintain their relationship to one another. The PushButtons simply grow or shrink to fill the form.
One common use of positional attachments is to lay out a number of widgets that need to be of equal size and equal spacing. For example, you might use this technique to arrange the buttons in the action area of a dialog. Chapter 7, Custom Dialogs, provides a detailed discussion of how to arrange buttons in this manner.
There may be situations where you would like to attach widgets to horizontal positions that do not match up with how you'd like to attach their vertical positions. Since the fraction base cannot be set differently for the horizontal and vertical orientations, you have to use the least common multiple as the fraction base value. For example, say you want to position the tops and bottoms of all of your widgets to the 2nd and 4th positions, as if the Form were segmented vertically into 5 parts. But, you also want to position the left and right edges of those same widgets to the 3rd, 5th, 7th, and 9th positions, as if it were segmented into 11 parts. You would have to apply some simple arithmetic and set the value for XmNfractionBase to 55 (5x11). The top and bottom edges would be set to the 22nd (2x11) and 44th (4x11) positions and the left and right edges would be set to the 15th (3x5), 25th (5x5), 35th (7x5), and 45th (9x5) positions.
Additional Resources
There are a few other useful Form resources that we have not covered so far. The XmNhorizontalSpacing resource can be used to specify the distance between horizontally adjacent widgets, while XmNverticalSpacing specifies the distance between vertically adjacent widgets. These values only apply when the left and right offset values are not specified, so they are intended to be used as global offset values global for a Form. The following resource specification:is equivalent to:*horizontalSpacing: 10The XmNrubberPositioning resource specifies the default attachments for widgets in the Form. The default value of False indicates that the top and left edges are attached to the form by default. If XmNrubberPositioning is set to True, the top and left attachments are set to XmATTACH_POSITION by default. If the XmNtopAttachment or XmNleftAttachment resource is explicitly set for a widget, then the default attachment has no effect.*leftOffset: 10 *rightOffset: 10The XmNresizable resource is another constraint resource that can be set on the children of a Form widget. This resource indicates whether or not the Form tries to grant resize requests from the child.
Nested Forms
Some widget layouts are difficult to create using a single Form widget. Since a manager widget can contain other managers, it is often possible to generate the desired layout by using a Form within a Form. One common problem is that there are no Form attachments available to align two widgets horizontally if they have different heights. We need a middle attachment resource, but one doesn't exist. For example, if you have a series of Labels and Text widgets that you want to pair off and stack vertically, it would be nice to align each pair of widgets at their mid sections.To solve this problem, we can place each Label-Text widget pair in a separate Form. If the top and bottom edges of the widgets are attached to the Form, the widgets are stretched to satisfy the constraints, which means that they are aligned horizontally. All of these smaller Form widgets can be placed inside of a larger Form widget. Example 8-5 shows an implementation of this idea.10
The output of the program is shown in Example 8-5./* text_form.c -- demonstrate how attachments work in Form widgets * by creating a text-entry form type application. */ #include <Xm/LabelG.h> #include <Xm/Text.h> #include <Xm/Form.h> char *prompts[] = {"Name:", "Phone:", "Address:", "City:", "State:", "Zip Code:"}; main (int argc, char *argv[]) { Widget toplevel, mainform, subform, last_subform, label, text; XtAppContext app; char buf[32]; int i; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); mainform = XmCreateForm (toplevel, "mainform", NULL, 0); last_subform = NULL; for (i = 0; i < XtNumber (prompts); i++) { subform = XmCreateForm (mainform, "subform", NULL, 0); XtVaSetValues (subform, /* first one should be attached for form */ XmNtopAttachment, last_subform ? XmATTACH_WIDGET : XmATTACH_FORM, /* others are attached to the previous subform */ XmNtopWidget, last_subform, XmNleftAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, NULL); /* Note that the label here contains a colon from the prompts ** array above. This makes it impossible for external resources ** to be set on these widgets. Here, that is intentional, but ** be careful in the general case. */ label = XmCreateLabelGadget (subform, prompts[i], NULL, 0); XtVaSetValues (label, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_FORM, XmNalignment, XmALIGNMENT_BEGINNING, NULL); XtManageChild (label); sprintf (buf, "text_%d", i); text = XmCreateText (subform, buf, NULL, 0); XtVaSetValues (text, XmNtopAttachment, XmATTACH_FORM, XmNbottomAttachment, XmATTACH_FORM, XmNrightAttachment, XmATTACH_FORM, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, label, NULL); XtManageChild (text); XtManageChild (subform); last_subform = subform; } /* Now that all the forms are added, manage the main form */ XtManageChild (mainform); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
Notice that the Labels are centered vertically with respect to their corresponding Text widgets. This arrangement happened because each Label was stretched vertically in order to attach it to the top and bottom of the respective Form. Of course, if the Labels were higher than the Text widgets, the Text widgets would be stretched instead.
Later, we'll show another version of this program that gives better results. As you can imagine, there are many different ways for a Form, or any other manager widget, to manage the geometry of its children to produce the same layout. Later, when we discuss the RowColumn widget, we will show you another solution to the problem of horizontal alignment. It is important to remember that there is no right or wrong way to create a layout, as long as it works for your application. However, you should be very careful to experiment with resizing issues as well as with resources that can beset by the user that might affect widget layout, such as fonts and strings.
Common Problems
With a Form widget, you can specify a virtually unlimited number of attachments for its children. The dependencies inherent in these attachments can lead to various errors in the layout of the widgets. One common problem involves circular dependencies. The following code fragment shows a very simple example of a circular dependency:In this example, the left widget is attached to the right widget and the right widget is attached to the left one. If you do mistakenly specify a circular dependency, it is unlikely that it will be as obvious as this example. Fortunately, in most cases, the Motif toolkit catches circular dependencies and displays an error message if one is found. When this situation occurs, you need to reconsider your widget layout and try to arrange things such that the relationship between widgets is less complex. One rule to remember is that adjacent widgets should generally only be attached in one direction.w1 = XmCreateLabel (form, "w1", NULL, 0); w2 = XmCreateLabel (form, "w2", NULL, 0); XtVaSetValues (w1, XmNrightAttachment, XmATTACH_WIDGET, XmNrightWidget, w2, NULL); XtVaSetValues (w2, XmNleftAttachment, XmATTACH_WIDGET, XmNleftWidget, w1, NULL);When you attach the side of a widget to another widget in a Form, you need to be careful about how you specify the attached widget. If you specify this widget in the application code, you need to make sure that the widget has been created before you specify it as a resource value. Alternatively, the toolkit provides a name-to-widget converter, so you can also specify widget IDs in a resource file. (See Volume 4, X Toolkit Intrinsics Programming Manual for information about resource converters.)
Another common problem arises with certain Motif compound objects, such as ScrolledList and ScrolledTextobjects. XmCreateScrolledText() and XmCreateScrolledList() return the corresponding Text or List widget, but it is the parent of this widget that needs to be positioned within a Form. The following code fragment shows an example of positioning a ScrolledList incorrectly:
Since the List is a child of the ScrolledWindow, not the Form, specifying attachments for the List has no effect on the position of the List in the Form. The attachments need to be specified on the ScrolledWindow, as shown in the following code fragment:form = XmCreateForm (parent, "form", NULL, 0); list = XmCreateScrolledList (form, "scrolled_list", NULL, 0); XtVaSetValues ( list, /* <- WRONG */ XmNleftAttachment, XmATTACH_FORM, XmNtopAttachment, XmATTACH_FORM, NULL);If you specify attachments for two opposing sides of a widget, the Form resizes the widget as needed, so that the default size of the widget is ignored. In most cases, the Form can resize the widget without a problem. However, one particular case that can cause a problem is a List widget that has its XmNvisibleItemCount resource set. This resource implies a specific size requirement, so that when the List is laid out in the Form widget, the negotiation process between the Form and the List may not be resolved. See Chapter 13, The List Widget, for a complete discussion of the List widget.XtVaSetValues ( XtParent (list), XmNleftAttachment, XmATTACH_FORM, XmNtopAttachment, XmATTACH_FORM, NULL);Attachments in Form widgets can be delicate specifications, which means that you must be specific and, above all, complete in your descriptions of how widgets should be aligned and positioned. Since resources can be set from many different places, the only way to guarantee that you get the layout you want is to hard-code these resource values explicitly. Even though it is important to allow the user to specify as many resources as possible, you do not want to compromise the integrity of your application. Attachments and attachment offsets are probably not in the set of resources that should be user-definable.
Although attachments can be delicate, they also provide a powerful, convenient, and flexible way to lay out widgets within a Form, especially when the widgets are grouped together in some abstract way. Attachments make it easy to chain widgets together, to bind them to the edge of a Form, and to allow them to be fixed on specific locations. You do not need to use a single attachment type exclusively; it is perfectly reasonable, and in most cases necessary, to use a variety of different types of attachments to achieve a particular layout. If you specify too few attachments, you may end up with misplaced widgets or widgets that drift when the Form is resized, while too many attachments may cause the Form to be too inflexible. In order to determine the best way to attach widgets to one another, you may find it helpful to a draw picture first, with all of the hooks and offset values considered.
The RowColumn Widget
The RowColumn widget is a manager widget that, as its name implies, lays out its children in a row and/or column format. The widget is also used internally by the Motif toolkit to implement a number of special objects, such as the Motif menus, including PopupMenus, PulldownMenus, MenuBars, and OptionMenus. Many of the resources for the RowColumn widget are used to control different aspects of these objects. The Motif convenience functions for creating these objects set most of these resources automatically, so they are generally hidden from the programmer. The resources are not useful when you are using the RowColumn as a simple manager widget anyway, so we do not discuss them here. The header file <Xm/RowColumn.h> is required if you are using this widget.The XmNrowColumnType resource controls how a particular instance of the RowColumn is used. The resource can be set to the following values:
The default value is XmWORK_AREA; this value is also the one that you should use whenever you want to use a RowColumn widget as a manager. The rest of the values are for the different types of Motif menus. If you want to create a particular menu object, you should use the appropriate convenience function, rather than try to create the menu yourself using a RowColumn directly. We discuss menu creation in Chapter 4, The Main Window, and Chapter 20, Interacting With the Window Manager. The RowColumn widget is also used to implement RadioBoxes and CheckBoxes, which are collections of ToggleButtons. See Chapter 12, Labels and Buttons, for more information on these objects.XmWORK_AREA XmMENU_BAR XmPULLDOWN XmMENU_POPUP XmMENU_OPTIONThe RowColumn is useful for generic geometry management because it requires less fine tuning than is necessary for a Form or a BulletinBoard widget. Although the RowColumn has a number of resources, you can create a usable layout without specifying any resources. In this case, the children of the RowColumn are automatically laid out vertically. In Example 8-6, we create several PushButtons as children of a RowColumn, without specifying any RowColumn resources.11
What makes the RowColumn widget unique is that it automates much of the process of widget layout and management. If you display the application and resize it in a number of ways, you can get a better feel for how the RowColumn works. Figure 8-14 shows a few configurations of the application; the first configuration is the initial layout of the application. As you can see, if the application is resized just so, the widgets are oriented horizontally rather than vertically./* rowcol.c -- demonstrate a simple RowColumn widget. Create one ** with 3 pushbutton gadgets. Once created, resize the thing in ** all sorts of contortions to get a feel for what RowColumns can ** do with its children. */ #include <Xm/PushB.h> #include <Xm/RowColumn.h> main (int argc, char *argv[]) { Widget toplevel, rowcol, button; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); rowcol = XmCreateRowColumn (toplevel, "rowcolumn", NULL, 0); button = XmCreatePushButton (rowcol, "One", NULL, 0); XtManageChild (button); button = XmCreatePushButton (rowcol, "Two", NULL, 0); XtManageChild (button); button = XmCreatePushButton (rowcol, "Three", NULL, 0); XtManageChild (button); XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
The orientation of the widgets in a RowColumn is controlled by the XmNorientation resource. The default value of the resource is XmVERTICAL. If we want to arrange the widgets horizontally, we can set the resource to XmHORIZONTAL. The orientation can be hard-coded in the application, or we can specify the value of the resource in a resource file. The following resource specification sets the orientation to horizontal:
Alternatively, we can specify the resource on the command line as follows:*RowColumn.orientation: horizontalFigure 8-15 shows the output of Example 8-6 with a horizontal orientation. As before, the figure shows a few different configurations of the application, with the first configuration being the initial one.% rowcol -xrm "*orientation: horizontal"
Figure 8-15 Output of the rowcol program with a horizontal orientation
If you use a RowColumn widget to manage more objects than can be arranged in a single row or column, you can specify that the widgets should be arranged in both rows and columns. You can also specify whether the widgets should be packed together tightly, so that the rows and columns are not necessarily the same size, or whether the objects should be placed in identically-sized boxes. As with the Form and BulletinBoard widgets, objects can also be placed at specific x,y locations in a RowColumn widget. The RowColumn widget does not provide a three-dimensional border, so if you want to provide a visual border for the widget, you should create it as a child of a Frame widget.
Rows and Columns
The RowColumn widget can be quite flexible in terms of how it lays out its children. The advantage of this flexibility is that all of its child widgets are arranged in an organized fashion, regardless of their widget types. The widgets remain organized when the RowColumn is resized and in spite of constraints imposed by other widgets or by resources. One disadvantage of the flexibility is that sometimes the children need to be arranged in a specific layout so that the user interface is intuitive.Example 8-7 shows how to lay out widgets in a spreadsheet-style format using a RowColumn. This layout requires that each of the widgets be the same size and be spaced equally in a predetermined number of rows and columns.12
The output of this example is shown in Figure 8-16./* spreadsheet.c -- This demo shows the most basic use of the RowColumn ** It displays a table of widgets in a row-column format similar to a ** spreadsheet. This is accomplished by setting the number ROWS and ** COLS and setting the appropriate resources correctly. */ #include <Xm/LabelG.h> #include <Xm/PushB.h> #include <Xm/RowColumn.h> #define ROWS 8 #define COLS 10 main (int argc, char *argv[]) { Widget toplevel, parent, child; XtAppContext app; char buf[16]; int i, j; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); parent = XmCreateRowColumn (toplevel, "rowcolumn", NULL, 0); XtVaSetValues (parent, XmNpacking, XmPACK_COLUMN, XmNnumColumns, COLS, XmNorientation, XmVERTICAL, NULL); /* simply loop through the strings creating a widget for each one */ for (i = 0; i < COLS; i++) for (j = 0; j < ROWS; j++) { sprintf (buf, "%d-%d", i+1, j+1); if (i == 0 || j == 0) child = XmCreateLabelGadget (parent, buf, NULL, 0); else child = XmCreatePushButton (parent, "", NULL, 0); XtManageChild (child); } XtManageChild (parent); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
The number of rows is specified by the ROWS definition and the number of columns is specified by COLS. In order to force the RowColumn to lay out its children in the spreadsheet format, we set the XmNpacking, XmNnumColumns, and XmNorientation resources.
The value for XmNpacking is set to XmPACK_COLUMN, which specifies that each of the cells should be the same size. The heights and widths of the widgets are evaluated and the largest height and width are used to determine the size of the rows and columns. All of the widgets are resized to this size. If you are mixing different widget types in a RowColumn, you may not want to use XmPACK_COLUMN because of size variations. XmPACK_COLUMN is typically used when the widgets are exactly the same, or at least similar in nature. The default value of XmPACK_TIGHT for XmNpacking allows each widget to keep its specified size and packs the widgets into rows and columns based on the size of the RowColumn widget.
Since we are packing the widgets in a row/column format, we need to specify how many columns (or rows) we are using by setting the value of XmNnumColumns to the number of columns. In this case, the program defines COLS to be 10, which indicates that the RowColumn should pack its children such that there are 10 columns. The widget creates as many rows as necessary to provide enough space for all of the child widgets.
Whether XmNnumColumns specifies the number of columns or the number of rows depends on the orientation of the RowColumn. In this program, XmNorientation is set to XmVERTICAL to indicate that the value of XmNnumColumns specifies the number of columns to use. If XmNorientation is set to XmHORIZONTAL, XmNnumColumns indicates the number of rows. If we wanted to use a horizontal orientation in our example, we would set XmNnumColumns to ROWS and XmNorientation to XmHORIZONTAL. The orientation also dictates how children are added to the RowColumn; when the orientation is vertical, children are added vertically so that each column is filled up before the next one is started.13
In our example, we explicitly set the value of XmNorientation to the default value of XmVERTICAL. If we do not hard-code this resource, an external resource specification can reset it. Since the orientation and the value for XmNnumColumns need to be consistent, you should always specify these resources together. Whether you choose to hard-code the resources, to use the fallback mechanism, or to use a specification in a resource file, you should be sure that both of the resources are specified in the same place.
In the spreadsheet example, we can use either a horizontal or vertical orientation. However, orientation may be significant in other situations, since it affects how the RowColumn adds its children. For example, if we want to implement the text-entry form from Example 8-5 using a RowColumn, the order of the widgets is important. In this case, there are two columns and the number of rows depends on the number of text entry fields provided by the application. We specify the orientation of the RowColumn as XmHORIZONTAL and set XmNnumColumns to the number of entries provided by the application, as shown in Example 8-8.14
The output of this example is shown in Figure 8-17./* text_entry.c -- This demo shows how the RowColumn widget can be ** configured to build a text entry form. It displays a table of ** right-justified Labels and Text widgets that extend to the right ** edge of the Form. */ #include <Xm/LabelG.h> #include <Xm/RowColumn.h> #include <Xm/Text.h> char *text_labels[] = {"Name:", "Phone:", "Address:", "City:", "State:", "Zip Code:"}; main (int argc, char *argv[]) { Widget toplevel, rowcol, child; XtAppContext app; char buf[8]; int i; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL,sessionShellWidgetClass, NULL); rowcol = XmCreateRowColumn (toplevel, "rowcolumn", NULL, 0); XtVaSetValues (rowcol, XmNpacking, XmPACK_COLUMN, XmNnumColumns, XtNumber (text_labels), XmNorientation, XmHORIZONTAL, XmNisAligned, True, XmNentryAlignment, XmALIGNMENT_END, NULL); /* simply loop through the strings creating a widget for each one */ for (i = 0; i < XtNumber (text_labels); i++) { child = XmCreateLabelGadget (rowcol, text_labels[i],NULL, 0); XtManageChild (child); sprintf (buf, "text_%d", i); child = XmCreateText (rowcol, buf, NULL, 0); XtManageChild (child); } XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
The labels for the text fields are initialized by the text_labels string array. When the RowColumn is created, it is set to a horizontal orientation and the number of rows is set to the number of items in text_labels. As you can see, the output of this program is slightly different from the output for the text_form example.
The example uses the XmNisAligned and XmNentryAlignment resources to control the positioning of the Labels in the RowColumn.These resources control the alignment of widgets that are subclasses of Label and LabelGadget. When XmNisAligned is True (the default), the alignment is taken from the XmNentryAlignment resource. The possible alignment values are the same as those that can be set for the Label's XmNalignment resource:
By default, the text is left justified. While the alignment of the Labels could also be specified using the XmNalignment resource for each widget, it is convenient to be able to set the alignment for the RowColumn and have it propagate automatically to its children. In our example, we use XmALIGNMENT_END to right justify the Labels so that they appear to be attached to the Text widgets.XmALIGNMENT_BEGINNING XmALIGNMENT_CENTER XmALIGNMENT_ENDThere is an additional resource for controlling the alignment of various children. The XmNentryVerticalAlignment resource controls the vertical positioning of children that are subclasses of Label, LabelGadget, and Text. The possible values for this resource are:
In the example, we do not specify this resource because the default value, XmALIGNMENT_CENTER, produces the layout that we want.XmALIGNMENT_BASELINE_BOTTOM XmALIGNMENT_BASELINE_TOP XmALIGNMENT_CENTER XmALIGNMENT_CONTENTS_BOTTOM XmALIGNMENT_CONTENTS_TOP
Homogeneous Children
The RowColumn can be set up so that it only manages one particular type of widget or gadget. In many cases, this feature facilitates layout and callback management. For example, a MenuBar consists entirely of CascadeButtons that all act the same way and a RadioBox contains only ToggleButtons. The XmNisHomogeneous resource indicates whether or not the RowColumn should only allow one type of widget child. The widget class that is allowed to be managed is specified by the XmNentryClass resource. XmNisHomogeneous can be set at creation-time only. Once a RowColumn is created, you cannot reset this resource, although you can always get its value. These resources are useful for ensuring consistency; if you attempt to add a widget as a child of a RowColumn that does not permit that widget class, an error message is printed and the widget is not accepted.The Motif toolkit uses these mechanisms to ensure consistency in certain compound objects, to prevent you from doing something like adding a List widget to a MenuBar, for example. In this case, the XmNentryClass is set to xmCascadeButtonWidgetClass. As another example, when XmNradioBehavior is set, the RowColumn only allows ToggleButton widgets and gadgets to be added. The XmCreateRadioBox() convenience function creates a RowColumn widget with the appropriate resources set automatically. (See Chapter 12, Labels and Buttons.)
You probably do not need to use XmNisHomogeneous unless you are providing a mechanism that is exported to other programmers. If you are writing an interactive user-interface builder or a program that creates widgets by scanning text files, you may want to ensure that new widgets are of a particular type before they are added to a RowColumn widget. In such cases, you may want to use XmNisHomogeneous and XmNentryClass. Unless there is some way for a user to dynamically create widgets while an application is running, these resources are not particularly useful.
Callbacks
The RowColumn does not provide any specific callback routines that react to user input. While there are no callbacks for FocusIn and FocusOut events, the widget does have XmNmapCallback and XmNunmapCallback callback resources. These callbacks are invoked when the window for the RowColumn is mapped and unmapped. The callbacks are similar to those for the BulletinBoard, but since the RowColumn is not designed specifically to be a child of a DialogShell, the routines are invoked regardless of whether the parent of the RowColumn is a DialogShell.The XmNentryCallback is the only other callback that is associated specifically with the RowColumn widget. This callback resource makes it possible to install a single callback function that acts as the activation callback for each of the children of a RowColumn widget. The routine specified for the XmNentryCallback overrides the XmNactivateCallback functions for any PushButton or CascadeButton children and the XmNvalueChangedCallback functions for ToggleButtons. The XmNentryCallback is a convenience to the programmer; if you use it, you don't have to install separate callbacks for each widget in the RowColumn. XmNentryCallback functions must be installed before children are added to the RowColumn, so be sure you call XtAddCallback() before you create any child widgets.
The callback procedure takes the standard form of an XtCallbackProc. The call_data parameter is an XmRowColumnCallbackStruct, which is defined as follows:
The reason field of this data structure is set to XmCR_ACTIVATE when the XmNentryCallback is invoked. The event indicates the event that caused the notification. The entry callback function is called regardless of which widget within the RowColumn was activated. Since an entry callback overrides any previously-set callback lists for PushButtons, CascadeButtons, and ToggleButtons, the parameters that would have been passed to these callback routines are provided in the RowColumn callback structure. The widget field specifies the child that was activated, the widget-specific callback structure is placed in the callbackstruct field, and the client data that was set for the widget is passed in the data field.typedef struct { int reason; XEvent *event; Widget widget; char *data; char *callbackstruct; } XmRowColumnCallbackStruct;Example 8-9shows the installation of an entry callback and demonstrates how the normal callback functions are overridden.15
The RowColumn is created and its XmNentryCallback is set to called(). This routine ignores the client_data parameter, as none is provided. However, we do use the data field of the callback structure because this is the data that is specified in the call to XtAddCallback() for each of the children. We install the never_called() routine for each PushButton and pass the position of the button in the RowColumn as the client_data. Even though the entry callback overrides the activate callback, the client_data is preserved./* entry_cb.c -- demonstrate how the XmNentryCallback resource works ** in RowColumn widgets. When a callback function is set for this ** resource, all the callbacks for the RowColumn's children are reset ** to point to this function. Their original functions are no longer ** called had they been set in favor of the entry-callback function. */ #include <Xm/PushBG.h> #include <Xm/RowColumn.h> char *strings[] = {"One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten"}; void called (Widget widget, XtPointer client_data, XtPointer call_data) { XmRowColumnCallbackStruct *cbs = (XmRowColumnCallbackStruct *) call_data; Widget pb = cbs->widget; printf ("%s: %d\n", XtName (pb), cbs->data); } static void never_called (Widget widget, XtPointer client_data, XtPointer call_data) { puts ("This function is never called"); } main (int argc, char *argv[]) { Widget toplevel, parent, w; XtAppContext app; int i; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); parent = XmCreateRowColumn (toplevel, "rowcolumn", NULL, 0); XtAddCallback (parent, XmNentryCallback, called, NULL); /* simply loop through the strings creating a widget for each one */ for (i = 0; i < XtNumber (strings); i++) { w = XmCreatePushButtonGadget (parent, strings[i], NULL, 0); /* Call XtAddCallback() to install client_data only! */ XtAddCallback (w, XmNactivateCallback, never_called, (XtPointer) (i+1)); XtManageChild (w); } XtManageChild (parent); XtRealizeWidget (toplevel); XtAppMainLoop (app); }Our example is a bit contrived, so it may seem pointless to call XtAddCallback() for each PushButton and specify an XmNentryCallback as well. The most compelling reason for using an entry callback is that you may want to provide client data for the RowColumn as a whole, as well as for each child widget.
Remember that the RowColumn widget is also used for a number of objects implemented internally by the Motif toolkit, such as the Motif menu system, RadioBoxes, and CheckBoxes. Many of the resources for the widget are specific to these objects, so they are not discussed here. For more information on menus, see Chapter 4, The Main Window, and Chapter 19, Menus; for information on RadioBoxes and CheckBoxes, see Chapter 12, Labels and Buttons.
The Frame Widget
The Frame is a simple manager widget; the purpose of the Frame is to draw a three-dimensional border around its child. The widget can have two children: a work area child and a title child. The Frame shrink wraps itself around its work area child, adding space for a title if one is specified. The children are responsible for setting the size of the Frame.The Frame is useful for grouping related control elements, so that they are separated visually from other elements in a window. The Frame is commonly used as the parent of RadioBoxes and CheckBoxes, since the RowColumn widget does not provide a three-dimensional border. Figure 8-18 shows a portion of a dialog box that uses Frames to segregate three groups of ToggleButtons.
To use Frame widgets in an application, you must include the file <Xm/Frame.h>. Creating a Frame widget is just like creating any other manager widget, as shown in the following code fragment:
Since the Frame performs only simple geometry management, you can also create a Frame widget as managed using XtVaCreateManagedWidget() and not worry too much about a performance loss. The Frame widget is an exception to the guidelines about creating manager widgets that we presented earlier in the chapter.Widget frame = XmCreateFrame (parent, "name", resource-value-array, resource-value-count);The principal resource used by the Frame widget is XmNshadowType. This resource specifies the style of the three-dimensional border that is placed around the work area child of the Frame. The value may be any of the following:
If the parent of the Frame is a shell widget, the default value for XmNshadowType is set to XmSHADOW_OUT and the value for XmNshadowThickness is set to 1. Otherwise, the default shadow type is XmSHADOW_ETCHED_IN and the thickness is 2. Of course, these values may be overridden by the application or the user.XmSHADOW_IN XmSHADOW_OUT XmSHADOW_ETCHED_IN XmSHADOW_ETCHED_OUTThe Frame provides some constraint resources that can be specified for its children. The XmNframeChildType16 resource indicates whether the child is the work area or the title child for the Frame. The default value is XmFRAME_WORKAREA_CHILD. To specify that a child is the title child, use the value XmFRAME_TITLE_CHILD.
The XmNchildHorizontalAlignment and XmNchildHorizontalSpacing resources control the horizontal positioning of the title. The possible values for horizontal alignment are:
The XmNchildVerticalAlignment resource specifies the vertical positioning of the title child relative to the top shadow of the Frame. The possible values for this resource are:XmALIGNMENT_BEGINNING XmALIGNMENT_END XmALIGNMENT_CENTERExample 8-10 demonstrates many of the different shadow and alignment styles that are possible with the Frame widget.17XmALIGNMENT_BASELINE_BOTTOM XmALIGNMENT_BASELINE_TOP XmALIGNMENT_CENTER XmALIGNMENT_WIDGET_TOP XmALIGNMENT_WIDGET_BOTTOM
The output of this example is shown in Figure 8-19./* frame.c -- demonstrate the Frame widget by creating ** four Labels with Frame widget parents. */ #include <Xm/LabelG.h> #include <Xm/RowColumn.h> #include <Xm/Frame.h> main (int argc, char *argv[]) { Widget toplevel, rowcol, frame, label; XtAppContext app; Arg args[10]; int n; XtSetLanguageProc (NULL, NULL, NULL); /* Initialize toolkit and create TopLevel shell widget */ toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); /* Make a RowColumn to contain all the Frames */ n = 0; XtSetArg (args[n], XmNspacing, 5); n++; rowcol = XmCreateRowColumn (toplevel, "rowcolumn", args, n); /* Some informative labelling */ label = XmCreateLabelGadget (rowcol, "Frame Types:", NULL, 0); XtManageChild (label); /* Create different Frames each containing a unique shadow type */ /* First frame: Shadow in */ n = 0; XtSetArg (args[n], XmNshadowType, XmSHADOW_IN); n++; frame = XmCreateFrame (rowcol, "frame1", args, n); label = XmCreateLabelGadget (frame, "XmSHADOW_IN", NULL, 0); XtManageChild (label); n = 0; XtSetArg (args[n], XmNframeChildType, XmFRAME_TITLE_CHILD); n++; XtSetArg (args[n], XmNchildVerticalAlignment, XmALIGNMENT_CENTER); n++; label = XmCreateLabelGadget (frame, "XmALIGNMENT_CENTER", args, n); XtManageChild (label); XtManageChild (frame); /* Second frame: Shadow out */ n = 0; XtSetArg (args[n], XmNshadowType, XmSHADOW_OUT); n++; frame = XmCreateFrame (rowcol, "frame2", args, n); label = XmCreateLabelGadget (frame, "XmSHADOW_OUT", NULL, 0); XtManageChild (label); n = 0; XtSetArg (args[n], XmNframeChildType, XmFRAME_TITLE_CHILD); n++; XtSetArg (args[n], XmNchildVerticalAlignment, XmALIGNMENT_BASELINE_TOP); n++; label = XmCreateLabelGadget (frame, "XmALIGNMENT_BASELINE_TOP", args, n); XtManageChild (label); XtManageChild (frame); /* Third frame: Etched in */ n = 0; XtSetArg (args[n], XmNshadowType, XmSHADOW_ETCHED_IN); n++; frame = XmCreateFrame (rowcol, "frame3", args, n); label = XmCreateLabelGadget (frame, "XmSHADOW_ETCHED_IN", NULL, 0); XtManageChild (label); n = 0; XtSetArg (args[n], XmNframeChildType, XmFRAME_TITLE_CHILD); n++; XtSetArg (args[n], XmNchildVerticalAlignment, XmALIGNMENT_WIDGET_TOP); n++; label = XmCreateLabelGadget (frame, "XmALIGNMENT_WIDGET_TOP", args, n); XtManageChild (label); XtManageChild (frame); /* Fourth frame: Etched out */ n = 0; XtSetArg (args[n], XmNshadowType, XmSHADOW_ETCHED_OUT); n++; frame = XmCreateFrame (rowcol, "frame4", args, n); label = XmCreateLabelGadget (frame, "XmSHADOW_ETCHED_OUT", NULL, 0); XtManageChild (label); n = 0; XtSetArg (args[n], XmNframeChildType, XmFRAME_TITLE_CHILD); n++; XtSetArg (args[n], XmNchildVerticalAlignment, XmALIGNMENT_WIDGET_BOTTOM); n++; label = XmCreateLabelGadget (frame, "XmALIGNMENT_WIDGET_BOTTOM", args, n); XtManageChild (label); XtManageChild (frame); XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
The program creates four Frame widgets. Each Frame has two Label children, one for the work area and one for the title. Each Frame uses a different value for the XmNshadowType and XmNchildVerticalPlacement resources, where these values are indicated by the text of the Labels. Although we have used a Label as the work area child of a Frame in this example, it is not a good idea to put a border around a Label. The shadow border implies selectability, which can confuse the user.
The PanedWindow Widget
The PanedWindow widget lays out its children in a horizontal or vertically-tiled format.18 The idea behind the PanedWindow is that the user can adjust the individual panes to provide more or less space as needed on a per-child basis. For example, if the user wants to see more text in a Text widget, he can use the control sashes (sometimes called grips) to resize the area for the Text widget. For a horizontal PanedWindow, set the XmNorientation resource to XmHORIZONTAL; a vertical pane has XmNorientation set to XmVERTICAL, which is the default. When the user moves the sash, the widget above or below (or, for horizontal layout, to the left or right) the one being resized is resized smaller to compensate for the size change.In a vertical layout, the width of the widget expands to that of its widest managed child and all of the other children are resized to match that width. The height of the PanedWindow is set to the sum of the heights of all of its children, plus the spacing between them and the size of the top and bottom margins.
In a horizontal layout, the height of the widget depends on the height of its tallest managed child. Similarly to the vertical case, the width of the PanedWindow depends upon the sum of the managed child widths, plus spacing and left/right margins.
By default, children are arranged in the PanedWindow in the order in which they are added. You can set the XmNpositionIndex constraint resource to control the position of a child in a PanedWindow if you do not want to use the default order.
An application that wants to use the PanedWindow widget must include the header file <Xm/PanedW.h>. An instance of the widget may be created as usual for manager widgets, as shown in the following code fragment:
The PanedWindow widget provides constraint resources that allow its children to indicate their preferred maximum and minimum sizes. Example 8-11 shows a pair of PanedWindows, each containing three widgets. One PanedWindow is in a vertical orientation, the other is horizontal.19Widget paned_w = XmCreatePanedWindow (parent, "name", resource-value-array, resource-value-count); ... XtManageChild (paned_w);
In the vertical layout, the two Label widgets are positioned above and below a Text widget. The minimum and maximum sizes of the Labels are set to 25 and 45 pixels respectively, using the resources XmNpaneMinimum and XmNpaneMaximum. No matter how the PanedWindow or any of the other widgets are resized, the two Labels cannot grow or shrink vertically beyond these bounds. The Text widget, however, only has a minimum size restriction, so it maybe resized as tall or as short as the user prefers, provided that it does not get smaller than the 35-pixel minimum. Similar constraints are placed on the Labels and Text in the horizontally oriented PanedWindow: the XmNpaneMinimum of each Label is set to 60 to prevent the user truncating the label string. The program creates the PanedWindow in the horizontal layout if the program parameter list is arbitrarily not empty (argc > 1). Figure 8-20 shows the horizontal and vertical configurations of this application./* paned_wind1.c -- two possible orientations of a PanedWindow. ** In the vertical PanedWindow, there are two Label widgets that are ** positioned above and below a Text widget. ** The Labels' minimum and maximum ** sizes are set to 25 and 45 respectively, preventing those ** panes from growing beyond those bounds. The Text widget has its ** minimum size set to 35 preventing it from becoming so small that ** its text cannot be read. ** In the horizontal orientation, the Label's minimum are set to 60 ** to prevent the label from being truncated */ #include <Xm/Label.h> #include <Xm/PanedW.h> #include <Xm/Text.h> Widget CreatePaneGroup (Widget parent, unsigned char orientation) { Widget pane, child; XmString xms; Arg args[6]; int n = 0; XtSetArg (args[n], XmNorientation, orientation); n++; pane = XmCreatePanedWindow (parent, "pane", args, n); n = 0; if (orientation == XmVERTICAL) { XtSetArg (args[n], XmNpaneMinimum, 25); n++; XtSetArg (args[n], XmNpaneMaximum, 45); n++; } else { XtSetArg (args[n], XmNpaneMinimum, 60); n++; } child = XmCreateLabel (pane, "Hello", args, n); XtManageChild (child); n = 0; XtSetArg (args[n], XmNpaneMinimum, 35); n++; XtSetArg (args[n], XmNrows, 5); n++; XtSetArg (args[n], XmNcolumns, 80); n++; XtSetArg (args[n], XmNvalue, "This is a test of the PanedWindow widget"); n++; XtSetArg (args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; child = XmCreateText (pane, "text", args, n); XtManageChild (child); n = 0; if (orientation == XmVERTICAL) { XtSetArg (args[n], XmNpaneMinimum, 25); n++; XtSetArg (args[n], XmNpaneMaximum, 45); n++; } else { XtSetArg (args[n], XmNpaneMinimum, 60); n++; } child = XmCreateLabel (pane, "Goodbye", args, n); XtManageChild (child); return pane; } main (int argc, char *argv[]) { Widget toplevel, pane; XtAppContext app; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); pane = CreatePaneGroup (toplevel, (argc > 1 ? XmHORIZONTAL : XmVERTICAL)); XtManageChild (pane); XtRealizeWidget (toplevel); XtAppMainLoop(app); }
Figure 8-21 shows how the PanedWindow behaves when a child is resized using the Sash.
Pane Constraints
One problem with setting the maximum and minimum resources for a widget involves determining exactly what those extents should be. The maximum size of 45 for the Label widgets in Example 8-11 is an arbitrary value that was selected for demonstration purposes only. If other resources had been set on one of the Labels such that the widget needed to be larger, the application would definitely look unbalanced. For example, an extremely high resolution monitor might require the use of unusually large fonts in order for text to appear normal. There are two choices available at this point. One is to specify the maximum and minimum values in a resolution-independent way and the other is to ask the Label widget itself what height it wants to be.Specifying resolution-independent dimensions requires you to carefully consider the type of application you are creating. When you specify resolution-independent values, you must specify the values in either millimeters, inches, points, or font units. The value of the XmNunitType Manager resource controls the type of units that are used. Example 8-12 demonstrates the use of resolution-independent dimensions.20
The second technique that we can use is to query the Label widgets about their heights. This technique requires the use of the Xt function XtQueryGeometry(), as shown in Example 8-13.21/* unit_types.c --the same as paned_win1.c except that the ** Labels' minimum and maximum sizes are set to 1/4 inch and ** 1/2 inch respectively. These measurements are retained ** regardless of the pixels-per-inch resolution of the user's ** display. */ #include <Xm/Label.h> #include <Xm/PanedW.h> #include <Xm/Text.h> main (int argc, char *argv[]) { Widget toplevel, pane, child; XtAppContext app; Arg args[6]; int n; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); n = 0; XtSetArg (args[n], XmNunitType, Xm1000TH_INCHES); n++; pane = XmCreatePanedWindow (toplevel, "pane", args, n); n= 0; XtSetArg (args[n], XmNpaneMinimum, 250); n++; /* quarter inch */ XtSetArg (args[n], XmNpaneMaximum, 500); n++; /* half inch */ child = XmCreateLabel (pane, "Hello",args, n); XtManageChild (child); n = 0; XtSetArg (args[n], XmNrows, 5); n++; XtSetArg (args[n], XmNcolumns, 80); n++; XtSetArg (args[n], XmNpaneMinimum, 250); n++; /* quarter inch */ XtSetArg (args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; XtSetArg (args[n], XmNvalue, "This is a test of the paned window widget."); n++; child = XmCreateText (pane, "text", args, n); XtManageChild (child); n = 0; XtSetArg (args[n], XmNpaneMinimum, 250); n++; /* quarter inch */ XtSetArg (args[n], XmNpaneMaximum, 500); n++; /* half inch */ child = XmCreateLabel (pane, "Goodbye", args, n); XtManageChild (child); XtManageChild (pane); XtRealizeWidget (toplevel); XtAppMainLoop (app); }
XtQueryGeometry() asks a widget what size it would like to be. This routine takes the following form:/* paned_wind2.c --there are two label widgets that are positioned ** above and below a Text widget. The labels' desired heights are ** queried using XtQueryGeometry() and their corresponding maximum ** and minimum sizes are set to the same value. This effectively ** prevents those panes from being resized. The Text widget has its ** minimum size set to 35 preventing it from becoming so small that ** its text cannot be read. */ #include <Xm/Label.h> #include <Xm/PanedW.h> #include <Xm/Text.h> main (int argc, char *argv[]) { Widget toplevel, pane, label, text; XtWidgetGeometry size; XtAppContext app; Arg args[8]; int n; XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaOpenApplication (&app, "Demos", NULL, 0, &argc, argv, NULL, sessionShellWidgetClass, NULL); pane = XmCreatePanedWindow (toplevel, "pane", NULL, 0); label = XmCreateLabel (pane, "Hello", NULL, 0); XtManageChild (label); size.request_mode = CWHeight; XtQueryGeometry (label, NULL, &size); XtVaSetValues (label, XmNpaneMaximum, size.height, XmNpaneMinimum, size.height, NULL); printf ("hello's height: %d\n", size.height); n = 0; XtSetArg (args[n], XmNrows, 5); n++; XtSetArg (args[n], XmNcolumns, 80); n++; XtSetArg (args[n], XmNresizeWidth, False); n++; XtSetArg (args[n], XmNresizeHeight, False); n++; XtSetArg (args[n], XmNpaneMinimum, 35); n++; XtSetArg (args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++; XtSetArg (args[n], XmNvalue, "This is a test of the paned widget."); n++; text = XmCreateText (pane, "text", args, n); XtManageChild (text); label = XmCreateLabel (pane, "Goodbye", NULL, 0); XtManageChild (label); size.request_mode = CWHeight; XtQueryGeometry (label, NULL, &size); XtVaSetValues (label, XmNpaneMaximum, size.height, XmNpaneMinimum, size. height, NULL); printf ("goodbye's height: %d\n", size.height); XtManageChild (pane); XtRealizeWidget (toplevel); XtAppMainLoop (app); }Since we do not want to resize the widget, we pass NULL for the intended parameter. We are not interested in the return value of the function, since the information that we want is returned in the preferred_return parameter. This parameter is of type XtWidgetGeometry, which is defined as follows:XtGeometryResult XtQueryGeometry ( Widget widget, XtWidgetGeometry *intended, XtWidgetGeometry *preferred_return)We tell the widget what we want to know by setting the request_mode field of the size variable that we pass to the routine. The request_mode field is checked by the query_geometry function within the called widget. Depending on which bits that are specified, the appropriate fields are set within the returned data structure. InExample 8-13, we set request_mode to CWHeight, which tells the Label widget's query_geometry method to return the desired height in the height field of the data structure. If we had wanted to know the width as well, we could have set request_mode as follows:typedef struct { XtGeometryMask request_mode; Position x, y; Dimension width, height, border_width; Widget sibling; int stack_mode; } XtWidgetGeometry;In this case, the width and height fields would be filled in by the Label widget.size.request_mode = (CWHeight | CWWidth);Once we have the Label's desired height, we can set the constraint resources XmNpaneMaximum and XmNpaneMinimum to the height of the Label. By making these two values the same, the pane associated with the Label cannot be resized. In most cases, the XtQueryGeometry() method can be used reliably to determine proper values for minimum and maximum pane extents. Setting extents is useful, since without them, the user can adjust a PanedWindow so that the size of a widget is unreasonable or unaesthetic.If you are setting the extents for a scrolled object (ScrolledText or ScrolledList), you do not need to be as concerned about the maximum extent, since these objects handle larger sizes appropriately. Minimum states are certainly legitimate though. For example, you could use the height of a font as a minimum extent for Text or a List.
The PanedWindow widget can be useful for building your own dialogs because you can control the size of the action area. The action area is always at the bottom of the dialog and its size should never be changed. See Chapter 7, Custom Dialogs, for a complete discussion of how a PanedWindow can be used in in this manner.
Sashes
The Sashes in a PanedWindow widget are in fact widgets, even though they are not described or defined publicly. While the Motif Style Guide says that the Sash is part of the PanedWindow widget, the Motif toolkit defines the object privately, which means that technically the Sash is not supported and it may change in the future. However, it is possible to get a handle to a Sash if you absolutely need one. In order to retrieve a Sash, you need to include the header file <Xm/SashP.h>. The fact that the file ends in an upper case P indicates that it is a private header file, which means that an application program should not include it. However, there is no public header file for the Sash widget, so unless you include the private header file, you cannot access the Sashes in a PanedWindow.If you retrieve all of the children from a PanedWindow using XtVaGetValues() on the XmNchildren resource, you can use the XmIsSash() macro to locate the Sash children. This macro is defined as follows:
Although XtIsSubclass() is a public function, xmSashWidgetClass is not declared publicly. One reason that you might want to get handles to the Sashes in a PanedWindow is to turn off keyboard traversal to the Sashes, as described in the next section.#define XmIsSash (w) XtIsSubclass (w, xmSashWidgetClass)
Keyboard Traversal
The Motif Style Guide specifies methods by which the user can interact with an application without using the mouse. These methods provide a way for the user to navigate through an application and activate user-interface elements on the desktop using only the keyboard. Such activity is known as keyboard traversal and is based on the Common User Access (CUA) interface specifications from Microsoft Windows and Presentation Manager.These specifications make heavy use of the TAB key to move between elements in a user interface; related interface controls are grouped into what are called tab groups. Some examples of tab groups are a set of ToggleButtons or a collection of PushButtons. Just as only one shell on the screen can have the keyboard focus, only one widget at a time can have the input focus. When keyboard activity occurs in a window, the toolkit knows which tab group is current and directs the input focus to the active item within that group.
The user can move from one item to the next within a tab group using the arrow keys. The user can move from one tab group to the next using the TAB key. To traverse the tab groups in the reverse direction, the user can use SHIFT-TAB. The CTRL key can be used with the TAB key in a Text widget to differentiate between a traversal operation and the use of the TAB key for input. The SPACEBAR activates the item that has the keyboard focus.
To illustrate the keyboard traversal mechanisms, let's examine tictactoe.c from Example 8-4. This program contains one tab group, the Form widget. Because the PushButtons inside of it are elements in the tab group, the user can move between the items in the tic-tac-toe board using the arrow keys on the keyboard, as illustrated in Figure 8-22.
Pressing the TAB key causes the input focus to be directed to the next tab group and set to the first item in the group, which is known as the home element. Since there is only one tab group in this application, the traversal mechanism moves