Chapter 4. An Example Application

This chapter describes a complete application, in several iterations. First, it shows a simple version of the program, a bitmap editor, as it would be written assuming the existence of a BitmapEdit widget (which is actually developed in Chapter 6, Inside a Widget). Then, two refined versions are developed, each demonstrating additional Toolkit programming techniques. Finally, the same application is shown as it would be written if the bitmap editor were implemented in an application window rather than with the BitmapEdit widget.

Enough of these trivial programs! Now for an (almost) real application. This chapter describes the development of a bitmap editor. Although it is simple, it can be easily extended to be quite powerful without any new techniques.

We will show several versions of xbitmap, beginning with a simple version that assumes the existence of a BitmapEdit widget (which will actually be developed in Chapter 6, Inside a Widget, and Chapter 7, Basic Widget Methods). Subsequent versions add two widgets that display the bitmap in small scale (one normal, one reverse video), and finally implement the same application without the use of the specialized BitmapEdit widget.

These examples will demonstrate the techniques described in Chapter 2, Introduction to the X Toolkit and Motif, and Chapter 3, More Techniques for Using Widgets, as they would appear in a real application, and will bring up a number of new topics:

Many applications have at least one custom window for which no existing widget will suffice. This chapter demonstrates several of the ways to implement such a window, and describes the tradeoffs between the different options. Features can be added from the application code by building on a Motif DrawingArea widget, or creating a Core or Primitive widget to draw into. They can also be implemented by creating a custom widget. By the end of Chapters 6 and 7, you will have seen how to implement this application entirely with existing widgets and application code, how to implement it using custom widgets, and how to implement a mixture of the two.

xbitmap1: Bitmap Editor Using a BitmapEdit Widget

Most application programming is done entirely using widgets written by someone else. xbitmap1 is such an application.

The screen appearance of xbitmap1 is shown in Figure 4-1. As usual, it is a good idea to compile and run each example as it is discussed.[35]

Figure 4-1. xbitmap1: how it looks on the screen


The BitmapEdit widget lets you set bits in the visible bitmap by clicking the first pointer button or dragging the pointer with the first button held down, lets you erase bits using the second button, or lets you toggle bits using the third button. The File menu contains a Print button which simply prints on the standard output an array of ones and zeroes representing the set and unset bits in the bitmap. (Code to read and write standard X11 bitmap files is added in a later version.) The File menu also contains a Quit button.

xbitmap1 is implemented by adding a small amount of code to xmainwindow, described in Chapter 3, More Techniques for Using Widgets. We replaced the Frame widget with a BitmapEdit widget, and added the Print button to the menu. Of course, a callback for the print button also has to be added.

The code for xbitmap1 is shown in Example 4-1. The only new technique shown in this example is the use of the public function BitmapEditGetArray defined by the BitmapEdit widget. BitmapEditGetArray gets a character array which represents the contents of the bitmap being edited, and the dimensions of the bitmap. This information is necessary for the application to print out the bitmap (or write it to a file). Although these values are widget resources that can be queried with XtGetValues(), this function makes it more convenient. Several Motif widgets also provide public functions to make it more convenient to set or get resources or manipulate internal widget data. All Motif public functions begin with Xm.

Example 4-1. xbitmap1: complete code

/*
 *  xbitmap1.c - bitmap in main window with help and quit
 */
/*  So that we can use fprintf: */
#include <stdio.h>
/* Standard Toolkit include files: */
#include <Xm/Xm.h>
/* Public include files for widgets used in this file.  */
#include <Xm/PushB.h>    /* push button */
#include <Xm/MainW.h>    /* main window */
#include <Xm/MessageB.h> /* message box */
#include <Xm/RowColum.h> /* row column (for menus) */
#include <Xm/CascadeB.h> /* cascade button */
#include "BitmapEdit.h"
/*
 * The printout routine prints an array of 1s and 0s representing the
 * contents of the bitmap.  This data can be processed into any
 * desired form, including standard X11 bitmap file format.
 */
/* ARGSUSED */
static void
PrintOut(widget, client_data, call_data)
Widget widget;
XtPointer client_data;    /* cast to bigBitmap */
XtPointer call_data;      /* unused */
{
    Widget bigBitmap = (Widget) client_data;
    int x, y;
    int width_in_cells, height_in_cells;
    char *cell;
    cell = BitmapEditGetArray(bigBitmap, &width_in_cells,
             &height_in_cells);
    (void) putchar('\n');
    for (y = 0; y < height_in_cells; y++) {
        for (x = 0; x < width_in_cells; x++)
            (void) putchar(cell[x + y * width_in_cells] ? '1' : '0');
        (void) putchar('\n');
    }
    (void) putchar('\n');
}
/*
 * callback to pop up help dialog widget
 */
/*ARGSUSED*/
void ShowHelp(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    Widget dialog = (Widget) client_data;
    XtManageChild(dialog);
}
/*
 * quit button callback function
 */
/*ARGSUSED*/
void Quit(w, client_data, call_data)
Widget w;
XtPointer client_data, call_data;
{
    exit(0);
}
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel, mainWindow, menuBar;
    Widget fileButton, fileMenu, quit, helpButton, helpMenu,
              help, helpBox;
    Widget temp;
    Widget bigBitmap, output;
    /* never call a Widget variable "exit"! */
    extern exit();
    static XrmOptionDescRec table[] = {
        {"-pw",           "*pixmapWidthInCells",  XrmoptionSepArg, NULL},
        {"-pixmapwidth",  "*pixmapWidthInCells",  XrmoptionSepArg, NULL},
        {"-ph",           "*pixmapHeightInCells", XrmoptionSepArg, NULL},
        {"-pixmapheight", "*pixmapHeightInCells", XrmoptionSepArg, NULL},
        {"-cellsize",     "*cellSizeInPixels",    XrmoptionSepArg, NULL},
    };
	XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    topLevel = XtVaAppInitialize( &app_context, "XBitmap1",
            table, XtNumber(table), &argc, argv, NULL, NULL);
    /* create main window */
    mainWindow = XtVaCreateManagedWidget( "mainWindow",
            xmMainWindowWidgetClass, topLevel, NULL);
    /* create menu bar along top inside of main window */
    menuBar = XmCreateMenuBar( mainWindow, "menuBar",
            NULL, 0);
    XtManageChild(menuBar);
    bigBitmap = XtVaCreateManagedWidget("bigBitmap",
            bitmapEditWidgetClass, mainWindow,
            NULL);
    /*  Set MainWindow areas */
    XmMainWindowSetAreas (mainWindow, menuBar, NULL, NULL, NULL,
            bigBitmap);
    /*
     *  CREATE FILE MENU AND CHILDREN
     */
    /* create button that will pop up the menu */
    fileButton = XtVaCreateManagedWidget("fileButton",
            xmCascadeButtonWidgetClass, menuBar, NULL);
    /* create menu (really Shell widget-RowColumn widget combo) */
    fileMenu = XmCreatePulldownMenu( menuBar,
            "fileMenu", NULL, 0);
    /*
     *  CREATE BUTTON TO OUTPUT BITMAP
     */
    /* create button that will pop up the menu */
    output = XtVaCreateManagedWidget( "output",
            xmPushButtonWidgetClass, fileMenu, NULL);
    XtAddCallback(output, XmNactivateCallback, PrintOut, bigBitmap);
    /* create the quit button last so it's last in the menu */
    quit = XtVaCreateManagedWidget( "quit",
            xmPushButtonWidgetClass, fileMenu, NULL);
    /*
     * Specify which menu fileButton will pop up.
     */
    XtVaSetValues(fileButton,
            XmNsubMenuId, fileMenu,
            NULL);
    /* arrange for quit button to call function that exits. */
    XtAddCallback(quit, XmNactivateCallback, Quit, 0);
    /*
     *  CREATE HELP BUTTON AND BOX
     */
    /* create button that will bring up help popup */
    helpButton = XtVaCreateManagedWidget( "helpButton",
            xmCascadeButtonWidgetClass, menuBar, NULL);
    /* tell menuBar which is the help button
     * (will be specially positioned. */
    XtVaSetValues(menuBar,
            XmNmenuHelpWidget, helpButton,
            NULL);
    helpMenu = XmCreatePulldownMenu(
            menuBar,    /* parent widget */
            "helpMenu", /* widget name */
            NULL,       /* terminate argument list (none needed) */
            0);         /* terminate argument list (none needed) */
    /* create help button in the menu */
    help = XtVaCreateManagedWidget(
            "helpMenu"  /* widget name */
            XmPushButtonWidgetClass,  /* widget name */
            helpMenu,   /* parent widget */
            NULL);      /* terminate argument list (none needed) */
    /* Specify which menu helpButton will pop up */
    XtVaSetValues(helpButton,
            XmNsubMenuId, helpMenu,
            NULL);
    /* create popup that will contain help */
    helpBox = XmCreateMessageDialog( help,
            "helpBox", NULL, 0);
    temp = XmMessageBoxGetChild (helpBox, XmDIALOG_CANCEL_BUTTON);
    XtUnmanageChild (temp);
    temp = XmMessageBoxGetChild (helpBox, XmDIALOG_HELP_BUTTON);
    XtUnmanageChild (temp);
    /* arrange for help button to pop up helpBox */
    XtAddCallback(help, XmNactivateCallback, ShowHelp, helpBox);
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}

