Subsections

The DrawingArea Widget

   

In this section we will look at how we create and use Motif's DrawingArea Widget which is concerned with the display of graphics. We will also put into practice the Xlib drawing and event scheduling routines met in Chapter 15.

Creating a DrawingArea Widget

 

To create a DrawingArea Widget, use XtVaCreateManagedWidget()  with
xmDrawingAreaWidgetClass  or use XmCreateDrawingArea() . Remember to include the <Xm/DrawingA.h> header file.

There is also a DrawnButton   Widget which is a combination of a DrawingArea and a PushButton. There is a xmDrawnButtonWidgetClass  and definitions are in the <Xm/DrawnB.h> header file.

DrawingArea Resources and Callbacks

 

A DrawingArea will usually be placed inside a container widget --e.g. a Frame or a MainWindow -- and it is frequently scrolled. As such, it usually inherits size and other resources from its parent widget. You can, however, set resources like XmNwidth and XmNheight for the DrawingArea directly. The XmNresizePolicy resource may also need to be set to allow changes in dimension of the DrawingArea in a program. Possible values are:

XmRESIZE_ANY
-- Allow any size of DrawingArea,
XmRESIZE_GROW
-- Allow the DrawingArea to expand only from its initial size,
XmRESIZE_NONE
-- No change in size is allowed.

Three callbacks are associated with this widget :

XmNexposeCallback
  -- This callback occurs when part of the window needs to be redrawn.
XmNresizeCallback
  -- If the dimensions of the window get changed this is called.
XmNinputCallback
  -- If input in the form of a mouse button or key press/release occurs this callback is invoked.

Using DrawingAreas in Practice

 

We are now in a position to draw 2D graphics in Motif. Recall that all graphics drawing is performed at the Xlib level and so we have to attach the higher level motif widgets to the lower level Xlib structures (Chapter 15).

In order to draw anything in a DrawingArea Widget we basically need to do the following:

Basic Drawing -- draw.c

  

The draw.c program illustrates basic Motif/Xlib drawing principles:

 

Fig. 16.1 Output of draw.c

The full program listing is:

#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/DrawingA.h>

/* Prototype functions */

void quit_call(void);
void draw_cbk(Widget , XtPointer ,
         XmDrawingAreaCallbackStruct *);
void load_font(XFontStruct **);

/* XLIB Data */

Display *display;
Screen *screen_ptr;

main(int argc, char *argv[])

{   Widget top_wid, main_w, menu_bar, draw, quit;
    XtAppContext app;
    XGCValues gcv;
    GC gc;
    

    top_wid = XtVaAppInitialize(&app, "Draw", NULL, 0, 
        &argc, argv, NULL,
        XmNwidth,  500,
        XmNheight, 500,
        NULL);
       
    main_w = XtVaCreateManagedWidget("main_window",
        xmMainWindowWidgetClass,   top_wid,
        NULL);
        
    menu_bar = XmCreateMenuBar(main_w, "main_list", 
        NULL, 0);        
    XtManageChild(menu_bar);
      
     /* create quit widget + callback */
        
   quit = XtVaCreateManagedWidget( "Quit",
        xmCascadeButtonWidgetClass, menu_bar,
        XmNmnemonic, 'Q',
        NULL);
        

    XtAddCallback(quit, XmNactivateCallback, quit_call, 
                  NULL);

    /* Create a DrawingArea widget. */
    draw = XtVaCreateWidget("draw",
        xmDrawingAreaWidgetClass, main_w,
        NULL);
        
    /* get XLib Display Screen and Window ID's  for draw */
    
    display = XtDisplay(draw);
    screen_ptr = XtScreen(draw);
       
/* set the DrawingArea as the "work area" of main window */
    XtVaSetValues(main_w,
        XmNmenuBar,    menu_bar,
        XmNworkWindow, draw,
        NULL);
        
    /* add callback for exposure event */
    XtAddCallback(draw, XmNexposeCallback, draw_cbk, NULL);

    /* Create a GC. Attach GC to the DrawingArea's 
       XmNuserData.
       NOTE : This is a useful method to pass data */
    
    gcv.foreground = BlackPixelOfScreen(screen_ptr);
    gc = XCreateGC(display,
        RootWindowOfScreen(screen_ptr), GCForeground, &gcv);
    XtVaSetValues(draw, XmNuserData, gc, NULL);

    XtManageChild(draw);
    XtRealizeWidget(top_wid);
    XtAppMainLoop(app);
}


