[ Previous | Next | Contents | Glossary | Home | Search ]
Motif 2.1 Programmer's Guide



Event Handling and Callbacks

DrawingArea has callbacks, translations, and actions that inform the application when the DrawingArea is resized or when it receives an exposure event or one of many input events. DrawingArea has the following callbacks:

XmNexposeCallback

DrawingArea invokes these callbacks whenever its expose widget class procedure is called. The callback reason is XmCR_EXPOSE.

XmNinputCallback

DrawingArea invokes these callbacks from the DrawingAreaInput() action. With the default translations, this action is called when the DrawingArea receives a key press, key release, button press, or button release event. The callback reason is XmCR_INPUT.

XmNresizeCallback
DrawingArea invokes these callbacks whenever its resize widget class procedure is called. The callback reason is XmCR_RESIZE.

Each callback procedure is passed a pointer to an XmDrawingAreaCallbackStruct, which includes the reason, the event (NULL for XmNresizeCallback), and the DrawingArea's window.

Handling Resize Events

A widget's resize procedure is invoked when the widget is resized by its parent or when the widget's width or height changes as a result of XtSetValues. DrawingArea also invokes its own resize procedure when it has made a successful geometry request of its parent to change its width or height.

For most widgets, the resize procedure recomputes the widget's layout to take account of the new size. DrawingArea's resize procedure does no layout of its own. It simply invokes the XmNresizeCallback callbacks. It is the responsibility of these callback procedures to resize or reposition children or to recompute other contents of the DrawingArea. The callback procedures essentially take the place of the DrawingArea's resize procedure.

Note that a resize procedure can be called when the widget is not realized.

Moving and Resizing Children

An XmNresizeCallback procedure should reposition or resize children by calling XtMoveWidget, XtResizeWidget, or XtConfigureWidget. Use of these functions is usually restricted to widget class methods, but for DrawingArea the XmNresizeCallback procedures act as part of the widget class resize procedure.

A callback procedure could also resize or reposition a child by invoking XtSetValues on one or more of the child's geometry resources (XmNx, XmNy, XmNheight, XmNwidth, and XmNborderWidth). This causes XtSetValues to generate a geometry request on behalf of the child. This request in turn might cause the DrawingArea to make a geometry request of its own parent. In particular, when a child's request would cause the DrawingArea to change size and when the XmNresizePolicy of the DrawingArea is XmRESIZE_GROW or XmRESIZE_ANY, the DrawingArea is likely to make a geometry request.

However, the Intrinsics forbid a widget's resize procedure from making geometry requests. Therefore, an XmNresizeCallback procedure must take care not to reposition or resize a child in such a way that the DrawingArea makes a geometry request. The easiest way to avoid this problem is to use XtMoveWidget, XtResizeWidget, and XtConfigureWidget, which are guaranteed not to make geometry requests.

An XmNresizeCallback procedure must take care not to call the resize procedure for a child that is in the midst of making a geometry request. This situation can arise when a child makes a geometry request, perhaps as a result of XtSetValues, that would cause the DrawingArea to change size. If the DrawingArea's geometry_manager procedure issues a successful geometry request, it invokes its own resize procedure, which in turn calls the XmNresizeCallback procedures.

When this situation arises, the XmNresizeCallback procedure must not call the requesting child's resize procedure, whether it does this directly, as a result of calling XtResizeWidget or XtConfigureWidget, or as a result of a call to XtSetValues that changes the child's width or height. If an application causes a DrawingArea child to make a geometry request--for example, by calling XtSetValues for one of the child's geometry resources--it should store information in an internal data structure that identifies that child as making a geometry request. The XmNresizeCallback procedure should check this information and take care not to call that child's resize procedure.

Resizing and Redisplay

A resize procedure often recomputes the layout of the widget but does not actually perform the redisplay. In many cases, the act of resizing the widget generates one or more subsequent exposure events, and these in turn cause Xt to invoke the widget's expose procedure. In general, the expose procedure is responsible for redisplay.

However, resizing a widget does not always generate exposure events, particularly when the widget is made smaller. This is not a problem when the widget's contents consist solely of child widgets or gadgets. The resize procedure can reposition or resize the children, and these actions generate the appropriate exposure events for both the children and the parent.