You should recognize the command-line options table as the one described in Section 3.7.2, "Defining Your Own Command-line Options." If there is anything you don't understand in Example 4-1, review Chapter 3, More Techniques for Using Widgets.

Adding a Print button to the menu is a simple matter of creating another PushButton as a child of fileMenu.

The Printout callback function gets the array of ones and zeroes from the BitmapEdit widget and prints them to the standard output. Some widget public functions like BitmapEditGetArray are provided simply because they are more convenient to use than calling XtSetValues() or XtGetValues(). Others are supplied by a widget class that wants certain data to be readable or writable by the application, but for some reason does not want the data both readable and writable as would be the case if it were a resource. Sometimes a widget class has features that it wants controllable from the application but never user-customizable (because they are meaningless at startup, for example), and therefore it provides a function for setting them.

XBitmap1 App-defaults File

Example 4-2 shows the app-defaults file for xbitmap1. This app-defaults file is identical to XMainWindow with the addition of resource settings to set an appropriate help message, the label Print Array for the new menu button, the size in cells of the bitmap, and the size in pixels of each cell in the bitmap.

Example 4-2. XBitmap1: app-defaults file

! BitmapEdit resources
*cellSizeInPixels:           30
*pixmapWidthInCells:         30
*pixmapHeightInCells:        30
! fonts
*fontList:                   variable
! strings
*helpButton.labelString:     Help
*help.labelString:           Editing Bitmaps
*quit.labelString:           Quit
*output.labelString:         Print Array
*fileButton.labelString:     File
!
*helpBox.messageString:      To set bits, press or drag \n\
the first mouse button. \n\
To clear bits, use the second mouse button. \n\
To toggle bits, use the third mouse button.
! The following used if the window manager titles the dialog:
! mwm does, twm doesn't
*helpBox.dialogTitle:        Help
! Turn on scrollbars
*XmMainWindow.scrollingPolicy:   XmAUTOMATIC
*XmMainWindow.width:         300
*XmMainWindow.height:        300


Note that by default the MainWindow will make itself big enough for only a few bitmap cells to be visible. The resource settings for the width and height of MainWindow make sure that the application has a reasonable initial size.

xbitmap2: Adding Graphics to Display the Bitmap

When you have some graphics to draw, you need a widget that is designed to allow you to draw into it; the DrawingArea widget.

In addition to displaying the bitmap in blown-up, editable form, a bitmap editor should display the bitmap in true scale, how the bitmap really looks when displayed on the screen. It is customary to present this bitmap in both normal and reverse video, and update it every time the user toggles a cell in the enlarged bitmap. Figure 4-2 shows the appearance of the application with this feature added.[36]

Figure 4-2. xbitmap2: true-scale normal and reverse bitmaps added

There are three ways to implement the small pixmap widgets. (All of them would look the same to the user.) One approach is to use the Label widget, which can display a pixmap instead of a string. However, it is then difficult to make the Label widget redraw the pixmap each time a cell is toggled. This is difficult because we are changing the contents of the pixmap (by drawing into it) without the Label widget's knowledge; therefore the widget does not know to redraw itself. The only obvious way to tell Label to redraw is to clear the widget's window; this generates an Expose event and forces the widget to redraw itself. However, this results in a flashing effect that is unacceptable. You can avoid flashing by synthesizing an Expose event (creating an XExposeEvent structure and filling it with values) and sending it to the Label widget using XSendEvent(). This is a bit of a kludge but it works. If Label provided a public function called XmLabelRedrawPixmap, we could more cleanly correct the flashing bug. Although adding this function to the widget code would be easy, we could no longer call the remaining widget class Label; we would be creating a new widget class.

Another way to display the small pixmaps is to use a DrawingArea widget, which is an empty widget that provides callbacks in which you can place drawing code. DrawingArea is useful for building simple custom windows that don't have complicated graphics or user input. This technique is implemented in xbitmap2.

The third possibility is to draw into a Core or Primitive widget. This would use code very similar to that in xbitmap2, but would link the application code with the widget using actions and translations instead of with callbacks. We will be demonstrating this technique in xbitmap3 as an introduction to translations and actions.

Exposure Strategy

The redrawing technique used in xbitmap2 works as follows. Two pixmaps (off-screen drawable areas) are created to record the current state of each bitmap. Whenever a cell is toggled, a pixel in each pixmap is changed by drawing into each pixmap. Then each entire pixmap is copied into the corresponding widget. The same routine that copies from pixmap to widget is used whenever the widgets become exposed. Figure 4-3 illustrates this technique.

Figure 4-3. Application draws into Pixmap and copies it to widget window

In the actual code, two single-plane pixmaps are created during application startup, the same size as the bitmap displayed in the BitmapEdit widget. Each of the two bitmap display areas is implemented with a DrawingArea widget. DrawingArea provides a callback resource called XmNexposeCallback. The application calls XtAddCallback() to register a function with each of the two widgets for the XmNexposeCallback resource. This function, called RedrawSmallPicture, copies from the pixmaps into the widget's window. This function takes care of redrawing the widgets whenever exposure occurs, and it also can be called whenever a cell is toggled. The BitmapEdit widget has an XtNtoggleCallback resource. The function registered for this widget and resource draws into the two pixmaps, and then calls RedrawSmallPicture to copy the updated pixmap information to the window (alternately, it could draw directly into window instead of copying from the pixmap).

Three new routines are needed: RedrawSmallPicture, SetUpThings, and CellToggled. These three routines are primarily composed of Xlib calls that set up for and perform graphics. The RedrawSmallPicture routine copies a pixmap into a widget, SetUpThings creates the pixmaps and the GCs (introduced in the next subsection, 4.2.2) needed to draw into these pixmaps, and CellToggled is a callback function registered with the BitmapEdit widget that updates the pixmaps whenever a cell is toggled. Code is also added to main to create the two DrawingArea widgets and register RedrawSmallPicture with them. We'll show these routines one at a time. Example 4-3 shows the improvements to the main routine.

Example 4-3. xbitmap2: implementing small pixmaps with DrawingArea widgets

/*
 * Public include files for widgets used in this file.
 */
  .
  .
  .
#include <Xm/DrawingA.h>  /* drawing area */
  .
  .
  .
/* Structure to hold private data (avoids lots of globals). */
struct {
    GC draw_gc, undraw_gc;
    Pixmap normal_bitmap, reverse_bitmap;
    Widget showNormalBitmap, showReverseBitmap;
    String filename;    /* filename to read and write */
    Dimension pixmap_width_in_cells, pixmap_height_in_cells;
} bitmap_stuff;
  .
  .
  .