/* CALL BACKS */

void quit_call()

{   printf("Quitting program\n");
    exit(0);
}

/*  DrawingArea Callback. NOTE: cbk->reason says type of 
    callback event */
 
void
draw_cbk(Widget w, XtPointer data,
         XmDrawingAreaCallbackStruct *cbk)

{   char  str1[25];
    int len1, width1, font_height;
    unsigned int width, height;
    int  x, y, angle1, angle2, x_end, y_end;
    unsigned int line_width = 1;
    int line_style = LineSolid;
    int cap_style = CapRound;
    int join_style = JoinRound;    
    XFontStruct *font_info;
    XEvent *event = cbk->event;
    GC gc;
    Window win = XtWindow(w);


  if (cbk->reason != XmCR_EXPOSE) 
     { /* Should NEVER HAPPEN for this program */
       printf("X is screwed up!!\n");
       exit(0);
     } 
     
   /* get font info */
       
   load_font(&font_info);
       
   font_height = font_info->ascent + font_info->descent;
       
   /* get gc from Drawing Area user data */
       
   XtVaGetValues(w, XmNuserData, &gc, NULL);

  /* DRAW A RECTANGLE */
 
  x = y = 10;
  width = 100;
  height = 50;
 
  XDrawRectangle(display, win, gc, x, y, width, height);

  strcpy(str1,"RECTANGLE"); 
  len1 = strlen(str1);
  y += height + font_height + 1; 
   if ( (x = (x + width/2) - len1/2) < 0 ) x = 10;
  
  XDrawString(display, win, gc, x, y, str1, len1); 

  /* Draw a filled rectangle */

  x = 10; y = 150;
  width = 80;
  height = 70;

  XFillRectangle(display, win, gc, x, y, width, height);

  strcpy(str1,"FILLED RECTANGLE"); 
  len1 = strlen(str1);
  y += height + font_height + 1; 
  if ( (x = (x + width/2) - len1/2) < 0 ) x = 10;
  
  XDrawString(display, win, gc, x, y, str1, len1); 

 
  /* draw an arc */

  x = 200; y = 10;
  width = 80;
  height = 70;
  angle1 = 180 * 64; /* 180 degrees */
  angle2 = 90 * 64; /* 90 degrees */

  XDrawArc(display, win, gc, x, y, width, height, 
           angle1, angle2);

  strcpy(str1,"ARC"); 
  len1 = strlen(str1);
  y += height + font_height + 1; 
  if ( (x = (x + width/2) - len1/2) < 0 ) x = 200;
  
  XDrawString(display, win, gc, x, y, str1, len1);

  /* draw a filled arc */
  
  x = 200; y = 200;
  width = 100;
  height = 50;
  angle1 = 270 * 64; /* 270 degrees */
  angle2 = 180 * 64; /* 180 degrees */

  XFillArc(display, win, gc, x, y, width, height, 
           angle1, angle2);

  strcpy(str1,"FILLED ARC"); 
  len1 = strlen(str1);
  y += height + font_height + 1; 
  if ( (x = (x + width/2) - len1/2) < 0 ) x = 200;
  
  XDrawString(display, win, gc, x, y, str1, len1);

  /* SOLID LINE */

  x = 10; y = 300;
  /* start and end points of line */
  x_end = 200; y_end = y - 30; 
  XDrawLine(display, win, gc, x, y, x_end, y_end);

  strcpy(str1,"SOLID LINE"); 
  len1 = strlen(str1);
  y += font_height + 1; 
  if ( (x = (x + x_end)/2 - len1/2) < 0 ) x = 10;

  XDrawString(display, win, gc, x, y, str1, len1);

  /* DASHED LINE */

  line_style = LineOnOffDash;
  line_width = 2;

  /* set line attributes */

   XSetLineAttributes(display, gc, line_width, line_style, 
                      cap_style, join_style);

  x = 10; y = 350;
  /* start and end points of line */
  x_end = 200; y_end = y - 30; 
  XDrawLine(display, win, gc, x, y, x_end, y_end);

  strcpy(str1,"DASHED LINE"); 
  len1 = strlen(str1);
  y += font_height + 1; 
  if ( (x = (x + x_end)/2 - len1/2) < 0 ) x = 10;

  XDrawString(display, win, gc, x, y, str1, len1);
 }