A resizing without an exposure event presents a problem when the contents of the widget include graphics, text, or other decoration outside child widgets. For example, if the widget displays a shadow or other decoration around its inside edge, it must redisplay that decoration when the widget becomes smaller. An application using a DrawingArea in this way must arrange to redisplay the window contents when the DrawingArea becomes smaller. Following are two possible approaches:

  1. In an XmNresizeCallback procedure, compare the DrawingArea's width and height with their previous values. If either width or height has decreased, redisplay the appropriate portions of the DrawingArea's contents. In an internal data structure, store the width and height as the previous width and height for use by the next invocation of the XmNresizeCallback procedure.

  2. In an XmNexposeCallback procedure, when the procedure is first invoked, set the window's bit gravity to ForgetGravity. This causes the window's contents to be lost and an exposure event to be generated anytime the window is resized. If the application does not set the bit gravity of the DrawingArea's window, the default set by the toolkit is NorthWestGravity. This usually causes the server not to generate an exposure event when the window is made smaller.

    DrawingArea itself does not draw shadows, and the default XmNshadowThickness is 0. It is not practical for an application to draw Motif shadows itself in a DrawingArea, because the Motif shadow-drawing interface is not public. An application that wants shadows with a DrawingArea should place the DrawingArea inside a Frame.

  3. Example of a Resize Procedure

    The following code from the DrawCB callback procedure handles an XmNresizeCallback. The procedure spreads or contracts the layout of children and lines in proportion to the increase or decrease in size of the DrawingArea. It uses an internal data structure to hold information about the end points of the lines and the previous width and height of the DrawingArea.

    static void DrawCB (w, client_data, call_data)
    Widget          w;              /*  widget id           */
    caddr_t         client_data;    /*  data from application   */
    caddr_t         call_data;      /*  data from widget class  */
    {
     
        XmDrawingAreaCallbackStruct * dacs =
            (XmDrawingAreaCallbackStruct *) call_data;
        Arg args[5];
        int n;
        Dimension width, height;
        Graphic * graph = (Graphic *) client_data;
     
        switch (dacs->reason) {
        ...
        case XmCR_RESIZE:
            n = 0;
            XtSetArg (args[n], XmNwidth, &width);  n++;
            XtSetArg (args[n], XmNheight, &height);  n++;
            XtGetValues (w, args, n);
            ReSize(graph, width, height);
            break;
        ...

    The ReSize method contains the following code:

    static void ReSize(graph, width, height)
    Graphic * graph;
    Dimension width, height;
    {
     Widget w = graph->work_area;
     Cardinal i,j;
     Arg args[5];
     int n;
     Widget * children;
     Cardinal num_children;
     Position x,y;
     
        float xratio = (float) width / graph->old_width,
              yratio = (float) height / graph->old_height;
     
        /* reposition and resize the graphic units */
        for (i=0; i < graph->num_graphics; i++) {
            for (j=0; j < graph->graphics[i].num_points; j++) {
                graph->graphics[i].points[j].x *= xratio;
                graph->graphics[i].points[j].y *= yratio;
            }
        }
     
        /* reposition the pushbutton children */
        /* I can use XtMoveWidget here since it's like being part of the
           widget resize class method... */
        n = 0;
        XtSetArg (args[n], XmNnumChildren, &num_children);  n++;
        XtSetArg (args[n], XmNchildren, &children);  n++;
        XtGetValues (w, args, n);
        for (i=0; i < num_children; i++) {
            n = 0;
            XtSetArg (args[n], XmNx, &x);  n++;
            XtSetArg (args[n], XmNy, &y);  n++;
            XtGetValues (children[i], args, n);
            XtMoveWidget(children[i],
                         (Position) (x * xratio),
                         (Position) (y * yratio));
        }
     
        graph->old_width = width;
        graph->old_height = height;
    }

    Handling Exposure Events

    Xt calls a widget's expose procedure when the widget receives an exposure event. The precise types of events that cause Xt to invoke the expose procedure are determined by the widget class compress_exposure field. For XmDrawingArea, the value of this field is XtExposeNoCompress. This means that Xt invokes the expose procedure when the widget receives an Expose event.

    When the expose procedure is called, some part of the contents of the widget's window has been lost, and the window needs to be redisplayed. Xt redisplays the contents of widget children by calling their expose procedures. DrawingArea's expose procedure calls the XmNexposeCallback procedures. These callbacks are responsible for redisplaying any contents of the DrawingArea that are outside the DrawingArea's children. DrawingArea's expose procedure then redisplays the contents of gadget children by calling their expose procedures.

    The X server generates Expose events when parts of a window are exposed for a variety of reasons, as when the window is raised or resized. The server determines which portions of the window are exposed and decomposes these into a series of rectangles. The server generates a series of Expose events, one for each rectangle.

    DrawingArea does not compress exposure events. The expose procedure, and therefore the XmNexposeCallback list, is called for each rectangle in an exposure series. A simple callback procedure may redisplay the entire window on each exposure series. Such a procedure should examine the count member of the XExposeEvent structure for the event. A nonzero count indicates that more events are to follow in the exposure series. The callback procedure should ignore these events and redisplay the entire window when count reaches 0.

    A more complex procedure may redisplay only the exposed rectangles. Such a procedure should extract the bounds of each rectangle from the x, y, width, and height members of each XExposeEvent structure. The procedure can either redisplay each rectangle immediately or accumulate all the rectangles in an exposure series into a region, using XtAddExposureToRegion, and then redisplay the region.

    An application that draws directly into the DrawingArea must be sure to regenerate the window contents correctly when the DrawingArea becomes smaller. Making the DrawingArea smaller does not always generate Expose events. The application can either perform the redisplay in an XmNresizeCallback procedure or, on the first invocation of the XmNexposeCallback list, set the window's bit gravity to ForgetGravity. This ensures that each resizing of the DrawingArea generates an Expose event, so the application can safely leave all redisplay to the XmNexposeCallback procedure. However, it also means that application must regenerate the entire contents of the window every time the window is resized.

    Example of an Expose Procedure

    The following code from the DrawCB callback procedure handles an XmNexposeCallback. The first time the procedure is invoked, it sets the window's bit gravity to ForgetGravity so that resizing the window generates Expose events. It uses an internal data structure to hold information about the end points of the lines.

    static void DrawCB (w, client_data, call_data)
    Widget          w;              /*  widget id           */
    caddr_t         client_data;    /*  data from application   */
    caddr_t         call_data;      /*  data from widget class  */
    {
     
        XmDrawingAreaCallbackStruct * dacs =
            (XmDrawingAreaCallbackStruct *) call_data;
        XSetWindowAttributes xswa;
        Graphic * graph = (Graphic *) client_data;
     
        static Boolean first_time = True;
     
      static Boolean first_time = True;
        switch (dacs->reason) {
        case XmCR_EXPOSE:
            if (first_time) {
                /* Change once the bit gravity of the Drawing Area; default
                   is north west and we want forget, so that resize
                   always generates exposure events */
                first_time = False;
                xswa.bit_gravity = ForgetGravity;
                XChangeWindowAttributes(XtDisplay(w), XtWindow(w),
                                        CWBitGravity, &xswa);
            }
            ReDraw(graph, dacs->event);
            break;
         ...

    The ReDraw method looks as follows:

    static void ReDraw(graph, event)
    Graphic * graph;
    XEvent * event;
    {
        Cardinal i;
        Widget w = graph->work_area;
     
        for (i=0; i < graph->num_graphics; i++) {
            if (graph->graphics[i].type == POLYLINE)
                XDrawLines(XtDisplay(w), XtWindow(w),
                           XDefaultGC(XtDisplay(w),
                           XDefaultScreen(XtDisplay(w))),
                           graph->graphics[i].points,
                           graph->graphics[i].num_points,
                           CoordModeOrigin);
        }
    }

    Handling Input Events

    As with any manager, DrawingArea may have three general kinds of input events within its borders:

    1. Events that belong to a widget child

    2. Events that belong to a gadget child

    3. Events that belong to no child

      Xt dispatches events to widget children when appropriate, and the DrawingArea does not process these. DrawingArea inherits Manager's translations for dispatching events to gadget children. Before calling any Manager action as a result of a button press or release or a key press or release, DrawingArea calls its own DrawingAreaInput() action. DrawingArea also calls this action whenever it receives a button press or release or a key press or release that does not have an associated Manager action.

      The DrawingAreaInput() action simply returns if the input event is not of type KeyPress, KeyRelease, ButtonPress, ButtonRelease, or MotionNotify. If the event is of one of these types, and if the event does not take place within a gadget child of the DrawingArea, the action calls the XmNinputCallback callbacks.

      With the default translations, the result is that the XmNinputCallback procedures are invoked whenever the DrawingArea receives a KeyPress, KeyRelease, ButtonPress, or ButtonRelease event that does not occur within a child.

      The default translations do not invoke the DrawingAreaInput() action, and therefore the XmNinputCallback procedures, when the DrawingArea receives a MotionNotify event. An application that wants its XmNinputCallback procedures invoked on pointer motion events must install the appropriate translations. When installing a translation for BtnMotion, the application must override the existing translations. The following translations cause a motion event to be sent to any gadget child in which it takes place. If the event does not take place within a child, the XmNinputCallback procedures are invoked:

      <BtnMotion>:DrawingAreaInput()
      ManagerGadgetButtonMotion()\n\
      <Motion>:DrawingAreaInput()

      There is one problem with these translations: Because DrawingArea has translations for Btn1 click and double click, the BtnMotion actions are not invoked when the user moves the pointer while pressing Btn1. In order to receive these events, the application must replace the DrawingArea translations, omitting the translations for Btn1 click and double click.

    4. Example of an Input Procedure

      Following is the portion of the DrawCB that handles the XmNinputCallback procedure. The DrawingArea contains button children and lines connecting them. The procedure takes action on ButtonPress and MotionNotify events. When the user presses a mouse button, the procedure retrieves the text from a TextField elsewhere in the application. If the user has entered text here, the input procedure creates a PushButton with the text as the label and places it at the point of the click. If the TextField contains no text and the user has pressed a button over a line or PushButton while holding the Shift key, the procedure deletes the line or PushButton.

      If the TextField is empty and the user presses a button without holding the Shift key, the procedure either starts or finishes drawing a line. The application uses a rubber-banding effect for line drawing. When it starts a line, the procedure sets a flag indicating it is drawing a line; when it finishes the line, the procedure clears this flag. When the procedure receives a MotionNotify event and is in the process of drawing a line, it erases the previous line (using XOR) and draws a new line from the anchor point to the current pointer position.

      case XmCR_INPUT:
          if (dacs->event->type == ButtonPress) {
              name = XmTextFieldGetString(graph->textf); /* textfield */
              if (strcmp ("", name) != 0) {
                  n = 0;
                  XtSetArg (args[n], XmNx, dacs->event->xbutton.x);  n++;
                  XtSetArg (args[n], XmNy, dacs->event->xbutton.y);  n++;
                  newpush = XmCreatePushButton(w, name, args, n);
                  XtAddCallback (newpush, XmNactivateCallback, PushCB, NULL);
                  XtManageChild (newpush);
              } else
              if ((dacs->event->xbutton.state & ShiftMask) &&
                  (!graph->in_drag)) {
                  DeleteUnit (graph, dacs->event->xbutton.x,
                              dacs->event->xbutton.y);
              } else {
                  if (!graph->in_drag) {
                      StartUnit(graph, dacs->event->xbutton.x,
                                dacs->event->xbutton.y);
                  } else {
                      EndUnit(graph, dacs->event->xbutton.x,
                              dacs->event->xbutton.y);
                  }
              }
              XtFree(name);
          } else  /* need to get motion events here: app_default should
                     modified DrawingArea translation with both Motion
                     and BtnMotion addition */
          if (dacs->event->type == MotionNotify) {
              /* this one just exits if in_drag is False */
              DragUnit(graph, dacs->event->xbutton.x,
                       dacs->event->xbutton.y);
          }
          break;
      }

      [ Previous | Next | Contents | Glossary | Home | Search ]