main(argc, argv)
int argc;
char **argv;
{
    XtAppContext app_context;
    Widget topLevel, mainWindow, menuBar;
    Widget fileButton, fileMenu, quit, helpButton, helpMenu,
            help, helpBox;
    Widget temp;
    Widget bigBitmap, output, smallPixmapBox;
    Widget scrolledWin, frame1, frame2;
      .
      .
      .
    /*
     * Size of bitmap must be queried so that small pixmaps
     * of the right size can be created.
     */
    XtVaGetValues(bigBitmap, XtNpixmapWidthInCells,
            &bitmap_stuff.pixmap_width_in_cells,
            XtNpixmapHeightInCells,
            &bitmap_stuff.pixmap_height_in_cells,
            NULL);
    /*
     * Create pixmaps and GCs, draw initial contents into pixmaps */
     */
    SetUpThings(topLevel);
      .
      .
      .
    /* create RowColumn to hold small pixmap widgets */
    smallPixmapBox = XtVaCreateManagedWidget("smallPixmapBox",
            xmRowColumnWidgetClass, mainWindow,
            NULL);
    /* create Frame so small pixmap widget appears in box */
    frame1 = XtVaCreateManagedWidget("frameNormal",
            xmFrameWidgetClass, smallPixmapBox,
            NULL);
    /* create DrawingArea for small pixmap widget - must set size. */
    bitmap_stuff.showNormalBitmap = XtVaCreateManagedWidget(
            "showNormalBitmap", xmDrawingAreaWidgetClass, frame1,
            XmNwidth, bitmap_stuff.pixmap_width_in_cells,
            XmNheight, bitmap_stuff.pixmap_height_in_cells,
            NULL);
    /* create Frame so small pixmap widget appears in box */
    frame2 = XtVaCreateManagedWidget("frameReverse",
            xmFrameWidgetClass, smallPixmapBox,
            NULL);
    /* create DrawingArea for small pixmap widget - must set size. */
    bitmap_stuff.showReverseBitmap = XtVaCreateManagedWidget(
            "showReverseBitmap", xmDrawingAreaWidgetClass, frame2,
            XmNwidth, bitmap_stuff.pixmap_width_in_cells,
            XmNheight, bitmap_stuff.pixmap_height_in_cells,
            NULL);
    /* register RedrawSmallPicture as the redrawing function */
    XtAddCallback(bitmap_stuff.showNormalBitmap, XmNexposeCallback,
            RedrawSmallPicture, 0);
    XtAddCallback(bitmap_stuff.showReverseBitmap, XmNexposeCallback,
            RedrawSmallPicture, 0);
    /* Register CellToggled to be called whenever a cell is toggled. */
    XtAddCallback(bigBitmap, XtNtoggleCallback, CellToggled, 0);
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}


The main routine registers RedrawSmallPicture as the XmNexposeCallback function for each DrawingArea widget. It also registers CellToggled as the XtNtoggleCallback for the BitmapEdit widget, to be called whenever a bitmap cell is toggled.

SetUpThings uses Xlib calls to create the pixmap that will be used as an off-screen copy of what will be displayed in the DrawingArea widgets. SetUpThings also creates GCs that will be used to draw into these pixmaps. CellToggled does the drawing, and RedrawSmallPicture performs the copying. These routines are shown in the next section, which describes how graphics work in Xt programs.

Graphics from the Application

Drawing in the X Window System is done by creating a graphics context (GC) that specifies such things as colors and line widths, and then calling a drawing routine that specifies what shape is to be drawn. These two steps are basic to X and required in programs written in any language with or without a toolkit. For example, the call to draw a line specifies only the start and endpoints of the line. The GC specifies everything else about how the server will actually draw this line.

A GC is a server-side object that must be created by the application. The purpose of the GC is to cache on the server side information about how graphics are to be executed by the server, so that this information doesn't have to be sent over the network with every graphics call. If X did not have GCs, every graphics call would have many arguments and this would waste network time (and be annoying to program). Instead, you create a small number of GCs before drawing (in the startup phase of the application). Each represents a particular color or line style you will need to draw with at some point. You then specify one of these GCs in each call to draw. For example, to draw text and be able to highlight it at will, it is customary to create two GCs, one for drawing in white on black, and one for drawing in black on white (where colors can be substituted for black and white on color screens).

Once created, a GC is referred to by its ID, of type GC. This ID is specified in calls to draw using that GC.

From the application, the Xlib routine XCreateGC() is usually used to create GCs. Xt also provides the XtGetGC() routine for creating GCs, but it is typically used only inside widget code when there could be many of the same GCs created. XtGetGC() is very similar to XCreateGC(), except that it arranges for GCs to be shared among widgets (within one application). XtGetGC() will be described in Section 5.1, "The Remaining Motif Widgets and Gadgets."

In xbitmap2, the SetUpThings routine is responsible for creating two pixmaps that act as off-screen drawing surfaces, and two GCs that will be used to draw and undraw pixels in the pixmaps. Whenever the DrawingArea widgets need to be updated, the pixmaps are copied into the widgets. This is only one possible exposure-handling approach. Another is to draw the required points directly into the DrawingArea widgets each time they need to be updated, keeping a record of the drawn points so that the entire widget can be redrawn in case of exposure. Example 4-4 shows SetUpThings, which creates pixmap and GCs and then initializes the pixmaps.

Example 4-4. xbitmap2: creating pixmaps and GCs

static void
SetUpThings(w)
Widget w;
{
    XGCValues values;
    bitmap_stuff.normal_bitmap = XCreatePixmap(XtDisplay(w),
            RootWindowOfScreen(XtScreen(w)),
            bitmap_stuff.pixmap_width_in_cells,
            bitmap_stuff.pixmap_height_in_cells, 1);
    bitmap_stuff.reverse_bitmap = XCreatePixmap(XtDisplay(w),
            RootWindowOfScreen(XtScreen(w)),
            bitmap_stuff.pixmap_width_in_cells,
            bitmap_stuff.pixmap_height_in_cells, 1);
    values.foreground = 1;
    values.background = 0;
    /* note that normal_bitmap is used as the drawable because it
     * is one bit deep.  The root window may not be one bit deep */
    bitmap_stuff.draw_gc = XCreateGC(XtDisplay(w),
            bitmap_stuff.normal_bitmap,
            GCForeground | GCBackground, &values);
    values.foreground = 0;
    values.background = 1;
    bitmap_stuff.undraw_gc = XCreateGC(XtDisplay(w),
            bitmap_stuff.normal_bitmap,
            GCForeground | GCBackground, &values);
    /* pixmaps must be cleared - may contain garbage */
    XFillRectangle(XtDisplay(w),
            bitmap_stuff.reverse_bitmap, bitmap_stuff.draw_gc,
            0, 0, bitmap_stuff.pixmap_width_in_cells + 1,
            bitmap_stuff.pixmap_height_in_cells + 1);
    XFillRectangle(XtDisplay(w),
            bitmap_stuff.normal_bitmap, bitmap_stuff.undraw_gc,
            0, 0, bitmap_stuff.pixmap_width_in_cells + 1,
            bitmap_stuff.pixmap_height_in_cells + 1);
}

Notice that GCs can only be used on windows or pixmaps with the same depth as the window or pixmap that is passed in the call to create the GC. In this case, the pixmaps created contain only one plane, even when running on a server with a color screen. These pixmaps are passed to XCreateGC() to set the depth of the GCs. These GCs can only be used to draw into the single plane pixmaps. As you will see in Example 4-6, a separate GC (the default GC) is used to copy from the pixmaps into the windows, since on color screens the windows have multiple planes. (You know you have done this incorrectly if you get an error only on color screens.)

Xt does not provide drawing calls of its own. You must call Xlib directly to draw. An Xlib drawing routine is known as a primitive. Under X, text is drawn by a graphics primitive, just as lines, arcs, and other graphics are drawn.

Colors are normally specified by the user as strings such as “blue,” but the X server understands colors only when specified as numbers called pixel values. A pixel value is used as an index to a lookup table called a colormap, which contains values for the RGB (red-green-blue) primaries used to generate colors on the screen. However, a particular pixel value does not necessarily always map to the same color, even when run twice on the same system, because the contents of the colormap are configurable on most color systems. The wide variation of graphics hardware that X supports has required that the design of color handling in X be very flexible and very complex.

Fortunately, as long as your widget or application requires only a small number of colors, and you do not particularly care whether the colors you get are exactly the colors you requested, Xt provides a simplified interface. It hides the actual Xlib calls involved in the Resource Manager's converter routines. As described in Chapter 3, More Techniques for Using Widgets, if you specify application resources for colors in a resource list, Xt will automatically convert color names specified as resource settings into pixel values. If you need to do more advanced color handling, then you will need to make Xlib calls yourself. For a description of Xlib's color handling, see Chapter 7, Color, in Volume One, Xlib Programming Manual.

xbitmap1 works either in color or monochrome, since the widgets it creates can have their color set by resources. All of xbitmap2 except the small pixmaps we have been implementing also support color. To support color in the small pixmaps takes more work. We would need to add application resources to get the pixel values that will be set into the GCs used when drawing, as described in Section 3.6, "Application Resources."

Example 4-5 shows the CellToggled routine. As you may recall, this routine is a callback function registered with the BitmapEdit widget, to be called whenever a cell is toggled.

Example 4-5. xbitmap2: the CellToggled routine