void load_font(XFontStruct **font_info)

{  char *fontname = "fixed";
    XFontStruct *XLoadQueryFont();

   /* load and get font info structure  */

  if (( *font_info = XLoadQueryFont(display, fontname)) 
        == NULL)
      { /* error - quit early */
         printf("%s: Cannot load  %s font\n", "draw.c", 
                fontname); 
         exit(-1);
      }
}

draw_input1.c -- Input to a DrawingArea

 

The previous program only illustrated one aspect of the DrawingArea widget, i.e. displaying graphics. Another important aspect of this widget is how the widget accepts input. In this Section we will develop a program that illustrate how input is processed in the DrawingArea. We will write a program, draw_input1.c, that highlights some deficiencies in the default event handling capabilities within a practical application.

The draw_input1.c program accepts mouse input in the DrawingArea. It allows the user to select a colour (as we have seen previously) and then draw a variable size rectangle that is shaded with the chosen colour. A clear DrawingArea facility is also provided.

 

Fig. 16.2 Output of draw_input1.c

In order to achieve a practical and intuitive manner of user interaction we will need to detect 3 different mouse events (all events described below refer to the left mouse button):

 

Fig. 16.3 Silhouette outline of rectangle during input ( draw_input1.c)

To detect mouse clicks up and down we can use the XmNinputCallback  resource. However, the default setting of callback resources in a DrawingArea Widget does not allow for mouse motion to be detected as we would like. We therefore have to override the default callback options.

Every Widget has a Translation Table   (Section 4.8.2) that contains a list of events that it can receive and actions that it is to take upon receipt of an event. We basically have to create a new translation table to achieve our desired interaction described above.

We have already defined the translation table format in Section 4.8.2. A translation table consists of events like the below excerpt of the default DrawingArea translation:

   ..............
   <Btn1Down>:    DrawingAreaInput() ManagerGadgetArm() 
   <Btn1Up>:      DrawingAreaInput() ManagerGadgetActivate()
   <Btn1Motion>:  ManagerGadgetButtonMotion() 
   ..............

Our particular problem is that button motion does not get passed to the DrawingAreaInput() function that notifies the program of an input event.

To create a new translation table, for our purpose, we simply include the functions and events we need. In this case:

 <Btn1Down>:   draw_cbk(down) ManagerGadgetArm() 
 <Btn1Up>:     draw_cbk(up) ManagerGadgetActivate()
 <Btn1Motion>: draw_cbk(motion) ManagerGadgetButtonMotion()

where draw_cbk() is our callback that performs the drawing. We use the same callback to detect each mouse button down, up or motion action. This is achieved by sending a message to the callback that identifies each action. The arm, activate and motion gadget manager functions control the (default) display of an event action.

In a motif program, we set up our translation table in a String structure and use the XtParseTranslationTable(String*)  to attach a translation table to the XmNtranslations  resource. We must also register the callback with the actions associated with the translation events. We use the XtAppAddActions()  function to do this.

For the above example we create the String as follows:

String translations = 
"<Btn1Motion>: draw_cbk(motion) 
               ManagerGadgetButtonMotion() \n\
<Btn1Down>: draw_cbk(down) ManagerGadgetArm() \n\
<Btn1Up>: draw_cbk(up) ManagerGadgetActivate()";

and register the callback and create a DrawingArea widget with the correct actions with the following code:

    actions.string = "draw_cbk";
    actions.proc = draw_cbk;
    XtAppAddActions(app, &actions, 1);
    
    draw = XtVaCreateWidget("draw",
     xmDrawingAreaWidgetClass, main_w,
     XmNtranslations, XtParseTranslationTable(translations),
     XmNbackground, WhitePixelOfScreen(XtScreen(main_w)),
     NULL);

The Callback function would be prototyped by:



draw_cbk(Widget w, XButtonEvent *event, String *args, int *num_args).



On calling the function, we simply inspect the args[0] String to see if an up, down or motion event has occurred and take the approptriate actions described below:

