XFillRectangle (dpy, pixmap, gc, 0, 0, WIDTH, HEIGHT);
/* don't foreget to reset */
XSetForeground (dpy, gc, BlackPixelOfScreen (XtScreen (widget)));
}
/* Note: we don't have to use WIDTH and HEIGHT−−we could pull the
* exposed area out of the event structure, but only if the reason
* was XmCR_EXPOSE... make it simple for the demo; optimize as needed.
*/
XCopyArea (dpy, pixmap, event−>xany.window, gc,
0, 0, WIDTH, HEIGHT, 0, 0);
}
}
A frequent problem encountered in using the DrawingArea widget is the need to redraw after every Resize event.
When you enlarge the DrawingArea window, an Expose event is automatically generated since more of the window
becomes exposed. But, if you shrink the window, no Expose event is generated since no new part of the window is
being exposed.
The reason why no Expose event is generated when you shrink a DrawingArea widget is deep inside Xlib. The bit
gravity of a window indicates where new bits are placed automatically by X when a window is resized. If you resize a
window larger, then the data in the window remains in the top−left corner and the application gets a Resize event
and an Expose event. The Expose event just identifies the newly exposed area, not the entire window. If you make
the window smaller, all of the data in the window gets pushed to the top left; there is no newly exposed area, so there
is no Expose event.
The solution is to make the window forget about bit gravity, so every Resize event causes all of the bits to be
cleared. As a result, the Expose event identifies the entire window as being exposed, instead of just the newly
exposed region. This technique has the side effect of generating an Expose event even when the window is resized
smaller.
There is no routine to set the bit gravity of a window individually. It can be set only with
XChangeWindowAttributes(), as in the following code fragment:
XSetWindowAttributes attrs;
attrs.bit_gravity = ForgetGravity;
XChangeWindowAttributes (XtDisplay (drawing_area),
XtWindow (drawing_area), CWBitGravity, &attrs);
Once you do this, the DrawingArea widget gets Expose events when you resize it to be smaller.
11.3 Using Translations on a DrawingArea
As mentioned earlier, it is generally permissible to override or replace the default translation table of the DrawingArea
widget with new translations. The only potential problem is if you plan to use the DrawingArea as a manager for other
widgets and you expect it to follow the keyboard traversal mechanisms described by the Motif Style Guide. In fact,
handling keyboard traversal is pretty much all that the default translations for the DrawingArea do. For example, the
following is a subset of the default translations for the DrawingArea widget: This translation table lists only a subset
of the current translations in the DrawingArea widget; there is no guarantee that the translations will remain the same
in future revisions of the toolkit.
<Key>osfSelect: DrawingAreaInput() ManagerGadgetSelect()
<Key>osfActivate: DrawingAreaInput() ManagerParentActivate()
<Key>osfHelp: DrawingAreaInput() ManagerGadgetHelp()
<KeyDown>: DrawingAreaInput() ManagerGadgetKeyInput()
11 The DrawingArea Widget11.3 Using Translations on a DrawingArea
290
<KeyUp>: DrawingAreaInput()
<BtnMotion>: ManagerGadgetButtonMotion()
<Btn1Down>: DrawingAreaInput() ManagerGadgetArm()
<Btn1Down>,<Btn1Up>: DrawingAreaInput() ManagerGadgetActivate()
These translations show that the manager widget part of the DrawingArea is responsible for tracking events for its
gadget children. It is not necessary to support these translations if you are not going to use the DrawingArea to
manage children. Most user−generated events also invoke DrawingAreaInput(), which does not do any drawing,
but simply invokes the XmNinputCallback.
As you can see, the BtnMotion translation is not passed to DrawingAreaInput(), which means that the
XmNinputCallback is not called for pointer motion events. When it comes to more complex drawing than that
done in the source code this omission is a serious deficiency. To support rubberbanding or free−hand drawing
techniques, which require pointer motion events, you must install either an event handler or a translation entry to
handle motion events.
The simplest approach would be to replace the translation table entry for <BtnMotion> events. However, this is not
possible, due to a bug in the X Toolkit Intrinsics. The correct thing to do is the following:
String translations =
"<Btn1Motion>: DrawingAreaInput() ManagerGadgetButtonMotion()";
...
drawing_a = XtVaCreateManagedWidget ("drawing_a",
xmDrawingAreaWidgetClass, main_w,
...
NULL);
XtOverrideTranslations (drawing_a, XtParseTranslationTable (translations));
XtAddCallback (drawing_a, XmNinputCallback, draw, NULL);
With this new translation, the XmNinputCallback function (draw()) would be notified of pointer motion while
Button 1 is down.
XtOverrideTranslations() is the preferred method for installing a new translation into the DrawingArea
widget because it is nondestructive. The routine only replaces translations for which identical events are specified and
leaves all other translations in place. However, this routine does not work in this case because there is already a
translation for the Button 1 down−up sequence in the DrawingArea translation table. In the current implementation,
once Button 1 goes down, the Xt event translator waits for the Button 1 up event to match the partially finished
translation. Therefore, no Button 1 motion events can be caught. If we want to get pointer motion events while the
button is down, we have to resort to other alternatives.
One such alternative is to replace the entire translation table, regardless of whether we are adding new entries or
overriding existing ones. This is known as a destructive override because the existing translation table is thrown out.
This action has the desired effect because the offending Button 1 translation is thrown out. However, we must then
take steps to re−install any other default translations that are still required. To completely replace the existing
translations, the XmNtranslations resource can be set as shown in the following code fragment:
String translations =
"<Btn1Motion>: DrawingAreaInput() ManagerGadgetButtonMotion()";
...
drawing_a = XtVaCreateManagedWidget ("drawing_a",
xmDrawingAreaWidgetClass, main_w,
XmNtranslations, XtParseTranslationTable (translations),
NULL);
XtAddCallback (drawing_a, XmNinputCallback, draw, NULL);
11 The DrawingArea Widget11.3 Using Translations on a DrawingArea
291
Once you go to the trouble of replacing the translation table, you may as well install your own action functions as
well. Doing so allows you to do the drawing directly from the action functions, rather than using it as an intermediate
function to call an application callback. This direct−drawing approach is demonstrated in the source code The
program uses pointer motion to draw lines as the pointer is dragged with the button down, rather than when the button
is pressed and released. You'll notice that we have used much the same design as in the source code but have moved
some of the code into different callback routines and have placed the DrawingArea widget into a MainWindow widget
for flexibility. None of these changes are required nor do they enhance performance in any way. They merely point
out different ways of providing the same functionality. XtSetLanguageProc() is only available in X11R5; there
is no corresponding function in X11R4.
/* free_hand.c −− simple drawing program that does freehand
* drawing. We use translations to do all the event handling
* for us rather than using the drawing area's XmNinputCallback.
*/
#include <Xm/MainW.h>
#include <Xm/DrawingA.h>
#include <Xm/PushBG.h>
#include <Xm/RowColumn.h>
/* Global variables */
GC gc;
Pixmap pixmap;
Dimension width, height;
main(argc, argv)
int argc;
char *argv[];
{
Widget toplevel, main_w, drawing_a, pb;
XtAppContext app;
XGCValues gcv;
void draw(), redraw(), clear_it();
XtActionsRec actions;
String translations = /* for the DrawingArea widget */
/* ManagerGadget* functions are necessary for DrawingArea widgets
* that steal away button events from the normal translation tables.
*/
"<Btn1Down>: draw(down) ManagerGadgetArm() 0 <Btn1Up>: draw(up) ManagerGadgetActivate() 0 <Btn1Motion>: draw(motion) ManagerGadgetButtonMotion()";
XtSetLanguageProc (NULL, NULL, NULL);
toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0,
&argc, argv, NULL, NULL);
/* Create a MainWindow to contain the drawing area */
main_w = XtVaCreateManagedWidget ("main_w",
xmMainWindowWidgetClass, toplevel,
XmNscrollingPolicy, XmAUTOMATIC,
NULL);
/* Add the "draw" action/function used by the translation table */
actions.string = "draw";
actions.proc = draw;
XtAppAddActions (app, &actions, 1);
/* Create a DrawingArea widget. Make it 5 inches wide by 6 inches tall.
* Don't let it resize so the Clear Button doesn't force a resize.
*/
drawing_a = XtVaCreateManagedWidget ("drawing_a",
11 The DrawingArea Widget11.3 Using Translations on a DrawingArea
292
xmDrawingAreaWidgetClass, main_w,
XmNtranslations, XtParseTranslationTable (translations),
XmNunitType, Xm1000TH_INCHES,
XmNwidth, 5000, /* 5 inches */
XmNheight, 6000, /* 6 inches */
XmNresizePolicy, XmNONE, /* remain this a fixed size */
NULL);
/* When scrolled, the drawing area will get expose events */
XtAddCallback (drawing_a, XmNexposeCallback, redraw, NULL);
/* convert drawing area back to pixels to get its width and height */
XtVaSetValues (drawing_a, XmNunitType, XmPIXELS, NULL);
XtVaGetValues (drawing_a, XmNwidth, &width, XmNheight, &height, NULL);
/* create a pixmap the same size as the drawing area. */
pixmap = XCreatePixmap (XtDisplay (drawing_a),
RootWindowOfScreen (XtScreen (drawing_a)), width, height,
DefaultDepthOfScreen (XtScreen (drawing_a)));
/* Create a GC for drawing (callback). Used a lot −− make global */
gcv.foreground = WhitePixelOfScreen (XtScreen (drawing_a));
gc = XCreateGC (XtDisplay (drawing_a),
RootWindowOfScreen (XtScreen (drawing_a)), GCForeground, &gcv);
/* clear pixmap with white */
XFillRectangle (XtDisplay (drawing_a), pixmap, gc, 0, 0, width, height);
/* drawing is now drawn into with "black"; change the gc */
XSetForeground (XtDisplay (drawing_a), gc,
BlackPixelOfScreen (XtScreen (drawing_a)));
pb = XtVaCreateManagedWidget ("Clear",
xmPushButtonGadgetClass, drawing_a, NULL);
/* Pushing the clear button calls clear_it() */
XtAddCallback (pb, XmNactivateCallback, clear_it, drawing_a);
XtRealizeWidget (toplevel);
XtAppMainLoop (app);
}
/* Action procedure to respond to any of the events from the
* translation table declared in main(). This function is called
* in response to Button1 Down, Up and Motion events. Basically,
* we're just doing a freehand draw −− not lines or anything.
*/
void
draw(widget, event, args, num_args)
Widget widget;
XEvent *event;
String *args;
int *num_args;
{
static Position x, y;
XButtonEvent *bevent = (XButtonEvent *) event;
if (*num_args != 1)
XtError ("Wrong number of args!");
if (strcmp (args[0], "down")) {
/* if it's not "down", it must either be "up" or "motion"
* draw full line from anchor point to new point.
*/
XDrawLine (bevent−>display, bevent−>window, gc, x, y,
bevent−>x, bevent−>y);
11 The DrawingArea Widget11.3 Using Translations on a DrawingArea
293
XDrawLine (bevent−>display, pixmap, gc, x, y, bevent−>x, bevent−>y);
}
/* freehand is really a bunch of line segments; save this point */
x = bevent−>x;
y = bevent−>y;
}
/* Clear the window by clearing the pixmap and calling XCopyArea() */
void
clear_it(pb, client_data, call_data)
Widget pb;
XtPointer client_data;
XtPointer call_data;
{
Widget drawing_a = (Widget) client_data;
XmPushButtonCallbackStruct *cbs =
(XmPushButtonCallbackStruct *) call_data;
/* clear pixmap with white */
XSetForeground (XtDisplay (drawing_a), gc,
WhitePixelOfScreen (XtScreen (drawing_a)));
XFillRectangle (XtDisplay (drawing_a), pixmap, gc, 0, 0, width, height);
/* drawing is now done using black; change the gc */
XSetForeground (XtDisplay (drawing_a), gc,
BlackPixelOfScreen (XtScreen (drawing_a)));
XCopyArea (cbs−>event−>xbutton.display, pixmap, XtWindow (drawing_a), gc,
0, 0, width, height, 0, 0);
}
/* redraw is called whenever all or portions of the drawing area is
* exposed. This includes newly exposed portions of the widget resulting
* from the user's interaction with the scrollbars.
*/
void
redraw(drawing_a, client_data, call_data)
Widget drawing_a;
XtPointer client_data;
XtPointer call_data;
{
XmDrawingAreaCallbackStruct *cbs =
(XmDrawingAreaCallbackStruct *) call_data;
XCopyArea (cbs−>event−>xexpose.display, pixmap, cbs−>window, gc,
0, 0, width, height, 0, 0);
}
The output of the program is shown in the figure.
11 The DrawingArea Widget11.3 Using Translations on a DrawingArea
294

Get Volume 6A: Motif Programming Manual now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.