/* ARGSUSED */
static void
CellToggled(w, client_data, call_data)
Widget w;
XtPointer client_data;  /* unused */
XtPointer call_data;    /* will be cast to cur_info */
{
    /* cast pointer to needed type: */
    BitmapEditPointInfo *cur_info = (BitmapEditPointInfo *) call_data;
    /*
     * Note, BitmapEditPointInfo is defined in BitmapEdit.h
     */
    XDrawPoint(XtDisplay(w), bitmap_stuff.normal_bitmap,
            ((cur_info->mode == DRAWN) ? bitmap_stuff.draw_gc :
            bitmap_stuff.undraw_gc), cur_info->newx, cur_info->newy);
    XDrawPoint(XtDisplay(w), bitmap_stuff.reverse_bitmap,
            ((cur_info->mode == DRAWN) ? bitmap_stuff.undraw_gc :
            bitmap_stuff.draw_gc), cur_info->newx, cur_info->newy);
    RedrawSmallPicture(bitmap_stuff.showNormalBitmap,
            cur_info->newx, cur_info->newy);
    RedrawSmallPicture(bitmap_stuff.showReverseBitmap,
            cur_info->newx, cur_info->newy);
}

Note that BitmapEdit passes a structure called BitmapEditPointInfo into the callback function as an argument. This structure is defined in the public include file, BitmapEdit.h, and it provides the information necessary to keep the small bitmaps displaying the same pattern as BitmapEdit. The fields of BitmapEditPointInfo are the mode (whether drawn or undrawn) and the coordinates of the point toggled. The CellToggled routine draws points into the pixmaps according to the information passed in, and then calls RedrawSmallPicture to copy the pixmaps into each Core widget.

The first line of CellToggled casts the generic pointer info into the structure type defined by BitmapEdit, BitmapEditPointInfo. Under most compilers this can also be done (perhaps more clearly) by declaring the info argument as type BitmapEditPointInfo in the first place. However, ANSI C compilers require a cast.

The RedrawSmallPicture routine is shown in Example 4-6.

Example 4-6. xbitmap2: the RedrawSmallPicture routine

/*ARGSUSED*/
static void
RedrawSmallPicture(w, client_data, call_data)
Widget w;
XtPointer client_data;
XtPointer call_data;
{
    Pixmap pixmap;
    if (w == bitmap_stuff.showNormalBitmap)
        pixmap = bitmap_stuff.normal_bitmap;
    else
        pixmap = bitmap_stuff.reverse_bitmap;
    if (DefaultDepthOfScreen(XtScreen(w)) == 1)
        XCopyArea(XtDisplay(w), pixmap, XtWindow(w),
                DefaultGCOfScreen(XtScreen(w)), 0, 0,
                bitmap_stuff.pixmap_width_in_cells,
                bitmap_stuff.pixmap_height_in_cells,
                0, 0);
    else
        XCopyPlane(XtDisplay(w), pixmap, XtWindow(w),
                DefaultGCOfScreen(XtScreen(w)), 0, 0,
                bitmap_stuff.pixmap_width_in_cells,
                bitmap_stuff.pixmap_height_in_cells,
                0, 0, 1);
}


RedrawSmallPicture is called from CellToggled, and also by Xt in response to Expose events because we registered it as a callback for the DrawingArea widgets. The use of one of two Xlib routines, depending on the depth of the screen, is an optimization. XCopyArea() is faster, but can be used for this job only on monochrome displays, because the pixmaps used here are one plane deep on all displays and must be translated into multiple planes with XCopyPlane() on color displays.

See Volume One, Xlib Programming Manual, for details.

Writing a Bitmap File

Once we have pixmaps in our application that contain the current bitmap, it is a trivial matter to change the printout callback function to write a bitmap file instead of just printing an array of ones and zeroes to the standard output. This is easy because there is an Xlib function, XWriteBitmapFile(), that writes the contents of a single-plane pixmap into a file. Example 4-7 shows the code that gets a filename from the command line and then writes the bitmap file.

Example 4-7. xbitmap2: writing a bitmap file

/* ARGSUSED */
static void
Printout(widget, client_data, call_data)
Widget widget;
XtPointer client_data, call_data; /* unused */
{
    XWriteBitmapFile(XtDisplay(widget), bitmap_stuff.filename,
            bitmap_stuff.normal_bitmap,
            bitmap_stuff.pixmap_width_in_cells,
            bitmap_stuff.pixmap_height_in_cells, 0, 0);
}
main(argc, argv)
int argc;
char *argv[];
{
      .
      .
    /* XtAppInitialize */
      .
      .
    if (argv[1] != NULL)
        bitmap_stuff.filename = argv[1];
    else {
        fprintf(stderr, "xbitmap: must specify filename\
              on command line. \n");
        exit(1);
    }
      .
      .
      .
}

Contrast this version of Printout to the one shown in Example 4-1.

Note that reading a bitmap file requires some more complicated Xlib programming, not Xt programming, so we will not take the space to describe it. If you are curious, the complete code for xbitmap5, which both reads and writes bitmap files, is shown in Appendix E, The xbitmap Application.

xbitmap3: Another Way to Create a Custom Window

The small bitmaps added in the last section provided a simple example of making a custom window by creating a DrawingArea widget and drawing into it from the application. DrawingArea also provides a callback for keyboard and pointer (mouse) input, so that you can implement simple user response from the application. However, you will quickly find that to implement a more complicated custom window you need more control than DrawingArea gives you.

The next technique to consider for implementing features not supported by any existing widget is to add features to an empty widget such as Core or Primitive using actions and translations. This is simpler than writing a widget to do the same thing. It also uses less memory because you do not create a whole new widget class. This section describes how to implement the small pixmaps that way. But first, there are some new concepts involved.

Actions

So far in the book we have used callbacks to link widgets with application code. When you are implementing a custom window, callbacks are useless because they are defined by each widget class, and none of the existing widget classes has the callbacks or features that you need.

Therefore, you need to use a different technique for linking application code with widget code: the combination of actions and translations. You can use actions to add features to a Primitive or Core widget. (Actions are also used inside widget code to implement widget features.)

The action and callback techniques for linking the application with the user interface differ in the way that the registered function is invoked. For callbacks, the trigger is an abstract occurrence defined by the widget, which may or may not be event related. When this happens, the routines on one of a widget's callback lists are invoked by the widget code, using a call to XtCallCallbacks() or XtCallCallbackList(). Actions, on the other hand, are invoked directly by Xt's translation mechanism, as the result of an event combination.

In xbitmap3 we will make the RedrawSmallPicture function into an action function instead of a callback function (this means just giving it different arguments). Then we need to arrange for Xt to call RedrawSmallPicture whenever one of the Primitive widgets becomes exposed. The general procedure for arranging this is the same as in xbitmap2--you register the function during the setup phase of the application, and then Xt will call it in response to Expose events. Registering an action, though, is quite different from registering a callback.

To register the action, you first declare an actions table, which maps action name strings to action function pointers. Then you must register the actions table with Xt by calling XtAppAddActions(). Finally you create a translation table and store it in the app-defaults file or hardcode it in the application. A translation table is a widget resource which contains a mapping between events or event sequences and action names. Figure 4-4 shows a translation table and an action table, and shows how each is used in the process of mapping events to action function calls. (The format of these two tables is described in the following sections and in Chapter 8, Events, Translations, and Accelerators.) This two-stage mapping is necessary so that translations can be specified in resource files. Since resource files contain only strings, a translation specified in a resource file can only translate an event into an action name. The action table, which can only be specified in the code, translates the action name into the actual action function pointer.

Figure 4-4. Two-stage mapping with translation table and action table

When an event arrives in a particular widget, Xt searches that widget's translation table resource. If that event is not found in the table, Xt does nothing. If the event is found, the matching action name is searched for in the action table. If the action name is found, the corresponding action function is called.

We'll describe the format of the action table and translation table, using xbitmap3 as an example.

The Actions Table

The format of an actions table is defined as follows:

typedef struct _XtActionsRec{
    char *string;
    XtActionProc proc;
} XtActionsRec;

By convention, the string and the function name are identical, and begin with uppercase letters, as in the following example:

static void RedrawSmallPicture();
static XtActionsRec actions[] = {
    {"RedrawSmallPicture", RedrawSmallPicture},
    /* {"Quit", Quit}, */
};

The entry that is commented out in the action table shows how additional action functions would be specified in the table if they were needed. xbitmap3 needs only one action function. In xbitmap4 you will see a practical example of multiple action functions.

Action names and action functions should not start with Xt or xt: these are reserved for the Intrinsics.

Example 4-8 shows the code from bitmap3 that creates the action table and registers it with XtAppAddActions().

Example 4-8. xbitmap3.c: adding a feature to a Primitive widget

main(argc, argv)
int argc;
char **argv;
{
    /* other declarations */
      .
      .
      .
    static XtActionsRec actions[] = {
        {"RedrawSmallPicture", RedrawSmallPicture},
    };
      .
      .
      .
    /* XtAppInitialize */
      .
      .
      .
    XtAppAddActions(app_context, actions, XtNumber(actions));
      .
      .
      .
    /* XtAppMainLoop */
}