Mouse Down
-- Simply remember the (x,y) coordinates of the mouse. These are found in the x,y elements of the event structure.
Mouse Motion
-- Draw a dashed line silhouette of the box whose current size is determined by mouse down (x,y) and current mouse position. Note: We set the Graphics Context Logical Function to GXinvert which means that pixels simply get inverted. We must invert them once more to get them back to their original state before we redraw again at another mouse position.
Mouse Up
-- We finally draw our rectangle with the desired colour.

The complete program listing of draw_input1.c is :

#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/DrawingA.h>

/* Prototype callbacks */

void quit_call(void);
void clear_call(void);
void colour_call(Widget , int); 
void draw_cbk(Widget , XButtonEvent *, 
               String *, int *);

GC gc;
XGCValues gcv;
Widget draw;
String colours[] = { "Black",  "Red", "Green", "Blue", 
                     "Grey", "White"};
long int fill_pixel = 1; /* stores current colour 
                         of fill - black default */
Display *display; /* xlib id of display */
Colormap cmap;

main(int argc, char *argv[])

{   Widget top_wid, main_w, menu_bar, quit, clear, colour;
    XtAppContext app;
    XmString  quits, clears, colourss, red, green, 
              blue, black, grey, white;
    XtActionsRec actions;
    
   
    String translations = 
"<Btn1Motion>: draw_cbk(motion) 
               ManagerGadgetButtonMotion() \n\
<Btn1Down>: draw_cbk(down) ManagerGadgetArm() \n\
<Btn1Up>: draw_cbk(up) ManagerGadgetActivate()";

    top_wid = XtVaAppInitialize(&app, "Draw", NULL, 0, 
        &argc, argv, NULL,
        XmNwidth,  500,
        XmNheight, 500,
        NULL);
   
    main_w = XtVaCreateManagedWidget("main_window",
        xmMainWindowWidgetClass,   top_wid,
        XmNwidth, 500,         
        XmNheight, 500,
        NULL);
        
    /* Create a simple MenuBar that contains three menus */
    quits = XmStringCreateLocalized("Quit");
    clears = XmStringCreateLocalized("Clear");
    colourss = XmStringCreateLocalized("Colour");
      
    menu_bar = XmVaCreateSimpleMenuBar(main_w, "main_list",
        XmVaCASCADEBUTTON, quits, 'Q',
        XmVaCASCADEBUTTON, clears, 'C',
        XmVaCASCADEBUTTON, colourss, 'o',
        NULL); 
               
    XtManageChild(menu_bar);
    

/* First menu is quit menu -- callback is quit_call() */
    
    XmVaCreateSimplePulldownMenu(menu_bar, "quit_menu", 0, 
        quit_call, XmVaPUSHBUTTON, quits, 'Q', NULL, NULL,
        NULL);
    XmStringFree(quits);
    
/* Second menu is clear menu -- callback is clear_call() */
    
    XmVaCreateSimplePulldownMenu(menu_bar, "clear_menu", 1, 
        clear_call, XmVaPUSHBUTTON, clears, 'C', NULL, NULL,
        NULL);
    XmStringFree(clears);
   
     /* create colour pull down menu */
    
    black = XmStringCreateLocalized(colours[0]);
    red = XmStringCreateLocalized(colours[1]);
    green = XmStringCreateLocalized(colours[2]);
    blue = XmStringCreateLocalized(colours[3]);
    grey = XmStringCreateLocalized(colours[4]);
    white = XmStringCreateLocalized(colours[5]);
 
    colour = XmVaCreateSimplePulldownMenu(menu_bar, 
        "edit_menu", 2, colour_call,
        XmVaRADIOBUTTON, black, 'k', NULL, NULL,
        XmVaRADIOBUTTON, red, 'R', NULL, NULL,
        XmVaRADIOBUTTON, green, 'G', NULL, NULL,
        XmVaRADIOBUTTON, blue, 'B', NULL, NULL,
        XmVaRADIOBUTTON, grey, 'e', NULL, NULL,
        XmVaRADIOBUTTON, white, 'W', NULL, NULL,
        XmNradioBehavior, True,     
        /* RowColumn resources to enforce */
        XmNradioAlwaysOne, True,    
        /* radio behavior in Menu */
        NULL);

    XmStringFree(black);
    XmStringFree(red);
    XmStringFree(green);
    XmStringFree(blue);
    XmStringFree(grey);
    XmStringFree(white);


    /* Create a DrawingArea widget. */
    /* make new actions */
    
    actions.string = "draw_cbk";
    actions.proc = draw_cbk;
    XtAppAddActions(app, &actions, 1);
    
    draw = XtVaCreateWidget("draw",
     xmDrawingAreaWidgetClass, main_w,
     XmNtranslations, XtParseTranslationTable(translations),
     XmNbackground, WhitePixelOfScreen(XtScreen(main_w)),
     NULL);

        
    cmap = DefaultColormapOfScreen(XtScreen(draw));
    display = XtDisplay(draw);
    
/* set the DrawingArea as the "work area" of main window */
    XtVaSetValues(main_w,
        XmNmenuBar,    menu_bar,
        XmNworkWindow, draw,
        NULL);


/* Create a GC. Attach GC to DrawingArea's XmNuserData. */
    gcv.foreground = BlackPixelOfScreen(XtScreen(draw));
    gc = XCreateGC(XtDisplay(draw),
        RootWindowOfScreen(XtScreen(draw)), 
        GCForeground, &gcv);

    XtManageChild(draw);
    XtRealizeWidget(top_wid);
    XtAppMainLoop(app);
}

