The ExmCommandButton widget demonstrates how to code a button widget for a DialogBox. As Figure 11-1 shows, ExmCommandButton is a subclass of ExmString. ExmCommandButton therefore inherits ExmString's ability to display a compound string. ExmCommandButton layers on the additional ability to function as an activatable button inside a DialogBox, much the way an XmPushButton is used as an OK button inside a standard selection box.
Figure 6. ExmCommandButton Position in Hierarchy.
View figure. |
In the following subsections, we examine a few features of the ExmCommandButton.
Every DialogBox button widget must be activatable. In other words, when the user explicitly or implicitly chooses your button, your button must call the appropriate activate callbacks. The ExmCommandButton widget does the following in order to become activatable:
ExmCommandButton provides an activate callback resource named XmNactivateCallback. The XmNactivateCallback resource is defined in the resources array of CommandB.c as follows:
{ XmNactivateCallback, XmCCallback, XmRCallback, sizeof(XtCallbackList), XtOffsetOf( ExmCommandButtonRec, command_button.activate_callback), XmRPointer, (XtPointer) NULL },
When an ExmCommandButton is activated, it must initialize a callback structure and then call XtCallCallbackList. Both of these things are done in several different action methods of ExmCommandButton. For example, following is the ExmCommandButtonActivate action method:
ExmCommandButtonActivate ( Widget w, XEvent *event, String *params, Cardinal *num_params ) { ExmCommandButtonWidget cw = (ExmCommandButtonWidget)w; XmAnyCallbackStruct cb; if (cw->command_button.activate_callback) { /* Initialize a callback structure. */ cb.reason = XmCR_ACTIVATE; cb.event = event; XFlush (XtDisplay(cw)); /* Call XtCallCallbackList. */ XtCallCallbackList (w, cw->command_button.activate_callback, &cb); } }
The XmNactivateCallback resource of ExmCommandButton uses the generic callback structure XmAnyCallbackStruct. Your own DialogBox button widget may define a more elaborate activation callback structure.
The XmQTactivatable trait tells a DialogBox that the ExmCommandButton widget wishes to be treated as a DialogBox button. In other words, Motif DialogBox widgets check their children for this trait. Only those children holding this trait are eligible to become DialogBox buttons.
The XmQTactivatable trait defines a trait method called changeCB. A DialogBox calls this trait method to add or remove a callback from the list of activate callbacks held by the DialogBox button widget.
The following shows how ExmCommandButton codes the changeCB trait method:
ChangeCB( Widget widget, XtCallbackProc activCB, XtPointer clientData, Boolean setUnset) { if (setUnset) XtAddCallback (widget, XmNactivateCallback, activCB, clientData); else XtRemoveCallback (widget, XmNactivateCallback, activCB, clientData); }
The Motif Style Guide mandates that a DialogBox widget have a default action associated with it at all times. For example, consider an XmSelectionBox having three DialogBox buttons: an OK button, a Cancel button, and an Apply button. Of these three buttons, suppose the OK button is the current default button. When the user activates the default action (typically by pressing Return anywhere in the DialogBox), the DialogBox must act as if the OK button was explicitly pushed.
The default button must provide some visual cue so that users will know that it is, in fact, the default button. Typically, the default button does this by displaying a border around itself; however, the widget writer is somewhat free to pick a different kind of visual cue. (See the Motif Style Guide for the exact constraints regarding this visual cue.)
To support default activation, Motif provides the XmQTtakesDefault trait. All dialog buttons must install this trait. This trait announces to DialogBox widgets that the dialog button is capable of changing its appearance to show that it is the default button.
The XmQTtakesDefault trait provides only one trait method, showAsDefault. The DialogBox managing the button will typically call this trait method one or more times, each time asking the button to get into a different state. The DialogBox will probably ask each of its buttons to get into the XmDEFAULT_READY state first. Each ExmCommandButton responds to this by expanding its margins. The DialogBox widget will then ask one of its buttons to become the default button by asking it to go into the XmDEFAULT_ON state. ExmCommandButton responds to this state by displaying distinctive highlighting. If a different button becomes the default button, the DialogBox widget asks the former default button to go into the XmDEFAULT_OFF state.ExmCommandButton responds to this request by removing its distinctive highlighting.What follows is the implementation of the showAsDefault trait method of ExmCommandButton:
ShowAsDefault(Widget w, XtEnum state) { ExmCommandButtonWidgetClass cbwc = (ExmCommandButtonWidgetClass)XtClass(w); ExmCommandButtonWidget cbw = (ExmCommandButtonWidget)w; Position start_x_of_outer_shadow, start_y_of_outer_shadow; Dimension margin_push_out; Dimension width_of_outer_shadow, height_of_outer_shadow; int dx, dy, width, height; GC top_GC, bottom_GC; Dimension outer_shadow_thickness; int outer_shadow_type; int margins_were_pushed_out=0; #define MARGIN_BETWEEN_HIGHLIGHT_AND_OUTER_SHADOW 2 start_x_of_outer_shadow = cbw->primitive.highlight_thickness + MARGIN_BETWEEN_HIGHLIGHT_AND_OUTER_SHADOW; start_y_of_outer_shadow = cbw->primitive.highlight_thickness + MARGIN_BETWEEN_HIGHLIGHT_AND_OUTER_SHADOW; width_of_outer_shadow = cbw->core.width - (2 * start_x_of_outer_shadow); height_of_outer_shadow = cbw->core.height - (2 * start_y_of_outer_shadow); outer_shadow_thickness = 3; switch (state) { case XmDEFAULT_READY: /* Push out the margins to make room for subsequent increases in the shadow thickness. The request to push out the margins will increase the size of the CommandButton widget assuming that its manager has the space to spare. */ if (cbw->primitive.shadow_thickness < 5) margin_push_out = 5; else margin_push_out = cbw->primitive.shadow_thickness; margins_were_pushed_out = 1; XtVaSetValues((Widget)cbw, XmNmarginWidth, cbw->simple.margin_width + margin_push_out, XmNmarginHeight, cbw->simple.margin_height + margin_push_out, NULL); break; case XmDEFAULT_ON: /* Draw an outer shadow. The outer shadow is drawn outside the widget's margins but inside the border highlight. The inner shadow is drawn by the DrawShadow method. */ top_GC = cbw->primitive.top_shadow_GC; bottom_GC = cbw->primitive.bottom_shadow_GC; outer_shadow_type = cbw->command_button.visual_armed ? XmSHADOW_ETCHED_IN: XmSHADOW_ETCHED_OUT; XmeDrawShadows(XtDisplay(w), XtWindow(w), top_GC, bottom_GC, start_x_of_outer_shadow, start_y_of_outer_shadow, width_of_outer_shadow, height_of_outer_shadow, outer_shadow_thickness, outer_shadow_type); break; case XmDEFAULT_OFF: /* Erase the outer shadow when the widget is no longer the default. */ XmeClearBorder(XtDisplay(w), XtWindow(w), start_x_of_outer_shadow, start_y_of_outer_shadow, width_of_outer_shadow, height_of_outer_shadow, outer_shadow_thickness); break; case XmDEFAULT_FORGET: default: /* The widget is not a potential default button. If XmDEFAULT_FORGET is called at some point after XmDEFAULT_READY was called, then we have to restore the margins back to their original size. */ if (margins_were_pushed_out) XtVaSetValues((Widget)cbw, XmNmarginWidth, cbw->simple.margin_width - margin_push_out, XmNmarginHeight, cbw->simple.margin_height - margin_push_out, NULL); break; } }