Actions defined by a widget class are usable only by all instances of that widget class. Actions added with XtAppAddActions(), on the other hand, are global to the application context, and therefore usable by any widget instance of any class in the application. Which widgets they are actually used with is defined by the translation table, as described below.

The names of widget class actions and application actions do not conflict. For example, if an application were to define an action called Move, and some widget class in Motif already has an action by that name, the two actions would not conflict with each other.

When you map the action to events in a particular widget (using a translation table), the same is true, with a slight caveat. If you write an action called Move, and the widget you install it on already has a Move action, your version will be ignored. This implies that you are trying to replace a widget action with your own, and this is not allowed. You have to write a subclass of the widget to replace a widget action.

Format of an Action Function

An action function is just a pointer to a function with four arguments: a widget, an event, a string containing any arguments specified for the action in the translation table (described in Section 4.3.2.3, "Action Parameters"), and the number of arguments contained in the string. Example 4-9 shows the action version of RedrawSmallPicture.

Example 4-9. An XtActionProc with widget and event arguments

/* ARGSUSED */
static void
RedrawSmallPicture(w, event, params, num_params)
Widget w;
XExposeEvent *event;
String *params;
Cardinal *num_params;
{
    Pixmap pixmap;
    if (w == bitmap_stuff.showNormalBitmap)
        pixmap = bitmap_stuff.normal_bitmap;
    else
        pixmap = bitmap_stuff.reverse_bitmap;
    if (DefaultDepthOfScreen(XtScreen(w)) == 1)
        XCopyArea(XtDisplay(w), pixmap, XtWindow(w),
                DefaultGCOfScreen(XtScreen(w)), event->x, event->y,
                event->width, event->height, event->x, event->y);
    else
        XCopyPlane(XtDisplay(w), pixmap, XtWindow(w),
                DefaultGCOfScreen(XtScreen(w)), event->x, event->y,
                event->width, event->height, event->x, event->y, 1);
}

Compare this version of RedrawSmallPicture to the callback version in Example 4-8. Action functions are directly passed the event structure that describes the event that triggered the action, in this case an XExposeEvent structure. This structure contains the position and dimensions of the area that was exposed. This version of RedrawSmallPicture copies only the necessary parts of the pixmap to refresh the areas that are exposed.

The callback version shown in Example 4-8 copies the entire pixmap to the widget regardless of which part of the widget was exposed. This is slightly inefficient but the difference is not noticeable when the pixmap is small. Under Motif, but not Athena, callback functions are passed the event structure as part of a larger structure, which is passed as the call_data argument (for example XmAnyCallbackStruct). Therefore, you have access to the event structure from within both callbacks and actions.

However, be aware that if you are allowing user configuration of be called with different kinds of events. You should at least check the event type in the action routine and print an appropriate message if the user has arranged to call the action with the wrong type of event. We'll show how to do this in Chapter 8, Events, Translations, and Accelerators.

Note that for true ANSI C portability, all four arguments to the action function must be declared, even though many compilers allow you to leave off trailing arguments that are not referenced in the function. If some of the arguments are not used, you should be sure to include the lint comment /* ARGSUSED */.

One difference between action functions and callback functions is that, unlike callbacks, there is no argument provided for passing in application data. You can only pass application data into an action function through global variables.

Translations

Once you have an action table registered, those actions are ready to be used by any widget in the application. But which widgets will they be used in, and which will trigger them? Translations determine these things.

You need to set a translation table resource, XmNtranslations, on any widget you want to be able to call an application action. In the case of xbitmap3, we want RedrawSmallPicture to be called when Expose events occur in the widgets that are displaying the small pixmaps. This means that we need to set the XmNtranslations resource on those two widgets, and the value of the resource should be a translation that maps an Expose event into a RedrawSmallPicture call.

The Translation Table

Every widget that contains actions also has a default translation table that maps event combinations into those actions. The application can override, augment, or replace this table to make a widget call application-registered actions in addition to the widget's own actions. Registering actions with XtAppAddActions() makes them eligible for inclusion in translation tables.

Each line of a translation table maps a sequence of events to a sequence of actions. The entire translation table is simply a string consisting of one or more event specifications in angle brackets, with optional modifiers, followed by a colon and a function name string defined in an action table. Multiple translations can be specified as part of the same string. By convention, the string is continued on several lines, one for each translation, each line except the last terminated with a linefeed (\n) and a backslash (\).

We'll describe the details of event specification and other aspects of translation table syntax in Chapter 8, Events, Translations, and Accelerators. For now, an example should get the point across quite clearly.

The translation we need for xbitmap3, as it would appear in the application code, is shown in Example 4-10. This example also shows how the translation table must be converted into an internal form with XtParseTranslationTable() before it can be used to set an XmNtranslations resource.

Example 4-10. A simple translation table

XtTranslations mytranslations;
static char transTable[] =
    "<Expose>:  RedrawSmallPicture()";
    .
    .
    .
bitmap_stuff.showReverseBitmap =
        XtVaCreateManagedWidget("showReverseBitmap",
        XmPrimitiveWidgetClass, frame2,
        XmNtranslations, XtParseTranslationTable(transTable);
        XmNwidth, bitmap_stuff.pixmap_width_in_cells,
        XmNheight, bitmap_stuff.pixmap_height_in_cells,
        NULL);
    .
    .
    .


The default translations for the PushButton widget (which are defined inside the widget implementation) are as follows:

Example 4-11. A complex translation table

static char defaultTranslations[] =
   "<Btn1Down>:         Arm() \n\
    <Btn1Down>,<Btn1Up>: Activate() Disarm() \n\
    <Btn1Down>(2+):     MultiArm() \n\
    <Btn1Up>(2+):       MultiActivate() \n\
    <Btn1Up>:           Activate() Disarm() \n\
    <Key>osfSelect:     ArmAndActivate() \n\
    <Key>osfActivate:   ArmAndActivate() \n\
    <Key>osfHelp:   Help() \n\
    ~Shift ~Meta ~Alt <Key>Return:  ArmAndActivate() \n\
    ~Shift ~Meta ~Alt <Key>space:   ArmAndActivate() \n\
    <EnterWindow>:      Enter() \n\
    <LeaveWindow>:      Leave()";

Notice that this entire translation table is a single string. This translation table shows a number of useful techniques, which is described in full detail in Chapter 8, Events, Translations, and Accelerators:

  • How to detect a button press followed by a release (second line).

  • How to invoke two successive actions in response to an event sequence (second line).

  • How to detect a double click (lines 3 and 4).

  • How to detect the press of a particular key (lines 5, 6, and 7).

  • How to detect a key press but only if certain modifier keys are not being held (8 and 9).

Like most other resources, translations can be specified in resource files. While debugging, it is a good idea to specify translation tables in files, to minimize recompiling. Example 4-12 shows the part of the app-defaults file for xbitmap3 that sets the translation table.

Example 4-12. XBitmap3: specifying translations in the app-defaults file

*XmPrimitive.baseTranslations:\
    <Expose>:  RedrawSmallPicture()

Note that since resource files are strings, no quotation marks are needed. However, for multiple-line translation tables, you still need to escape the ends of each line with \n\.

The resource set here is baseTranslations (new in R5). (This resource has the same name for all widgets.) As you know, the contents of the app-default file consist solely of strings, so Xt processes the setting for baseTranslations into a translation table using what's called a resource converter. The resource converter calls XtParseTranslationTable() for you when you specify translations in a resource file. Applications intended for both R4 and R5 should also set the translations resource. See Section 10.2.12, "The XtNbaseTranslations Resource" for more information.

In principle, you can modify the translations of existing widgets to customize their behavior. However, Motif discourages this practice since it tends to break user-interface conventions. In fact, some Motif widgets allow you to modify their translations (no error is generated), but they do not actually modify their behavior.[37] When modifying the translations of a widget with existing translations, you may specify a directive to control what happens when there are conflicts between your new translations and the existing translations. Xt recognizes one of three directives in a translation table, beginning with # on the first line, which tell how to handle existing translations (either set as widget defaults, or in other resource database files):

  • #replace (the default if no directive is specified). translations with the current table.

  • #augment. Merge the new translations into the existing translation table, but not to disturb any existing translations. If a translation already exists for a particular event, the conflicting translation specification that appears in a table beginning with #augment is ignored.

  • #override. Merge the new translations into the existing translation table, replacing old values with the current specifications in the event of conflict.