/* CALL BACKS */

void quit_call()

{   printf("Quitting program\n");
    exit(0);
}

void clear_call() /* clear work area */

{ XClearWindow(display, XtWindow(draw)); 
}


/* called from any of the "Colour" menu items.  
   Change the colour of the label widget. 
   Note: we have to use dynamic setting with setargs()..
 */

void
colour_call(Widget w, int item_no)

  /*  w -- menu item that was selected */
  /*  item_no --- the index into the menu */

{
    int n =0;
    Arg args[1];
    
    XColor xcolour, spare; /* xlib colour struct */
        
    if (XAllocNamedColor(display, cmap, colours[item_no], 
        &xcolour, &spare) == 0)
       return;
    
    /* remember new colour */        
    fill_pixel = xcolour.pixel;     
}

/*  DrawingArea Callback.*/ 
 
void  draw_cbk(Widget w, XButtonEvent *event, 
               String *args, int *num_args)

{   static Position x, y, last_x, last_y;
    Position width, height;
    
    int line_style;
    unsigned int line_width = 1;
    int cap_style = CapRound;
    int join_style = JoinRound;    
  
    if (strcmp(args[0], "down") == 0) 
          {  /* anchor initial point (save its value) */
            x = event->x;
            y = event->y;
          } 
     else 
       if (strcmp(args[0], "motion") == 0) 
          { /* draw "ghost" box to show where it could go */
             /* undraw last box */
             
             line_style = LineOnOffDash;
            
             /* set line attributes */

             XSetLineAttributes(event->display, gc,  
              line_width, line_style, cap_style, join_style);
             
             gcv.foreground 
               = WhitePixelOfScreen(XtScreen(w));

             XSetForeground(event->display, gc, 
                            gcv.foreground);
             
             XSetFunction(event->display, gc, GXinvert);

           
            XDrawLine(event->display, event->window, gc,  
                       x, y, last_x, y);
            XDrawLine(event->display, event->window, gc, 
                       last_x, y, last_x, last_y);
            XDrawLine(event->display, event->window, gc, 
                       last_x, last_y, x, last_y);
            XDrawLine(event->display, event->window, gc,  
                       x, last_y, x, y);
       
            /* Draw New Box */
            gcv.foreground 
              = BlackPixelOfScreen(XtScreen(w));
            XSetForeground(event->display, gc, 
                   gcv.foreground);
          
            XDrawLine(event->display, event->window, gc, 
                      x, y, event->x, y);
            XDrawLine(event->display, event->window, gc, 
                      event->x, y, event->x, event->y);
            XDrawLine(event->display, event->window, gc, 
                      event->x, event->y, x, event->y);
            XDrawLine(event->display, event->window, gc,  
                      x, event->y, x, y);
          }

        else       
         if (strcmp(args[0], "up") == 0)             
          { /* draw full line */
          
             XSetFunction(event->display, gc, GXcopy);
          
             line_style = LineSolid;
             
             /* set line attributes */

             XSetLineAttributes(event->display, gc, 
        line_width, line_style, cap_style, join_style);
             
             XSetForeground(event->display, gc, fill_pixel);
          
            XDrawLine(event->display, event->window, gc, 
                      x, y, event->x, y);
            XDrawLine(event->display, event->window, gc, 
                      event->x, y, event->x, event->y);
            XDrawLine(event->display, event->window, gc, 
                      event->x, event->y, x, event->y);
            XDrawLine(event->display, event->window, gc, 
                      x, event->y, x, y);   
        
            width = event->x - x;
            height = event->y - y;
            XFillRectangle(event->display, event->window, 
                           gc, x, y, width, height);
          }
          last_x = event->x;
          last_y = event->y;
              
}

