In this example, the ManagerGadget actions don't do anything unless the pointer is inside the Clear button, so the
translation is relatively safe. However, you should be sure to remember that both actions are called. If you press
Button 1 inside the PushButton and doodle around a bit before releasing it, the drawing is still done, even though the
result is hidden by the gadget. In another application, the fact that actions for both the drawing area itself and its
gadget children are both called might lead to indeterminate results.
The draw() action does not (and cannot) know if the gadget is also going to react to the button event. This problem
does not exist with the standard DrawingAreaInput() action routine used in the previous examples because that
routine is implemented by the Motif toolkit and it uses its own internal mechanisms to determine if the gadget is
activated as well. If the DrawingArea does process the event on the gadget, the DrawingAreaInput() action
knows that it should not invoke the callback function. However, this internal mechanism is not available outside of the
widget code. Reordering the action functions does not help, since there is still no way to know, without making an
educated guess, whether or not the DrawingArea acted upon an event on behalf of a gadget child.
As a result of this problem, draw() starts drawing a line, even if it starts in the middle of the PushButton, because
the DrawingArea processes all of the action functions in the list. If you drag the pointer out of the gadget before
releasing the mouse button, the starting point of the line is inside the gadget, but it is hidden when the gadget repaints
itself. However, in this particular situation, you can do some guesswork. By installing an XmNarmCallback
function, you can tell whether or not the DrawingArea activated a button, and by setting an internal state variable, you
can decide whether or not the draw() action routine should do its drawing.
This confusing behavior is yet another reason why it is best not to include children in DrawingArea widgets that are
intended for interactive graphics. If the DrawingArea does not have any gadget children, installing these auxiliary
actions in the translation table is not necessary.
11.4 Using Color in a DrawingArea
In this section, we expand on our previous examples by incorporating color. The choice of colors is primarily
supported by a function we define called set_color(), which takes a widget and an arbitrary color name and sets
the global GC's foreground color. By providing an array of colors in the form of colored PushButtons, we've got a
color paint program. We have removed the PushButton gadget from the DrawingArea and created a proper control
panel to the left of the DrawingArea. The program uses a RowColumn widget (see Section #srowcolumn in
Chapter 8, Manager Widgets) to manage a set of eighteen colored PushButtons. On a monochrome screen, the
program runs, but the buttons are either black or white, depending on which is closer to the RGB values corresponding
to the color names chosen. You can only draw with the black buttons, since the background is already white. The
program that demonstrates these techniques is shown in the source code XtSetLanguageProc() is only available
in X11R5; there is no corresponding function in X11R4.
/* color_draw.c −− simple drawing program using predefined colors. */
#include <Xm/MainW.h>
#include <Xm/DrawingA.h>
#include <Xm/PushBG.h>
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
#include <Xm/ScrolledW.h>
#include <Xm/Form.h>
GC gc;
Pixmap pixmap;
/* dimensions of drawing area (pixmap) */
Dimension width, height;
String colors[] = {
11 The DrawingArea Widget 11.4 Using Color in a DrawingArea
296
"Black", "Red", "Green", "Blue", "White", "Navy", "Orange", "Yellow",
"Pink", "Magenta", "Cyan", "Brown", "Grey", "LimeGreen", "Turquoise",
"Violet", "Wheat", "Purple"
};
main(argc, argv)
int argc;
char *argv[];
{
Widget toplevel, main_w, sw, rc, form, drawing_a, pb;
XtAppContext app;
XGCValues gcv;
void draw(), redraw(), set_color(), exit(), clear_it();
int i;
XtActionsRec actions;
String translations = /* for the DrawingArea widget */
"<Btn1Down>: draw(down)0 <Btn1Up>: draw(up) 0 <Btn1Motion>: draw(motion)";
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",
xmFormWidgetClass, toplevel, NULL);
/* Create a GC for drawing (callback). Used a lot −− make global */
gcv.foreground = WhitePixelOfScreen (XtScreen (main_w));
gc = XCreateGC (XtDisplay (main_w),
RootWindowOfScreen (XtScreen (main_w)), GCForeground, &gcv);
/* Create a 3−column array of color tiles */
rc = XtVaCreateWidget ("rc", xmRowColumnWidgetClass, main_w,
XmNnumColumns, 3,
XmNpacking, XmPACK_COLUMN,
XmNleftAttachment, XmATTACH_FORM,
XmNtopAttachment, XmATTACH_FORM,
NULL);
for (i = 0; i < XtNumber(colors); i++) {
/* Create a single tile (pixmap) for each color */
pixmap = XCreatePixmap (XtDisplay (rc),
RootWindowOfScreen (XtScreen (rc)),
16, 16, DefaultDepthOfScreen (XtScreen (rc)));
set_color (rc, colors[i]); /* set the gc's color according to name */
XFillRectangle (XtDisplay (main_w), pixmap, gc, 0, 0, 16, 16);
pb = XtVaCreateManagedWidget (colors[i], xmPushButtonWidgetClass, rc,
XmNlabelType, XmPIXMAP,
XmNlabelPixmap, pixmap,
NULL);
/* callback for this pushbutton sets the current color */
XtAddCallback (pb, XmNactivateCallback, set_color, colors[i]);
}
XtManageChild (rc);
pb = XtVaCreateManagedWidget ("Quit",
xmPushButtonGadgetClass, main_w,
XmNleftAttachment, XmATTACH_FORM,
XmNtopAttachment, XmATTACH_WIDGET,
XmNtopWidget, rc,
NULL);
11 The DrawingArea Widget 11.4 Using Color in a DrawingArea
297
XtAddCallback (pb, XmNactivateCallback, exit, NULL);
/* Clear button −− wait till DrawingArea is created so we can use
* it to pass as client data.
*/
pb = XtVaCreateManagedWidget ("Clear",
xmPushButtonGadgetClass, main_w,
XmNleftAttachment, XmATTACH_WIDGET,
XmNleftWidget, pb,
XmNtopAttachment, XmATTACH_WIDGET,
XmNtopWidget, rc,
NULL);
sw = XtVaCreateManagedWidget ("scrolled_win",
xmScrolledWindowWidgetClass, main_w,
XmNwidth, 300,
XmNscrollingPolicy, XmAUTOMATIC,
XmNscrollBarDisplayPolicy, XmAS_NEEDED,
XmNtopAttachment, XmATTACH_FORM,
XmNbottomAttachment, XmATTACH_FORM,
XmNleftAttachment, XmATTACH_WIDGET,
XmNleftWidget, rc,
XmNrightAttachment, XmATTACH_FORM,
NULL);
/* Add the "draw" action/function used by the translation table
* parsed by the translations resource below.
*/
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",
xmDrawingAreaWidgetClass, sw,
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);
/* Pushing the clear button clears the drawing area widget */
XtAddCallback (pb, XmNactivateCallback, clear_it, drawing_a);
/* 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)));
/* clear pixmap with white */
set_color (drawing_a, "White");
XFillRectangle (XtDisplay (drawing_a), pixmap, gc, 0, 0, width, height);
XtRealizeWidget (toplevel);
XtAppMainLoop (app);
11 The DrawingArea Widget 11.4 Using Color in a DrawingArea
298
}
/* 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);
XDrawLine (bevent−>display, pixmap, gc, x, y, bevent−>x, bevent−>y);
}
/* freehand is really a bunch of line segements; 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)));
/* this clears the pixmap */
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)));
/* render the newly cleared pixmap onto the window */
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.
11 The DrawingArea Widget 11.4 Using Color in a DrawingArea
299
*/
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);
}
/* callback routine for when any of the color tiles are pressed.
* This general function may also be used to set the global gc's
* color directly. Just provide a widget and a color name.
*/
void
set_color(widget, client_data, call_data)
Widget widget;
XtPointer client_data;
XtPointer call_data;
{
String color = (String) client_data;
Display *dpy = XtDisplay (widget);
Colormap cmap = DefaultColormapOfScreen (XtScreen (widget));
XColor col, unused;
if (!XAllocNamedColor (dpy, cmap, color, &col, &unused)) {
char buf[32];
sprintf (buf, "Can't alloc %s", color);
XtWarning (buf);
return;
}
XSetForeground (dpy, gc, col.pixel);
}
The output of the program in shown in the figure.
11 The DrawingArea Widget 11.4 Using Color in a DrawingArea
300
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.