We used #override because this allows us to keep the translations for <EnterWindow> and <LeaveWindow> events in place. In addition, the PushButton widget's set and unset actions will also remain in effect for <Btn1Down> and <Btn1Up>. (For more details on why these aren't overridden by the new translations, see Chapter 8, Events, Translations, and Accelerators.)

The translation:

<Btn1Down>,<Btn1Up>:  confirm()

specifies that the confirm action should be called in response to a pair of events, namely a button press followed by a button release, with no other events intervening.

The translations:

<Btn2Down>:             set() \n\
<Btn2Down>,<Btn2Up>:    quit()

specify that the PushButton widget's internal set action should be invoked by pressing button 2, and that our own quit action should be invoked by clicking button 2. Note that we don't bother to bind the PushButton widget's unset action to <Btn2Up>, since the application will disappear as a result of the quit action. (The unset action is still used when the user presses button 2 and then moves the pointer outside the widget before releasing button 2. This is one of PushButton's default bindings that we have not overridden.) If we were using the widget for any other purpose, we would map <Btn2Up> to unset, so that the widget was restored to its normal appearance when our own action was completed.

Hardcoding Translations

There are cases in which an application may want not only to specify translations in an app-defaults file, but also to hardcode them into the application. When you specify translations only in the app-defaults file, the user has unlimited configurability; if the default translations are deleted or changed beyond recognition, the application may no longer work. But you can hardcode a minimum set of translations in the code, and then supplement these with a set of translations in the app-defaults file that you want to be user-customizable. Among resources, translations are unique because the final value can be the result of merging the values in the resource files with the values in the application (with other resources you get one or the other, but not a combination).

Three Xt functions are used for setting translation tables from the application code:

  • XtParseTranslationTable() is used to compile a string translation table into the opaque internal representation XtTranslations. (For translations specified in resource files, this conversion is performed automatically by a resource converter.)

  • XtAugmentTranslations() is used, like the #augment directive in a resource file, to nondestructively merge translations into a widget's existing translations.

  • XtOverrideTranslations() is used, like the #override directive in a resource file, to destructively merge translations into a widget's existing translations.

Both XtAugmentTranslations() and XtOverrideTranslations() take as arguments a widget and a compiled translation table returned by XtParseTranslationTable().

There is no function that completely replaces a widget's translations; however, you can do this by calling XtSetValues(), the general routine for setting resources (whose use is demonstrated later in this chapter) that set the value of a widget's XmNtranslations resource to a compiled translation table returned by XtParseTranslationTable().

To set the same translations specified in the app-defaults file from the application itself, we would have used the following code:

Example 4-13. Code fragment: specifying translations in the program

static char defaultTranslations[] =
       "<Btn1Down>,<Btn1Up>:    confirm() \n\
        <Btn2Down>:             set() \n\
        <Btn2Down>,<Btn2Up>:    quit()";
XtTranslations mytranslations;
    .
    .
    .
mytranslations = XtParseTranslationTable(defaultTranslations);
XtOverrideTranslations(farewell, mytranslations);

As mentioned earlier, you will find it more convenient to place the translation table in the app-defaults file until the last minute, because this allows changes without recompiling the source.

Action Parameters

The params and num-params arguments of an action function are passed in from the translation table by Xt. For example, consider an action called Move, which can move some data left of right. A translation table that selects events that will invoke Move is shown in Example 4-14.

Example 4-14. A translation table with action parameters

*tetris.translations: \n\
<Key>Left: Move(l) \n\
<Key>Right: Move(r)

With this translation table, Move will be passed a params argument “l” when the left arrow key is pressed, and “r” when the right arrow key is pressed.

Adding Actions to Existing Widgets