Drawing to a pixmap -- draw_input2.c

  

One problem the draw_input1.c program has is that if the window was covered and then exposed the picture would not redraw itself (Section 15.6). To see this for yourself run the draw_input1.c obscure the MainWindow with another window and then click on the draw_input1.c frame to bring it to the foreground and note the appearance of the window.

Indeed, the best method to store the picture we draw is via a Pixmap . Since the drawing in this application is an interactive process it would be very difficult to redraw the picture unless we used Pixmaps. There is no way that we could predict how the user would use the application and storing each drawing stroke would become complex. The best approach is to store the drawing data in a Pixmap by writing directly to a Pixmap. Obtaining an immediate visual feedback is also important (the user needs to see what he has drawn) so we also draw directly to the display when data is being input. When an expose event is detected all we need to do is remap the pixmap to the window.

The draw_input2.c program does exactly the same task as draw_input1.c but draws to a pixmap which can be remapped to the window upon an exposure.

The major differences between the programs are:

The draw_input2.c program listing is as follows: 

#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/CascadeB.h>
#include <Xm/DrawingA.h>

/* Prototype callbacks */

void quit_call(void);
void clear_call(void);
void colour_call(Widget , int);
void draw_cbk(Widget , XButtonEvent *, String *, int *);
void expose(Widget , XtPointer , 
            XmDrawingAreaCallbackStruct *);

GC gc;
XGCValues gcv;
Widget draw;
Display *display; /* xlib id of display */
Screen *screen;
Colormap cmap;
Pixmap pix;
Dimension width, height; /* store size of pixmap */
String colours[] = { "Black",  "Red", "Green", "Blue", 
                     "Grey", "White"};
long int fill_pixel = 1;  /* stores current colour of fill 
                             - black default */

main(int argc, char *argv[])

{   Widget top_wid, main_w, menu_bar, quit, clear, colour;
    XtAppContext app;
    XmString  quits, clears, colourss, red, green, blue, 
              black, grey, white;
    XtActionsRec actions;
    
    
    String translations = 
        "<Btn1Motion>: draw_cbk(motion) ManagerGadgetButtonMotion() \n\
         <Btn1Down>: draw_cbk(down) ManagerGadgetArm() \n\
         <Btn1Up>: draw_cbk(up) ManagerGadgetActivate()";

    top_wid = XtVaAppInitialize(&app, "Draw", NULL, 0, 
        &argc, argv, NULL,
        NULL);
     
    main_w = XtVaCreateManagedWidget("main_window",
        xmMainWindowWidgetClass,   top_wid,
        NULL);
        
    /* Create a simple MenuBar that contains three menus */
    quits = XmStringCreateLocalized("Quit");
    clears = XmStringCreateLocalized("Clear");
    colourss = XmStringCreateLocalized("Colour");
        
    menu_bar = XmVaCreateSimpleMenuBar(main_w, "main_list",
        XmVaCASCADEBUTTON, quits, 'Q',
        XmVaCASCADEBUTTON, clears, 'C',
        XmVaCASCADEBUTTON, colourss, 'o',
        NULL); 
               
    XtManageChild(menu_bar);
    
   
    /* First menu is the quit menu 
         -- callback is quit_call() */
    
    XmVaCreateSimplePulldownMenu(menu_bar, "quit_menu", 0, 
        quit_call,
        XmVaPUSHBUTTON, quits, 'Q', NULL, NULL,
        NULL);
    XmStringFree(quits);
    
    /* Second menu is the clear menu 
          -- callback is clear_call() */
    
    XmVaCreateSimplePulldownMenu(menu_bar, "clear_menu", 1, 
        clear_call,
        XmVaPUSHBUTTON, clears, 'C', NULL, NULL,
        NULL);
    XmStringFree(clears);
    
        
    /* create colour pull down menu */
    
    black = XmStringCreateLocalized(colours[0]);
    red = XmStringCreateLocalized(colours[1]);
    green = XmStringCreateLocalized(colours[2]);
    blue = XmStringCreateLocalized(colours[3]);
    grey = XmStringCreateLocalized(colours[4]);
    white = XmStringCreateLocalized(colours[5]);
    
    colour = XmVaCreateSimplePulldownMenu(menu_bar, "edit_menu", 2, 
       colour_call,
        XmVaRADIOBUTTON, black, 'k', NULL, NULL,
        XmVaRADIOBUTTON, red, 'R', NULL, NULL,
        XmVaRADIOBUTTON, green, 'G', NULL, NULL,
        XmVaRADIOBUTTON, blue, 'B', NULL, NULL,
        XmVaRADIOBUTTON, grey, 'e', NULL, NULL,
        XmVaRADIOBUTTON, white, 'W', NULL, NULL,
        XmNradioBehavior, True,  /* RowColumn resources set */
        XmNradioAlwaysOne, True, /* radio behavior in Menu */
        NULL);
    XmStringFree(black);
    XmStringFree(red);
    XmStringFree(green);
    XmStringFree(blue);
    XmStringFree(grey);
    XmStringFree(white);

    
    /* Create a DrawingArea widget. */
    
    /* make new actions */
    
    actions.string = "draw_cbk";
    actions.proc = draw_cbk;
    XtAppAddActions(app, &actions, 1);
    
    draw = XtVaCreateWidget("draw",
        xmDrawingAreaWidgetClass, main_w,
        XmNtranslations, XtParseTranslationTable(translations),
        XmNbackground, WhitePixelOfScreen(XtScreen(main_w)),
        XmNwidth, 500,         
        XmNheight, 500,
        NULL);
        
    cmap = DefaultColormapOfScreen(XtScreen(draw));
    display = XtDisplay(draw);
    screen = XtScreen(draw);
    
    /* Create a GC. Attach GC to the DrawingArea's XmNuserData. */
    gcv.foreground = BlackPixelOfScreen(XtScreen(draw));
    gc = XCreateGC(XtDisplay(draw),
        RootWindowOfScreen(XtScreen(draw)), GCForeground, &gcv);
    
    /* get pixmap of DrawingArea */
    XtVaGetValues(draw, XmNwidth, &width, XmNheight, &height, NULL);
    
    pix = XCreatePixmap(display, RootWindowOfScreen(screen), 
                        width, height, DefaultDepthOfScreen(screen));
    
    /* initial white pixmap */
    XSetForeground(XtDisplay(draw), gc,
        WhitePixelOfScreen(XtScreen(draw)));

    XFillRectangle(display, pix, gc, 0, 0, width, height);
    
    /* reset gc with current colour */
    XSetForeground(display, gc, fill_pixel);
           
    /* set the DrawingArea as the "work area" of the main window */
    XtVaSetValues(main_w,
        XmNmenuBar,    menu_bar,
        XmNworkWindow, draw,
        NULL);
        
    /* add callback for exposure event */
    XtAddCallback(draw, XmNexposeCallback, expose, NULL);

    XtManageChild(draw);
    XtRealizeWidget(top_wid);
    XtAppMainLoop(app);
}

/* CALL BACKS */

void quit_call()

{   printf("Quitting program\n");
    exit(0);
}

void clear_call(Widget w, int item_no) /* clear work area */

{ /* clear pixmap with white */
    XSetForeground(XtDisplay(draw), gc,
        WhitePixelOfScreen(XtScreen(draw)));
        
    XFillRectangle(display, pix, gc, 0, 0, width, height);
    
    /* reset gc with current colour */
    XSetForeground(display, gc, fill_pixel);
    
    /* copy pixmap to window of drawing area */
    
    XCopyArea(display, pix, XtWindow(draw), gc,
        0, 0, width, height, 0, 0);
}

/* expose is called whenever all or portions of the drawing area is
   exposed.  
 */