It is also possible to add actions to widgets other than Core and Primitive, but it takes more care. Usually, when a widget does not provide a certain callback, it also does not provide various other characteristics that you want. For example, to make the Label widget work like PushButton (pretending that the PushButton widget didn't already exist), you would have to make it accept more kinds of input, add the drawing code to highlight the border and draw the window darker to simulate shadow, and add the ability to call an application function. All of this can be done with actions, but it would take a lot of work. What can make it difficult is that your code may interact with the widget's code in unpleasant ways. When the changes are major, it makes more sense to create a new widget subclass that shares some characteristics and code with its superclass. As we will see, that is exactly how PushButton is implemented, as a subclass of Label. The Primitive and DrawingArea widgets, on the other hand, have no input or output semantics at all, and therefore it is simpler to add actions to them without conflict.

xbitmap4: A Bitmap Editor Without a BitmapEdit Widget

Until you have experience working with widget code, it can be easier to prototype a custom window for your application by adding to a Primitive widget from the application code. Once this code is working, and you have read Chapter 6, Inside a Widget, and Chapter 7, Basic Widget Methods, you can easily move the code into a widget when you want to package it or take advantage of any of the features of Xt that are inaccessible from the application. The code for the BitmapEdit widget was originally written as an application (as described in this section) and later moved into a widget (with the result described in Chapters 6 and 7). Once you understand the structure of a widget, it becomes easier to write widgets directly than to build the same functionality in the application first.

The xbitmap4 implements an application almost identical to xbitmap1, but without using the BitmapEdit widget. The custom bitmap editing window is done in a Primitive widget, with all the code in the application. This is an example of a custom window that could not be easily built using DrawingArea. This application is a culmination of everything you have seen so far. It is also a preview of what you will see in the next chapter. The same code in xbitmap4 that is used to implement the bitmap editor will be moved into widget code in Chapters 6 and 7.

This example takes advantage of application resources to set the configurable parameters of the bitmap code. The code that sets up the application resources is described in Section 3.6, "Application Resources." When moving the code into a widget framework, the same resource list will be used verbatim. The example also provides command-line options to set the important parameters of the bitmap code. The code for processing these options is described in Section 3.7, "Command-line Options." The code is the same whether used for setting application resources or widget resources, except no call equivalent to XtGetApplicationResources() is necessary in a widget.

The exposure strategy used for the bitmap editor is the same as for the small bitmaps in the previous section. The application creates a large pixmap of depth one that stores the current image of the bitmap being edited. Whenever the screen needs updating, the applicable part of the pixmap is copied to the Core widget in the Redraw_picture routine. Because this pixmap is much bigger than the ones in the last section, it is an important optimization that only the required parts of the pixmap are copied. (This is not the only possible exposure strategy. This particular strategy has very low network load, but uses a relatively large amount of server memory. For this reason it is not ideal for PC servers.)

The SetUpThings routine creates the pixmap, draws a grid into it that will persist for the life of the application, and creates three GCs. One GC is for copying from the pixmap to the window, and two are for drawing and undrawing cells in the pixmap. The btn_event routine draws and undraws cells in the pixmap according to pointer clicks and drags, and calls Redraw_picture to update the Core widget display.

Redraw_picture is called both from the application and from Xt. This is a common trick used to reduce the duplication of drawing code. Since Redraw_picture is an action, it has an event argument that is used by Xt to pass in the Expose event describing the area exposed. This application also uses this argument by constructing a fake event to pass in information about which part of the widget to draw.

The application adds actions and sets the XmNtranslations resource of the Core widget so that Xt calls the application routine Redraw_picture whenever Expose events arrive, and calls btn_event when ButtonPress or MotionNotify events arrive.

Example 4-15 shows the complete code for xbitmap4. You have seen all the techniques here in various examples before. You should work through the code and make sure you understand the purpose of each section. However, don't worry about the details of the Xlib calls, since they are specific to this application.

Example 4-15. xbitmap4: implementing the bitmap editor from the application

/*
 * xbitmap4.c
 */
#include <Xm/Xm.h>  
#include <Xm/PanedW.h>  
#include <Xm/RowColumn.h>  
#include <Xm/PushB.h>  
/* we use XmPrimitive, but no header file needed (in Xm.h) */
#include <stdio.h>  
/* The following could be placed in an "xbitmap.h" file. */
#define XtNdebug "debug"
#define XtCDebug "Debug"
#define XtNpixmapWidthInCells "pixmapWidthInCells"
#define XtCPixmapWidthInCells "PixmapWidthInCells"
#define XtNpixmapHeightInCells "pixmapHeightInCells"
#define XtCPixmapHeightInCells "PixmapHeightInCells"
#define XtNcellSizeInPixels "cellSizeInPixels"
#define XtCCellSizeInPixels "CellSizeInPixels"
#define DRAWN 1
#define UNDRAWN 0
#define DRAW 1
#define UNDRAW 0
#define MAXLINES  1000
#define MINBITMAPWIDTH  2
#define MAXBITMAPWIDTH  1000
#define MINBITMAPHEIGHT  2
#define MAXBITMAPHEIGHT  1000
#define MINCELLSIZE  4
#define MAXCELLSIZE  100
#define SCROLLBARWIDTH 15
/*
 * Data structure for private data.
 * (This avoids lots of global variables.)
 */
typedef struct {
    Pixmap big_picture;
    GC draw_gc, undraw_gc; /* for drawing into the
                            * big_picture, 1-bit deep */
    GC copy_gc;            /* for copying from pixmap
                            * into window, screen depth */
    Widget bitmap;         /* this is the drawing surface */
    char *cell;            /* this is the array for printing output
                            * and keeping track of cells drawn */
    int cur_x, cur_y;
    Dimension pixmap_width_in_pixels, pixmap_height_in_pixels;
} PrivateAppData;
/* data structure for application resources */
typedef struct {
    Pixel copy_fg;
    Pixel copy_bg;
    int pixmap_width_in_cells;
    int pixmap_height_in_cells;
    int cell_size_in_pixels;
    Boolean debug;
} AppData;
AppData app_data;
PrivateAppData private_app_data;
/* resource list */
static XtResource resources[] = {
    {
        XmNforeground,
        XmCForeground,
        XmRPixel,
        sizeof(Pixel),
        XtOffsetOf(AppData, copy_fg),
        XmRString,
        XtDefaultForeground
    },
    {
        XmNbackground,
        XmCBackground,
        XmRPixel,
        sizeof(Pixel),
        XtOffsetOf(AppData, copy_bg),
        XmRString,
        XtDefaultBackground
    },
    {
        XtNpixmapWidthInCells,
        XtCPixmapWidthInCells,
        XmRInt,
        sizeof(int),
        XtOffsetOf(AppData, pixmap_width_in_cells),
        XmRImmediate,
        (XtPointer) 32,
    },
    {
        XtNpixmapHeightInCells,
        XtCPixmapHeightInCells,
        XmRInt,
        sizeof(int),
        XtOffsetOf(AppData, pixmap_height_in_cells),
        XmRImmediate,
        (XtPointer) 32,
    },
    {
        XtNcellSizeInPixels,
        XtCCellSizeInPixels,
        XmRInt,
        sizeof(int),
        XtOffsetOf(AppData, cell_size_in_pixels),
        XmRImmediate,
        (XtPointer) 30,
    },
    {
        XtNdebug,
        XtCDebug,
        XmRBoolean,
        sizeof(Boolean),
        XtOffsetOf(AppData, debug),
        XmRImmediate,
        (XtPointer) False,
    },
};
/* Command-line options table */
static XrmOptionDescRec options[] = {
    {"-pw",            "*pixmapWidthInCells",   XrmoptionSepArg, NULL},
    {"-pixmapwidth",   "*pixmapWidthInCells",   XrmoptionSepArg, NULL},
    {"-ph",            "*pixmapHeightInCells",  XrmoptionSepArg, NULL},
    {"-pixmapheight",  "*pixmapHeightInCells",  XrmoptionSepArg, NULL},
    {"-cellsize",      "*cellSizeInPixels",     XrmoptionSepArg, NULL},
    {"-fg",            "*foreground",           XrmoptionSepArg, NULL},
    {"-foreground",    "*foreground",           XrmoptionSepArg, NULL},
    {"-debug",    "     *debug",                XrmoptionNoArg, "True"},
};
/* callback function to print cell array to stdout */
/* ARGSUSED */
static void
PrintOut(w, event, params, num_params)
Widget w;
XEvent *event;
String *params;
Cardinal *num_params;
{
    /*
     * The absense of the small pixmaps in this version makes it
     * more difficult to call XWriteBitmapFile.  Therefore, we will
     * stick with the printed output for this version.
     */
    int x, y;
    putchar(' \n');
    for (y = 0; y < app_data.pixmap_height_in_cells; y++) {
        for (x = 0; x < app_data.pixmap_width_in_cells; x++)
            putchar(private_app_data.cell[x + y *
                    app_data.pixmap_width_in_cells] ? '1' : '0');
        putchar(' \n');
    }
    putchar(' \n');
}
static void RedrawPicture(), DrawCell(), UndrawCell(), ToggleCell(),
        DrawPixmaps();
static void Syntax(argc, argv)
int argc;
char * argv[];
{
    int i;
    static int errs = False;
    /* first argument is program name - skip that */
    for (i = 1; i < argc; i++) {
        if (!errs++) /* do first time through */
            fprintf(stderr, "xbitmap4: command line\
                    option not understood: \n");
        fprintf(stderr, "option: %s\n", argv[i]);
    }
    fprintf(stderr, "xbitmap understands all standard Xt\
            command line options. \n");
    fprintf(stderr, "Additional options are as follows: \n");
    fprintf(stderr, "Option             Valid Range);
    fprintf(stderr, "-pw                MINBITMAPWIDTH to\
            MAXBITMAPWIDTH\n");
    fprintf(stderr, "-pixmapwidth       MINBITMAPWIDTH to\
            MAXBITMAPWIDTH\n");
    fprintf(stderr, "-ph                MINBITMAPHEIGHT to\
            MAXBITMAPHEIGHT\n");
    fprintf(stderr, "-pixmapheight      MINBITMAPHEIGHT to\
            MAXBITMAPHEIGHT\n");
    fprintf(stderr, "-cellsize          MINCELLSIZE to\
            MAXCELLSIZE\n");
    fprintf(stderr, "-fg                color name\n");
    fprintf(stderr, "-foreground        color name\n");
    fprintf(stderr, "-debug             no value necessary\n");
}
main(argc, argv)
int argc;
char *argv[];
{
    XtAppContext app_context;
    Widget topLevel, vpane, buttonbox, quit, output;
    extern exit();
    /* translation table for bitmap core widget */
    String trans =
    "<Expose>:  RedrawPicture()                  \n\
         <Btn1Down>:    DrawCell()               \n\
         <Btn2Down>:    UndrawCell()             \n\
         <Btn3Down>:    ToggleCell()             \n\
         <Btn1Motion>:  DrawCell()               \n\
         <Btn2Motion>:  UndrawCell()             \n\
         <Btn3Motion>:  ToggleCell()";
    static XtActionsRec window_actions[] = {
        {"RedrawPicture",   RedrawPicture},
        {"DrawCell",    DrawCell},
        {"UndrawCell",  UndrawCell},
        {"ToggleCell",  ToggleCell},
    };
	XtSetLanguageProc(NULL, (XtLanguageProc)NULL, NULL);
    topLevel = XtVaAppInitialize(
            &app_context,       /* Application context */
            "XBitmap4",
            options, XtNumber(options),
            &argc, argv,        /* command line args */
            NULL,               /* for missing app-defaults file */
            NULL);              /* terminate varargs list */
    /* XtInitialize leaves program name in args */
    if (argc > 1)
        Syntax(argc, argv);
    XtGetApplicationResources(topLevel,
            &app_data,
            resources,
            XtNumber(resources),
            NULL,
            0);
    /*
     * We must check the application resource values here.
     * Otherwise, user could supply out of range values and crash
     * program. Conversion routines do this automatically, so
     * colors are already checked.
     */
    if ((app_data.pixmap_width_in_cells > MAXBITMAPWIDTH) ||
            (app_data.pixmap_width_in_cells < MINBITMAPWIDTH) ||
            (app_data.pixmap_height_in_cells > MAXBITMAPWIDTH) ||
            (app_data.pixmap_height_in_cells < MINBITMAPWIDTH)) {
        fprintf(stderr, "xbitmap: error in resource settings:\
                dimension must be between %d and %d cells,
                MINBITMAPWIDTH, MAXBITMAPWIDTH);
        exit(1);
    }
    if ((app_data.cell_size_in_pixels < MINCELLSIZE) ||
            (app_data.cell_size_in_pixels > MAXCELLSIZE)) {
        fprintf(stderr, "xbitmap: error in resource settings:\
                cell size must be between %d and %d pixels\n",
                MINCELLSIZE, MAXCELLSIZE);
        exit(1);
    }
    /* begin application code */
    set_up_things(topLevel);
    private_app_data.cell = XtCalloc(app_data.pixmap_width_in_cells *
            app_data.pixmap_height_in_cells, sizeof(char));
    if (app_data.debug)
        fprintf(stderr, "xbitmap: pixmap dimensions are %d by %d\n",
                app_data.pixmap_width_in_cells,
                app_data.pixmap_height_in_cells);
    vpane = XtVaCreateManagedWidget("vpane", xmPanedWindowWidgetClass,
            topLevel, XmNwidth,
            private_app_data.pixmap_width_in_pixels, NULL);
    buttonbox = XtVaCreateManagedWidget("buttonbox",
            xmRowColumnWidgetClass, vpane, NULL);
    output = XtVaCreateManagedWidget("output", xmPushButtonWidgetClass,
            buttonbox, NULL);
    XtAddCallback(output, XmNactivateCallback, PrintOut, NULL);
    quit = XtVaCreateManagedWidget("quit", xmPushButtonWidgetClass,
            buttonbox, NULL);
    XtAddCallback(quit, XmNactivateCallback, exit, NULL);
    /* note: no header file needed to create xmPrimitive */
    private_app_data.bitmap = XtVaCreateManagedWidget("bitmap",
            xmPrimitiveWidgetClass, vpane,
            XmNtranslations, XtParseTranslationTable(trans),
            XmNwidth, private_app_data.pixmap_width_in_pixels,
            XmNheight, private_app_data.pixmap_height_in_pixels,
            NULL);
    XtAppAddActions(app_context, window_actions,
            XtNumber(window_actions));
    XtRealizeWidget(topLevel);
    XtAppMainLoop(app_context);
}
set_up_things(w)
Widget w;
{
    XGCValues values;
    int x, y;
    XSegment segment[MAXLINES];
    int n_horiz_segments, n_vert_segments;
    private_app_data.pixmap_width_in_pixels =
            app_data.pixmap_width_in_cells *
            app_data.cell_size_in_pixels;
    private_app_data.pixmap_height_in_pixels =
            app_data.pixmap_height_in_cells *
            app_data.cell_size_in_pixels;
    private_app_data.big_picture = XCreatePixmap(XtDisplay(w),
            RootWindowOfScreen(XtScreen(w)),
            private_app_data.pixmap_width_in_pixels,
            private_app_data.pixmap_height_in_pixels, 1);
    values.foreground = 1;
    values.background = 0;
    values.dashes = 1;
    values.dash_offset = 0;
    values.line_style = LineOnOffDash;
    private_app_data.draw_gc = XCreateGC(XtDisplay(w),
            private_app_data.big_picture, GCForeground | GCBackground
            | GCDashOffset | GCDashList | GCLineStyle, &values);
    values.foreground = 0;
    values.background = 1;
    private_app_data.undraw_gc = XCreateGC(XtDisplay(w),
            private_app_data.big_picture, GCForeground | GCBackground
            | GCDashOffset | GCDashList | GCLineStyle, &values);
    values.foreground = app_data.copy_fg;
    values.background = app_data.copy_bg;
    private_app_data.copy_gc = XCreateGC(XtDisplay(w),
            RootWindowOfScreen(XtScreen(w)),
            GCForeground | GCBackground, &values);
    XFillRectangle(XtDisplay(w), private_app_data.big_picture,
            private_app_data.undraw_gc, 0, 0,
            private_app_data.pixmap_width_in_pixels,
            private_app_data.pixmap_height_in_pixels);
    /* draw permanent grid into pixmap */
    n_horiz_segments = app_data.pixmap_height_in_cells + 1;
    n_vert_segments = app_data.pixmap_width_in_cells + 1;
    for (x = 0; x < n_horiz_segments; x += 1) {
        segment[x].x1 = 0;
        segment[x].x2 = private_app_data.pixmap_width_in_pixels;
        segment[x].y1 = app_data.cell_size_in_pixels * x;
        segment[x].y2 = app_data.cell_size_in_pixels * x;
    }
    /* drawn only once into pixmap */
    XDrawSegments(XtDisplay(w), private_app_data.big_picture,
            private_app_data.draw_gc, segment, n_horiz_segments);
    for (y = 0; y < n_vert_segments; y += 1) {
        segment[y].x1 = y * app_data.cell_size_in_pixels;
        segment[y].x2 = y * app_data.cell_size_in_pixels;
        segment[y].y1 = 0;
        segment[y].y2 = private_app_data.pixmap_height_in_pixels;
    }
    /* drawn only once into pixmap */
    XDrawSegments(XtDisplay(w), private_app_data.big_picture,
            private_app_data.draw_gc, segment, n_vert_segments);
}
/* ARGSUSED */
static void
RedrawPicture(w, event, params, num_params)
Widget w;
XExposeEvent *event;
String *params;
Cardinal *num_params;
{
    register int x, y;
    unsigned int width, height;
    if (event) {    /* drawing because of expose or button press */
        x = event->x;
        y = event->y;
        width = event->width;
        height =  event->height;
    }
    else {  /* drawing because of scrolling */
        x = 0;
        y = 0;
        width =  10000;  /* always the whole window! */
        height =  10000;
    }
    if (DefaultDepthOfScreen(XtScreen(w)) == 1)
        XCopyArea(XtDisplay(w), private_app_data.big_picture,
                XtWindow(w), private_app_data.copy_gc, x +
                private_app_data.cur_x, y + private_app_data.cur_y,
                width, height, x, y);
    else
        XCopyPlane(XtDisplay(w), private_app_data.big_picture,
                XtWindow(w), private_app_data.copy_gc, x +
                private_app_data.cur_x, y + private_app_data.cur_y,
                width, height, x, y, 1);
}
/* ARGSUSED */
static void
DrawCell(w, event, params, num_params)
Widget w;
XButtonEvent *event;
String *params;
Cardinal *num_params;
{
    DrawPixmaps(private_app_data.draw_gc, DRAW, w, event);
}
/* ARGSUSED */
static void
UndrawCell(w, event, params, num_params)
Widget w;
XButtonEvent *event;
String *params;
Cardinal *num_params;
{
    DrawPixmaps(private_app_data.undraw_gc, UNDRAW, w, event);
}
/* ARGSUSED */
static void
ToggleCell(w, event, params, num_params)
Widget w;
XButtonEvent *event;
String *params;
Cardinal *num_params;
{
    static int oldx = -1, oldy = -1;
    GC gc;
    int mode;
    int newx = (private_app_data.cur_x + event->x) /
            app_data.cell_size_in_pixels;
    int newy = (private_app_data.cur_y + event->y) /
            app_data.cell_size_in_pixels;
    if ((mode = private_app_data.cell[newx + newy *
            app_data.pixmap_width_in_cells]) == DRAWN) {
        gc = private_app_data.undraw_gc;
        mode = UNDRAW;
    }
    else {
        gc = private_app_data.draw_gc;
        mode = DRAW;
    }
    if (oldx != newx || oldy != newy) {
        oldx = newx;
        oldy = newy;
        DrawPixmaps(gc, mode, w, event);
    }
}
/* Private Function */
static void
DrawPixmaps(gc, mode, w, event)
GC gc;
int mode;
Widget w;
XButtonEvent *event;
{
    int newx = (private_app_data.cur_x + event->x) /
            app_data.cell_size_in_pixels;
    int newy = (private_app_data.cur_y + event->y) /
            app_data.cell_size_in_pixels;
    XExposeEvent fake_event;
    /* if already done, return */
    if (private_app_data.cell[newx + newy *
            app_data.pixmap_width_in_cells] == mode)
        return;
    XFillRectangle(XtDisplay(w), private_app_data.big_picture, gc,
            app_data.cell_size_in_pixels*newx + 2,
            app_data.cell_size_in_pixels*newy + 2,
            (unsigned int) app_data.cell_size_in_pixels - 3,
            (unsigned int) app_data.cell_size_in_pixels - 3);
    private_app_data.cell[newx + newy *
            app_data.pixmap_width_in_cells] = mode;
    fake_event.x = app_data.cell_size_in_pixels * newx -
            private_app_data.cur_x;
    fake_event.y = app_data.cell_size_in_pixels * newy -
            private_app_data.cur_y;
    fake_event.width = app_data.cell_size_in_pixels;
    fake_event.height = app_data.cell_size_in_pixels;
    RedrawPicture(private_app_data.bitmap, &fake_event);
}

Of particular interest in the code for xbitmap4 is the Syntax function. Notice that it prints out the options that were not understood, in addition to printing out a list of valid options.



[35] How to get and compile the example source code is described in the Preface and Section 2.6.2.2, "Methods."

[36] You may notice that we have abandoned the MainWindow used in xbitmap1, and instead used PanedWindow as the main window of the application. This is because there is no easy way to fit the two additional small pixmap widgets into the layout scheme of MainWindow. (We originally intended to place the small pixmaps in the menubar, but it can accept only CascadeButton widgets. This restriction is intended to enforce the convention that the menubar is only for menus.)

[37] Which translation you cannot modify, and in which widget classes, is not precisely documented in Motif. In general, you should avoid modifying translation of existing widget classes altogether (with the exception of Primitive).