void
expose(Widget draw, XtPointer client_data, 
       XmDrawingAreaCallbackStruct *cbk)

{   XCopyArea(cbk->event->xexpose.display, pix, cbk->window, gc,
        0, 0, width, height, 0, 0);
}


/* called from any of the "Colour" menu items.  
   Change the colour of the label widget. 
   Note: we have to use dynamic setting with setargs().
*/

void
colour_call(Widget w, int item_no)
     
/* w = menu item that was selected 
   item_no = the index into the menu 
*/

{
    int n =0;
    Arg args[1];
    
    XColor xcolour, spare; /* xlib color struct */
        
    
    if (XAllocNamedColor(display, cmap, colours[item_no], 
                         &xcolour, &spare) == 0)
       return;
    
    /* remember new colour */        
    fill_pixel = xcolour.pixel;     
}


/*  DrawingArea Callback */

 
void  draw_cbk(Widget w, XButtonEvent *event, 
               String *args, int *num_args)

{
    static Position x, y, last_x, last_y;
    Position width, height;
    
    int line_style;
    unsigned int line_width = 1;
    int cap_style = CapRound;
    int join_style = JoinRound;    

    
   
        if (strcmp(args[0], "down") == 0) 
          {  /* anchor initial point (i.e., save its value) */
            x = event->x;
            y = event->y;
            
          } 
       else 
         if (strcmp(args[0], "motion") == 0) 
          { /* draw "ghost" box to show where it could go */
          
             /* undraw last box */
             
             line_style = LineOnOffDash;
             
             /* set line attributes */

             XSetLineAttributes(event->display, gc, line_width, 
                                line_style, cap_style, join_style);
             
             gcv.foreground = WhitePixelOfScreen(XtScreen(w));
             XSetForeground(event->display, gc, gcv.foreground);
             
             XSetFunction(event->display, gc, GXinvert);

             
            XDrawLine(event->display, event->window, gc, 
                      x, y, last_x, y);
            XDrawLine(event->display, event->window, gc, 
                      last_x, y, last_x, last_y);
            XDrawLine(event->display, event->window, gc, 
                      last_x, last_y, x, last_y);
            XDrawLine(event->display, event->window, gc, 
                      x, last_y, x, y);
          
            /* Draw New Box */
                        
            gcv.foreground = BlackPixelOfScreen(XtScreen(w));
            XSetForeground(event->display, gc, gcv.foreground);
          
            XDrawLine(event->display, event->window, gc, 
                      x, y, event->x, y);
            XDrawLine(event->display, event->window, gc, 
                      event->x, y, event->x, event->y);
            XDrawLine(event->display, event->window, gc, 
                      event->x, event->y, x, event->y);
            XDrawLine(event->display, event->window, gc, 
                      x, event->y, x, y);
            
          }
        else       
         if (strcmp(args[0], "up") == 0)             
          { /* draw full line; get GC and use in XDrawLine() */
          
             XSetFunction(event->display, gc, GXcopy);
          
             line_style = LineSolid;
             
             /* set line attributes */

             XSetLineAttributes(event->display, gc, 
               line_width, line_style, cap_style, join_style);
             
             XSetForeground(event->display, gc, fill_pixel);
             
             XDrawLine(event->display, event->window, gc, 
                       x, y, event->x, y);
             XDrawLine(event->display, event->window, gc, 
                       event->x, y, event->x, event->y); 
             XDrawLine(event->display, event->window, gc, 
                       event->x, event->y, x, event->y);
             XDrawLine(event->display, event->window, gc, 
                       x, event->y, x, y);   
                      
            width = event->x - x;
            height = event->y - y;
            XFillRectangle(event->display, event->window, gc, 
                           x, y, width, height);
            
            /* only need to draw final selection to pixmap */
            
            XDrawLine(event->display, pix, gc, 
                      x, y, event->x, y);
            XDrawLine(event->display, pix, gc, 
                      event->x, y, event->x, event->y);
            XDrawLine(event->display, pix, gc, 
                      event->x, event->y, x, event->y);
            XDrawLine(event->display, pix, gc, 
                      x, event->y, x, y);   
                      
            XFillRectangle(event->display, pix, gc, 
                           x, y, width, height);
          }
          
          last_x = event->x;
          last_y = event->y;
              
}


Dave Marshall
1/5/1999