Contents Previous

28 Additional Example Programs


This appendix provides some additional example programs that illustrate techniques not discussed in the body of the book.

This appendix contains a number of programs that provide more realistic examples of how the Motif toolkit is used. Most of the examples are also intended to encourage further investigation into other X-related topics, such as the use of app-defaults files, fallback resources, and command-line option parsing. Our discussion of the examples is fairly limited; see the comments in the code for explanations of various implementation details.

28.1 A Postcard Interface for Mail

The first example provides a GUI wrapper for a mail program. The user-interface model is that of a postcard. The program does not provide any facilities for reading mail messages; it simply allows the user to compose and send one message at a time. Before compiling the program shown in the source code check the definition of MAIL_CMD. If you don't have zmail on your system, set the value to the name of the mail agent you normally use. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1. XmFONTLIST_DEFAULT_TAG replaces XmSTRING_DEFAULT_CHARSET in Motif 1.2.

   /* Written by Dan Heller.  Copyright 1991, 1993, Z-Code Software Corp.
    * This program is freely distributable without licensing fees and
    * is provided without guarantee or warrantee expressed or implied.
    * This program is -not- in the public domain.
    */

   /* zcard.c -- a postcard interface for zmail.
    */
   #include <stdio.h>
   #include <Xm/List.h>
   #include <Xm/LabelG.h>
   #include <Xm/PushB.h>
   #include <Xm/MessageB.h>
   #include <Xm/RowColumn.h>
   #include <Xm/Form.h>
   #include <Xm/Text.h>

   #include "zcard.icon"

   /* redefine to "mush" or "Mail" if you don't have Z-Mail */
   #define MAIL_CMD     "mush"

   extern char *strcpy();
   Widget list_w, text_w, to_w, subj_w;
   Widget CreateLabeledTextForm();
   void add_user(), send_it(), add_to_to(), move();

   /* These only take effect if the app-defaults file is not found */
   String fallback_resources[] = {
       "*XmText.fontList: -*-courier-medium-r-*--12-*",
       "*XmText.translations: #override         Ctrl<Key>D: activate() 0        Ctrl<Key>U: kill-to-start-of-line() 0        Ctrl<Key>W: delete-previous-word() 0        <Key>osfDelete: delete-previous-character()",
       "*msg-text.rows: 15",
       "*msg-text.columns: 35",
       "*XmPushButton.fontList: -*-new century schoolbook-bold-r-*--12-*",
       "*XmPushButtonGadget.fontList: -*-new century schoolbook-bold-r-*--12-*",
       "*XmLabelGadget.fontList: -*-new century schoolbook-bold-r-*--12-*",
       "*XmList.fontList: -*-courier-medium-r-*--12-*",
       "*zcard.labelString: Z-Card",
       "*title.labelString: Quick Message Sender",
       "*actions*leftAttachment: attach_position",
       "*actions*rightAttachment: attach_position",
       "*to-label.labelString: To:",
       "*to-list.visibleItemCount: 6",
       "*subject-label.labelString: Subject:",
       "*add-btn.labelString: Add",
       "*delete-btn.labelString: Delete",
       "*send-btn.labelString: Send",
       "*quit-btn.labelString: Quit",
       "*error.messageString: You must provide at least one message recipient.",
       NULL
   };

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, label, left, heading, icon, titles;
       Widget actions, rc, w, send_w;
       XtAppContext app;
       Arg args[5];
       int n;
       Pixel fg, bg;
       Pixmap pixmap;
       extern void exit();

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app, "Zcard", NULL, 0,
           &argc, argv, fallback_resources,
           XmNallowShellResize,  True,
           NULL);

       /* The form is the general layout manager for the application.
        * It contains two main widgets: a rowcolumn and a scrolled text.
        */
       rc = XtVaCreateWidget ("rc",
           xmRowColumnWidgetClass, toplevel,
           XmNorientation, XmHORIZONTAL,
           NULL);

       /* left side is a RowColumn -- a child of the bigger RowColumn */
       left = XtVaCreateWidget ("left", xmRowColumnWidgetClass, rc, NULL);

       /* start the left side with a Form to hold the heading */
       heading = XtVaCreateWidget ("heading", xmFormWidgetClass, left, NULL);

       /* create an icon to make things pretty */
       XtVaGetValues (heading,
           XmNforeground, &fg,
           XmNbackground, &bg,
           NULL);
       pixmap = XCreatePixmapFromBitmapData (XtDisplay (heading),
           RootWindowOfScreen (XtScreen (heading)),
           /* these values are defined in "zcard.icon" */
           zcard_logo_bits, zcard_logo_width, zcard_logo_height,
           fg, bg, DefaultDepthOfScreen (XtScreen (heading)));
       icon = XtVaCreateManagedWidget ("zcard_icon",
           xmLabelGadgetClass, heading,
           XmNleftAttachment,  XmATTACH_FORM,
           XmNlabelType,       XmPIXMAP,
           XmNlabelPixmap,     pixmap,
           XmNalignment,       XmALIGNMENT_END,
           NULL);

       /* identify the program */
       titles = XtVaCreateWidget ("titles",
           xmRowColumnWidgetClass, heading,
           XmNrightAttachment,  XmATTACH_FORM,
           XmNleftAttachment,   XmATTACH_WIDGET,
           XmNleftWidget,       icon,
           XmNtopAttachment,    XmATTACH_FORM,
           XmNbottomAttachment, XmATTACH_FORM,
           NULL);
       XtVaCreateManagedWidget ("zcard", xmLabelGadgetClass, titles, NULL);
       XtVaCreateManagedWidget ("title", xmLabelGadgetClass, titles, NULL);
       XtManageChild (titles);
       XtManageChild (heading);

       /* provide the "To:" prompt (see the resources above) */
       to_w = CreateLabeledTextForm (left, "to-label", "to");

       /* prompt for the subject (see the resources above) */
       subj_w = CreateLabeledTextForm (left, "subject-label", "subject-text");

       /* when user hits <Return>, advance caret to next input item */
       XtAddCallback (subj_w, XmNactivateCallback, move, NULL);

       /* right side is a scrolled text region for letter input. */
       n = 0;
       XtSetArg (args[n], XmNeditMode,          XmMULTI_LINE_EDIT); n++;
       XtSetArg (args[n], XmNscrollVertical,    True); n++;
       XtSetArg (args[n], XmNscrollHorizontal,  True); n++;
       text_w = XmCreateScrolledText (rc, "msg-text", args, n);
       XtManageChild (text_w);

       /* Ctrl-D in text_w causes activate() which calls send_it() */
       XtAddCallback (text_w, XmNactivateCallback, send_it, send_w);

       /* Create a ScrolledList of all the recipients entered in To: */
       n = 0;
       XtSetArg (args[n], XmNscrollingPolicy, XmAUTOMATIC); n++;
       XtSetArg (args[n], XmNselectionPolicy, XmEXTENDED_SELECT); n++;
       XtSetArg (args[n], XmNlistSizePolicy, XmRESIZE_IF_POSSIBLE); n++;
       list_w = XmCreateScrolledList (left, "to-list", args, n);
       XtAddCallback (list_w, XmNdefaultActionCallback, add_to_to, to_w);
       XtManageChild (list_w);

       /* Any command line args are recipients */
       while (argc-- > 1) {
           XmString str = XmStringCreateLocalized (*++argv);
           XmListAddItemUnselected (list_w, str, 0);
           XmStringFree (str);
       }

       /* Add, Delete, Send and Quit buttons -- space equally */
       actions = XtVaCreateWidget ("actions", xmFormWidgetClass, left, NULL);

       send_w = XtVaCreateManagedWidget ("send-btn",
           xmPushButtonWidgetClass, actions,
           XmNleftAttachment, XmATTACH_POSITION,
           XmNleftPosition, 0,
           XmNrightAttachment, XmATTACH_POSITION,
           XmNrightPosition, 23,
           NULL);
       XtAddCallback (send_w, XmNactivateCallback, send_it, NULL);

       w = XtVaCreateManagedWidget ("add-btn",
           xmPushButtonWidgetClass, actions,
           XmNleftAttachment, XmATTACH_POSITION,
           XmNleftPosition, 26,
           XmNrightAttachment, XmATTACH_POSITION,
           XmNrightPosition, 46,
           NULL);
       /* clicking on Add user adds user to scrolled list */
       XtAddCallback (w, XmNactivateCallback, add_user, (XtPointer) 1);

       /* Make it appear as tho hitting return in To: text widget
        * is just like clicking on the Add button.
        */
       XtAddCallback (to_w, XmNactivateCallback, add_user, w);

       w = XtVaCreateManagedWidget ("delete-btn",
           xmPushButtonWidgetClass, actions,
           XmNleftAttachment, XmATTACH_POSITION,
           XmNleftPosition, 49,
           XmNrightAttachment, XmATTACH_POSITION,
           XmNrightPosition, 75,
           NULL);
       /* clicking on delete calls add_user() with a 0 client_data */
       XtAddCallback (w, XmNactivateCallback, add_user, (XtPointer) 0);

       w = XtVaCreateManagedWidget ("quit-btn",
           xmPushButtonWidgetClass, actions,
           XmNleftAttachment, XmATTACH_POSITION,
           XmNleftPosition, 78,
           XmNrightAttachment, XmATTACH_POSITION,
           XmNrightPosition, 100,
           NULL);
       XtAddCallback (w, XmNactivateCallback, exit, NULL);
       XtManageChild (actions);

       XtManageChild (left);
       XtManageChild (rc);

       /* specify tab groups in the order we'd like tabbing to follow */
       XtVaSetValues (to_w, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, NULL);
       XtVaSetValues (subj_w, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, NULL);
       XtVaSetValues (text_w, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, NULL);
       XtVaSetValues (actions, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, NULL);
       XtVaSetValues (list_w, XmNnavigationType, XmEXCLUSIVE_TAB_GROUP, NULL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* add_user() -- add an address to the list of recipients.
    * The user clicked on either Add or Delete buttons, or he hit return in
    * the To: text field.  In the latter case, client data is the add_btn,
    * so call that widget's ArmAndActivate() action proc.
    */
   void
   add_user(w, client_data, call_data)
   Widget w;
   XtPointer client_data;
   XtPointer call_data;
   {
       int data = (int) client_data;
       XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *) call_data;

       if (w == to_w) {
           /* User hit return... make it look as tho he clicked on Add */
           XtCallActionProc (data, "ArmAndActivate", cbs->event, NULL, 0);
           return;
       }

       /* User clicked on Add if data == 1, or delete otherwise */
       if (data) {
           /* get the value of the To: text widget */
           char *text = XmTextGetString (to_w);
           XmString str = XmStringCreateLocalized (text);
           if (text && *text) /* if not a null string, add to List */
               XmListAddItemUnselected (list_w, str, 0);
           XmStringFree (str);
           XtFree (text);
           XmTextSetString (to_w, NULL); /* reset so user can add more */
       }
       else {
           /* user clicked on Delete; delete all selected names */
           int *sel, n;
           if (!XmListGetSelectedPos (list_w, &sel, &n))
               return;
           /* Must delete in reverse order or positions get messed up! */
           while (n--)
               XmListDeletePos (list_w, sel[n]);
           XtFree (sel);
       }
   }

   /* add_to_to() -- callback for double-clicking a list item that
    * causes the selected item to be added to To: text.  Now
    * the user can edit the address.
    */
   void
   add_to_to(list_w, client_data, call_data)
   Widget list_w;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget to_w = (Widget) client_data;
       XmListCallbackStruct *cbs = (XmListCallbackStruct *) call_data;
       char *text;

       XmStringGetLtoR (cbs->item, XmFONTLIST_DEFAULT_TAG, &text);
       XmTextSetString (to_w, text);
       XmTextSetInsertionPosition (to_w, strlen(text));
       XtFree (text);
       XmListDeletePos (list_w, cbs->item_position);
       /* it's a long way, but traverse to To: text field */
       XmProcessTraversal (list_w, XmTRAVERSE_NEXT_TAB_GROUP);
   }

   /* send_it() -- callback for when user clicked on Send.  Build
    * a command line, use popen() to open pipe to mail command, send
    * text data to it and then exit.  The message is sent to all
    * of the addresses that have been specified and are shown in the
    * list.
    */
   void
   send_it(w, client_data, call_data)
   Widget w;
   XtPointer client_data;
   XtPointer call_data;
   {
       Widget send_w;
       XmAnyCallbackStruct *cbs = (XmAnyCallbackStruct *) call_data;
       char *text, *subj, cmd[BUFSIZ], *p, *dummy, *getenv();
       int n, i, status;
       XmString *list;
       FILE *pp, *popen();

       if (w == text_w) {
           send_w = (Widget) client_data;
           XtCallActionProc (send_w, "ArmAndActivate", cbs->event, NULL, 0);
           return;
       }

       /* if something was left in the To: field, grab it */
       text = XmTextGetString (to_w);
       if (text != 0 && *text != 0) {
           XmString str = XmStringCreateLocalized (text);
           XmListAddItemUnselected (list_w, str, 0);
           XmTextSetString (to_w, "");
           XmStringFree (str);
           XtFree (text);
       }

       /* Get the list of users entered */
       XtVaGetValues (list_w,
           XmNitems, &list,
           XmNitemCount, &n,
           NULL);
       if (n == 0) {
           static Widget dialog;
           /* user goofed -- must provide at least one recipient */
           if (!dialog) {
               Arg args[5];
               n = 0;
               XtSetArg (args[n], XmNdialogStyle,
                   XmDIALOG_APPLICATION_MODAL); n++;
               dialog = XmCreateErrorDialog (to_w, "error", args, n);
               XtUnmanageChild (
                   XmMessageBoxGetChild (dialog, XmDIALOG_HELP_BUTTON));
               XtUnmanageChild (
                   XmMessageBoxGetChild (dialog, XmDIALOG_CANCEL_BUTTON));
           }
           XtManageChild (dialog);
           return;
       }

       /* get the subject (may be empty) */
       subj = XmTextGetString (subj_w);

       /* build command line */
       if (!(p = getenv ("MAIL_CMD")))
           p = MAIL_CMD;
       p = strcpy (cmd, p);
       p += strlen (cmd);
       *p++ = ' ';
       if (subj && *subj) {
           /* if subject not empty, add to mail command */
           sprintf (p, "-s
           p += strlen (p);
       }

       /* Add each user in the List to the command line */
       for (i = 0; i < n; i++) {
           XmStringGetLtoR (list[i], XmFONTLIST_DEFAULT_TAG, &dummy);
           p += strlen (strcpy (p, dummy));
           if (i < n-1) /* more to come yet... */
               *p++ = ',', *p++ = ' '; /* separate addresses w/commas */
       }

       /* open pipe to mail command */
       if (!(pp = popen (cmd, "w"))) {
           fprintf (stderr, "Can't execute");
           perror (cmd);
           return;
       }
       /* give it the text user typed (may be empty) */
       text = XmTextGetString (text_w);
       fputs (text, pp);
       fputc ('0, pp); /* make sure there's a terminating newline */
       status = pclose (pp); /* close mail program */

       XtFree (text);
       XtFree (subj);
       if (status == 0) {
           XmTextSetString (to_w, NULL);
           XmTextSetString (text_w, NULL);
           XmTextSetString (subj_w, NULL);
           XmListDeleteAllItems (list_w);
       }
       /* send complete -- start back at beginning */
       XmProcessTraversal (w, XmTRAVERSE_HOME);
   }

   /* move() -- callback for when the user hits return in the Text widget */
   void
   move(text_w, client_data, call_data)
   Widget text_w;
   XtPointer client_data;
   XtPointer call_data;
   {
       XmProcessTraversal (text_w, XmTRAVERSE_NEXT_TAB_GROUP);
   }

   /* CreateLabeledTextForm() -- create a Form widget that has a label on
    * the left and a Text widget to the right.  Attach perimeter edges to
    * form.  We use it twice in the program, so make a function out of it.
    */
   Widget
   CreateLabeledTextForm(parent, label_name, text_name)
   Widget parent;
   char *label_name, *text_name;
   {
       Widget form, label, ret;

       form = XtVaCreateWidget ("form",
           xmFormWidgetClass, parent,
           XmNorientation,      XmHORIZONTAL,
           NULL);
       label = XtVaCreateManagedWidget (label_name,
           xmLabelGadgetClass, form,
           XmNleftAttachment,   XmATTACH_FORM,
           XmNtopAttachment,    XmATTACH_FORM,
           XmNbottomAttachment, XmATTACH_FORM,
           NULL);
       ret = XtVaCreateManagedWidget (text_name,
           xmTextWidgetClass, form,
           XmNleftAttachment,   XmATTACH_WIDGET,
           XmNleftWidget,       label,
           XmNtopAttachment,    XmATTACH_FORM,
           XmNrightAttachment,  XmATTACH_FORM,
           XmNbottomAttachment, XmATTACH_FORM,
           NULL);
       XtManageChild (form);

       return ret;
   }
the figure shows the output of the program.

figs.eps/V6a.appa.01.eps.png
Output of zcard.c


28.2 A Bitmap Display Utility

The xshowbitmap program is a useful utility for reviewing a group of bitmap files. The filenames for the bitmaps can be specified on the command line, sent through a pipe, or typed into stdin. All of the bitmaps are drawn into a pixmap, which is rendered into a DrawingArea widget. The DrawingArea is used as the work window for a ScrolledWindow, so that we can demonstrate application-defined scrolling for the Motif ScrolledWindow. The bitmaps are displayed in an equal number of rows and columns if possible. Alternatively, you can specify either the number of rows or the number of columns using the -rows or -columns command-line option, respectively.

The example in the source code demonstrates the use of Xt mechanisms for adding command-line options and application-level resources in an application. For an explanation of these Xt features, see Volume Four, X Toolkit Intrinsics Programming Manual. For details on the Xlib functions for reading and manipulating bitmaps, see Volume One, Xlib Programming Manual. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4.

   /* xshowbitmap.c -- displays a set of bitmaps specified on the command
    * line, from a pipe, or typed into stdin.  Bitmaps must be specified
    * as file names.
    *
    * Usage: xshowbitmap
    *   -s       sorts the bitmaps in order of size with largest first
    *   -v       verbose mode for when input is redirected to stdin
    *   -w       width of viewport window
    *   -h       height of viewport window
    *   -fg      foreground color
    *   -bg      background color
    *   -label   labels each bitmap with its corresponding filename; default
    *   -nolabel doesn't label each bitmap with its filename
    *   -grid N  line width for grid between bitmaps; defaults to 1
    *   -rows N  number of rows; cannot be used with -cols
    *   -cols N  number of columns; cannot be used with -rows
    *   -fn font font for bitmap filenames
    *   -bw max-width  excludes bitmaps larger than this width; defaults to 64
    *   -bh max-height excludes bitmaps larger than this height; defaults to 64
    *   -        indicates to read from stdin; piping doesn't require the '-'
    *            argument
    *            no arguments reads from stdin
    *
    * Example usage:
    *  xshowbitmaps /usr/include/X11/bitmaps/*
    */

   #include <stdio.h>
   #include <X11/Xos.h>
   #include <Xm/ScrolledW.h>
   #include <Xm/DrawingA.h>
   #include <Xm/ScrollBar.h>

   #ifdef max
   #undef max
   #endif
   #define max(a,b) ((int)(a)>(int)(b)?(int)(a):(int)(b))
   #define min(a,b) ((int)(a)<(int)(b)?(int)(a):(int)(b))

   typedef struct {
       char *name;
       int len;
       unsigned int width, height;
       Pixmap bitmap;
   } Bitmap;

   /* Resrcs is an object that contains global variables that we want the
    * user to be able to initialize through resources or command line options.
    * XtAppInitialize() initializes the fields in this data structure to values
    * indicated by the XrmOptionsDescRec structure defined later.
    */
   struct _resrcs {
       Boolean      sort;                    /* sort the bitmaps */
       Boolean      verbose;                 /* loading bitmaps verbosely */
       Boolean      label_bitmap;            /* whether to label bitmaps */
       int          max_width, max_height;   /* largest allowable bitmap */
       unsigned int grid;                    /* line width between bitmaps */
       Pixel        fg, bg;                  /* colors of bitmaps */
       XFontStruct *font;                    /* font for bitmap labels */
       Dimension    view_width, view_height; /* initial clip window size */
       int          rows, cols;              /* forcefully set #rows/cols */
   } Resrcs;

   /* .Xdefaults or app-defaults resources.  The last field in each structure
    * is used as the default values for the field in the Resrcs struct above.
    */
   static XtResource resources[] = {
       { "sort", "Sort", XmRBoolean, sizeof (Boolean),
           XtOffsetOf (struct _resrcs, sort), XmRImmediate, False },
       { "verbose", "Verbose", XmRBoolean, sizeof (Boolean),
           XtOffsetOf (struct _resrcs,verbose), XmRImmediate, False },
       { "labelBitmap", "LabelBitmap", XmRBoolean, sizeof (Boolean),
           XtOffsetOf (struct _resrcs, label_bitmap), XmRImmediate,
           (char *) True },
       { "grid", "Grid", XmRInt, sizeof (int),
           XtOffsetOf (struct _resrcs, grid), XmRImmediate, (char *) 1 },
       { "bitmapWidth", "BitmapWidth", XmRInt, sizeof (int),
           XtOffsetOf (struct _resrcs, max_width), XmRImmediate, (char *) 64 },
       { "bitmapHeight", "BitmapHeight", XmRInt, sizeof (int),
           XtOffsetOf (struct _resrcs, max_height), XmRImmediate, (char *) 64 },
       { XmNfont, XmCFont, XmRFontStruct, sizeof (XFontStruct *),
           XtOffsetOf (struct _resrcs, font), XmRString, XtDefaultFont },
       { XmNforeground, XmCForeground, XmRPixel, sizeof (Pixel),
           XtOffsetOf (struct _resrcs, fg), XmRString, XtDefaultForeground },
       { XmNbackground, XmCBackground, XmRPixel, sizeof (Pixel),
           XtOffsetOf (struct _resrcs, bg), XmRString, XtDefaultBackground },
       { "view-width", "View-width", XmRDimension, sizeof (Dimension),
           XtOffsetOf (struct _resrcs, view_width), XmRImmediate,
           (char *) 500 },
       { "view-height", "View-height", XmRDimension, sizeof (Dimension),
           XtOffsetOf (struct _resrcs, view_height), XmRImmediate,
           (char *) 300 },
       { "rows", "Rows", XmRInt, sizeof (int),
           XtOffsetOf (struct _resrcs, rows), XmRImmediate, 0 },
       { "cols", "Cols", XmRInt, sizeof (int),
           XtOffsetOf (struct _resrcs, cols), XmRImmediate, 0 },
   };

   /* If the following command line args (1st field) are found, set the
    * associated resource values (2nd field) to the given value (4th field).
    */
   static XrmOptionDescRec options[] = {
       { "-sort", "sort", XrmoptionNoArg, "True" },
       { "-v", "verbose", XrmoptionNoArg, "True" },
       { "-fn", "font", XrmoptionSepArg, NULL },
       { "-fg", "foreground", XrmoptionSepArg, NULL },
       { "-bg", "background", XrmoptionSepArg, NULL },
       { "-w", "view-width", XrmoptionSepArg, NULL },
       { "-h", "view-height", XrmoptionSepArg, NULL },
       { "-rows", "rows", XrmoptionSepArg, NULL },
       { "-cols", "cols", XrmoptionSepArg, NULL },
       { "-bw", "bitmapWidth", XrmoptionSepArg, NULL },
       { "-bh", "bitmapHeight", XrmoptionSepArg, NULL },
       { "-bitmap_width", "bitmapWidth", XrmoptionSepArg, NULL },
       { "-bitmap_height", "bitmapHeight", XrmoptionSepArg, NULL },
       { "-label", "labelBitmap", XrmoptionNoArg, "True" },
       { "-nolabel", "labelBitmap", XrmoptionNoArg, "False" },
       { "-grid", "grid", XrmoptionSepArg, NULL },
   };

   /* size_cmp() -- used by qsort to sort bitmaps into alphabetical order
    * This is used when the "sort" resource is true or when -sort is given.
    */
   size_cmp(b1, b2)
   Bitmap *b1, *b2;
   {
       int n = (int) (b1->width * b1->height) - (int) (b2->width * b2->height);
       if (n)
           return n;
       return strcmp (b1->name, b2->name);
   }

   /* int_sqrt() -- get the integer square root of n.  Used to put the
    * bitmaps in an equal number of rows and colums.
    */
   int_sqrt(n)
   register int n;
   {
       register int i, s = 0, t;
       for (i = 15; i >= 0; i--) {
           t = (s | (1L << i));
           if (t * t <= n)
               s = t;
       }
       return s;
   }

   /* global variables that are not changable thru resources or command
    * line options.
    */
   Widget drawing_a, vsb, hsb;
   Pixmap pixmap; /* used the as image for Label widget */
   GC gc;
   Display *dpy;
   unsigned int cell_width, cell_height;
   unsigned int pix_hoffset, pix_voffset, sw_hoffset, sw_voffset;
   void redraw();

   main(argc, argv)
   int argc;
   char *argv[];
   {
       extern char *strcpy();
       XtAppContext app;
       Widget toplevel, scrolled_w;
       Bitmap *list = (Bitmap *) NULL;
       char buf[128], *p;
       XFontStruct *font;
       int istty = isatty(0), redirect = !istty, i = 0, total = 0;
       unsigned int bitmap_error;
       int j, k;
       void scrolled(), expose_resize();

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtAppInitialize (&app, "XShowbitmap",
           options, XtNumber (options), &argc, argv, NULL, NULL, 0);
       dpy = XtDisplay (toplevel);

       XtGetApplicationResources (toplevel, &Resrcs,
           resources, XtNumber (resources), NULL, 0);

       if (Resrcs.rows && Resrcs.cols)
           XtWarning ("You can't specify both rows *and* columns.");

       font = Resrcs.font;

       /* check to see if we have to load the bitmaps from stdin */
       if (!argv[1] || !strcmp(argv[1], "-")) {
           printf ("Loading bitmap names from standard input. ");
           if (istty) {
               puts ("End with EOF or .");
               redirect++;
           }
           else
               puts ("Use -v to view bitmap names being loaded.");
       }
       else if (!istty && strcmp(argv[1], "-")) {
           printf ("%s: either use pipes or specify bitmap names.0,
               argv[0]);
           exit (1);
       }

       /* Now, load the bitmap file names */
       while (*++argv || redirect) {
           if (!redirect)
               /* this may appear at the end of a list of filenames */
               if (!strcmp (*argv, "-"))
                   redirect++; /* switch to stdin prompting */
               else
                   (void) strcpy (buf, *argv);
           if (redirect) {
               if (istty)
                   printf ("Bitmap file: "), fflush(stdout);
               if (!fgets (buf, sizeof buf - 1, stdin) || !strcmp (buf, ".0))
                   break;
               buf[strlen (buf) - 1] = 0; /* plug a null at the newline */
           }
           if (!buf[0])
               continue;
           if (Resrcs.verbose)
               printf ("Loading
           if (i == total) {
               total += 10; /* allocate bitmap structures in groups of 10 */
               if (!(list = (Bitmap *) XtRealloc
                       (list, total * sizeof (Bitmap))))
                   XtError ("Not enough memory for bitmap data");
           }
           if ((bitmap_error = XReadBitmapFile (dpy, DefaultRootWindow(dpy),
                   buf, &list[i].width, &list[i].height, &list[i].bitmap,
                   &j, &k)) == BitmapSuccess) {
               if (p = rindex (buf, '/'))
                   p++;
               else
                   p = buf;
               if (Resrcs.max_height && list[i].height > Resrcs.max_height ||
                   Resrcs.max_width && list[i].width > Resrcs.max_width) {
                   printf ("%s: bitmap too big0, p);
                   XFreePixmap (dpy, list[i].bitmap);
                   continue;
               }
               list[i].len = strlen (p);
               list[i].name = strcpy (XtMalloc (list[i].len + 1), p);
               if (Resrcs.verbose)
                   printf ("size: %dx%d0, list[i].width, list[i].height);
               i++;
           }
           else {
               printf ("Couldn't load bitmap: ");
               if (!istty && !Resrcs.verbose)
                   printf("
               switch (bitmap_error) {
                   case BitmapOpenFailed : puts ("open failed."); break;
                   case BitmapFileInvalid : puts ("bad file format."); break;
                   case BitmapNoMemory : puts ("not enough memory."); break;
               }
           }
       }
       if ((total = i) == 0) {
           puts ("couldn't load any bitmaps.");
           exit (1);
       }
       printf ("Total bitmaps loaded: %d0, total);
       if (Resrcs.sort) {
           printf ("Sorting bitmaps...");
           fflush (stdout);
           qsort (list, total, sizeof (Bitmap), size_cmp);
           putchar ('0);
       }

       /* calculate size for pixmap by getting the dimensions of each bitmap. */
       printf ("Calculating sizes for pixmap...");
       fflush (stdout);
       for (i = 0; i < total; i++) {
           if (list[i].width > cell_width)
               cell_width = list[i].width;
           if (list[i].height > cell_height)
               cell_height = list[i].height;
           if (Resrcs.label_bitmap && (j = XTextWidth
                   (font, list[i].name, list[i].len)) > cell_width)
               cell_width = j;
       }

       /* Compensate for vertical font height if label_bitmap is true.
        * Add value of grid line weight and a 6 pixel padding for aesthetics.
        */
       cell_height += Resrcs.grid + 6 +
           Resrcs.label_bitmap * (font->ascent + font->descent);
       cell_width += Resrcs.grid + 6;

       /* if user didn't specify row/column layout figure it out ourselves.
        * optimize layout by making it "square".
        */
       if (!Resrcs.rows && !Resrcs.cols) {
           Resrcs.cols = int_sqrt (total);
           Resrcs.rows = (total + Resrcs.cols - 1) / Resrcs.cols;
       }
       else if (Resrcs.rows)
           /* user specified rows -- figure out columns */
           Resrcs.cols = (total + Resrcs.rows - 1) / Resrcs.rows;
       else
           /* user specified cols -- figure out rows */
           Resrcs.rows = (total + Resrcs.cols - 1) / Resrcs.cols;

       printf ("Creating pixmap area of size %dx%d (%d rows, %d cols)0,
           Resrcs.cols * cell_width, Resrcs.rows * cell_height,
           Resrcs.rows, Resrcs.cols);

       if (!(pixmap = XCreatePixmap (dpy, DefaultRootWindow(dpy),
               Resrcs.cols * cell_width, Resrcs.rows * cell_height,
               DefaultDepthOfScreen (XtScreen (toplevel)))))
           XtError ("Can't Create pixmap.");

       if (!(gc = XCreateGC (dpy, pixmap, NULL, 0)))
           XtError ("Can't create gc.");
       XSetForeground (dpy, gc, Resrcs.bg); /* init GC's foreground to bg */
       XFillRectangle (dpy, pixmap, gc, 0, 0,
           Resrcs.cols * cell_width, Resrcs.rows * cell_height);
       XSetForeground (dpy, gc, Resrcs.fg);
       XSetBackground (dpy, gc, Resrcs.bg);
       XSetFont (dpy, gc, font->fid);
       if (Resrcs.grid) {
           if (Resrcs.grid != 1)
               /* Line weight of 1 is faster when left as 0 (the default) */
               XSetLineAttributes (dpy, gc, Resrcs.grid, 0, 0, 0);
           for (j = 0; j <= Resrcs.rows * cell_height; j += cell_height)
               XDrawLine (dpy, pixmap, gc, 0, j, Resrcs.cols * cell_width, j);
           for (j = 0; j <= Resrcs.cols * cell_width; j += cell_width)
               XDrawLine (dpy, pixmap, gc, j, 0, j, Resrcs.rows * cell_height);
       }

       /* Draw each of the bitmaps into the big picture */
       for (i = 0; i < total; i++) {
           int x = cell_width * (i % Resrcs.cols);
           int y = cell_height * (i / Resrcs.cols);
           if (Resrcs.label_bitmap)
               XDrawString (dpy, pixmap, gc,
                   x + 5 + Resrcs.grid / 2, y + font->ascent + Resrcs.grid / 2,
                   list[i].name, list[i].len);
           if (DefaultDepthOfScreen (XtScreen (toplevel)) > 1)
               XCopyPlane (dpy, list[i].bitmap, pixmap, gc,
                   0, 0, list[i].width, list[i].height,
                   x + 5 + Resrcs.grid / 2,
                   y + font->ascent + font->descent + Resrcs.grid / 2, 1L);
           else
               XCopyArea (dpy, list[i].bitmap, pixmap, gc,
                   0, 0, list[i].width, list[i].height,
                   x + 5 + Resrcs.grid / 2,
                   y + font->ascent + font->descent + Resrcs.grid / 2);
           XFreePixmap (dpy, list[i].bitmap);
           XtFree (list[i].name);
       }
       XtFree (list);

       /* Now we get into the Motif stuff */

       /* Create automatic Scrolled Window */
       scrolled_w = XtVaCreateManagedWidget ("scrolled_w",
           xmScrolledWindowWidgetClass, toplevel,
           XmNscrollingPolicy, XmAPPLICATION_DEFINED,
           XmNvisualPolicy,    XmVARIABLE,
           XmNshadowThickness, 0,
           NULL);

       /* Create a drawing area as a child of the ScrolledWindow.
        * The DA's size is initialized (arbitrarily) to view_width and
        * view_height.  The ScrolledWindow will expand to this size.
        */
       drawing_a = XtVaCreateManagedWidget ("drawing_a",
           xmDrawingAreaWidgetClass, scrolled_w,
           XmNwidth,       Resrcs.view_width,
           XmNheight,      Resrcs.view_height,
           NULL);
       XtAddCallback (drawing_a, XmNexposeCallback, expose_resize, NULL);
       XtAddCallback (drawing_a, XmNresizeCallback, expose_resize, NULL);

       /* Application-defined ScrolledWindows won't create their own
        * ScrollBars.  So, we create them ourselves as children of the
        * ScrolledWindow widget.  The vertical ScrollBar's maximum size is
        * the number of rows that exist (in unit values).  The horizontal
        * ScrollBar's maximum width is represented by the number of columns.
        */
       vsb = XtVaCreateManagedWidget ("vsb",
           xmScrollBarWidgetClass, scrolled_w,
           XmNorientation,   XmVERTICAL,
           XmNmaximum,       Resrcs.rows,
           XmNsliderSize,    min (Resrcs.view_height / cell_height, Resrcs.rows),
           NULL);
       if (Resrcs.view_height / cell_height > Resrcs.rows)
           sw_voffset = (Resrcs.view_height - Resrcs.rows * cell_height) / 2;
       hsb = XtVaCreateManagedWidget ("hsb",
           xmScrollBarWidgetClass, scrolled_w,
           XmNorientation,   XmHORIZONTAL,
           XmNmaximum,       Resrcs.cols,
           XmNsliderSize,    min (Resrcs.view_width / cell_width, Resrcs.cols),
           NULL);
       if (Resrcs.view_width / cell_width > Resrcs.cols)
           sw_hoffset = (Resrcs.view_width - Resrcs.cols * cell_width) / 2;

       /* Allow the ScrolledWindow to initialize itself accordingly...*/
       XmScrolledWindowSetAreas (scrolled_w, hsb, vsb, drawing_a);

       XtAddCallback (vsb, XmNvalueChangedCallback, scrolled, XmVERTICAL);
       XtAddCallback (hsb, XmNvalueChangedCallback, scrolled, XmHORIZONTAL);
       XtAddCallback (vsb, XmNdragCallback, scrolled, XmVERTICAL);
       XtAddCallback (hsb, XmNdragCallback, scrolled, XmHORIZONTAL);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* scrolled() -- react to scrolling actions; cbs->value is ScrollBar's
    * new position.
   */
   void
   scrolled(scrollbar, client_data, call_data)
   Widget scrollbar;
   XtPointer client_data;
   XtPointer call_data;
   {
       int orientation = (int) client_data;
       XmScrollBarCallbackStruct *cbs =
           (XmScrollBarCallbackStruct *) call_data;

       if (orientation == XmVERTICAL)
           pix_voffset = cbs->value * cell_height;
       else
           pix_hoffset = cbs->value * cell_width;
       redraw (XtWindow (drawing_a));
   }

   /* expose_resize() -- handles both expose and resize (configure) events.
    * For XmCR_EXPOSE, just call redraw() and return.  For resizing,
    * we must calculate the new size of the viewable area and possibly
    * reposition the pixmap's display and position offset.  Since we
    * are also responsible for the ScrollBars, adjust them accordingly.
    */
   void
   expose_resize(drawing_a, client_data, call_data)
   Widget drawing_a;
   XtPointer client_data;
   XtPointer call_data;
   {
       XmDrawingAreaCallbackStruct *cbs =
           (XmDrawingAreaCallbackStruct *) call_data;
       Dimension view_width, view_height, oldw, oldh;
       int do_clear = 0;

       if (cbs->reason == XmCR_EXPOSE) {
           redraw (cbs->window);
           return;
       }
       oldw = Resrcs.view_width;
       oldh = Resrcs.view_height;

       /* Unfortunately, the cbs->event field is NULL, we have to have
        * get the size of the drawing area manually.
        */
       XtVaGetValues (drawing_a,
           XmNwidth,  &Resrcs.view_width,
           XmNheight, &Resrcs.view_height,
           NULL);

       /* Get the size of the viewable area in "units lengths" where
        * each unit is the cell size for each dimension.  This prevents
        * rounding error for the {vert,horiz}_start values later.
        */
       view_width = Resrcs.view_width / cell_width;
       view_height = Resrcs.view_height / cell_height;

       /* When the user resizes the frame bigger, expose events are generated,
        * so that's not a problem, since the expose handler will repaint the
        * whole viewport.  However, when the window resizes smaller, then no
        * expose event is generated.  In this case, the window does not need
        * to be redisplayed if the old viewport was smaller than the pixmap.
        * (The existing image is still valid--no redisplay is necessary.)
        * The window WILL need to be redisplayed if:
        *  1) new view size is larger than pixmap (pixmap needs to be centered).
        *  2) new view size is smaller than pixmap, but the OLD view size was
        *     larger than pixmap.
        */
       if ( (int) view_height >= Resrcs.rows) {
           /* The height of the viewport is taller than the pixmap, so set
            * pix_voffset = 0, so the top origin of the pixmap is shown,
            * and the pixmap is centered vertically in viewport.
            */
           pix_voffset = 0;
           sw_voffset = (Resrcs.view_height - Resrcs.rows * cell_height) / 2;
           /* Case 1 above */
           do_clear = 1;
           /* scrollbar is maximum size */
           view_height = Resrcs.rows;
       }
       else {
           /* Pixmap is larger than viewport, so viewport will be completely
            * redrawn on the redisplay.  (So, we don't need to clear window.)
            * Make sure upper side has origin of a cell (bitmap).
            */
           pix_voffset = min (pix_voffset,
               (Resrcs.rows-view_height) * cell_height);
           sw_voffset = 0; /* no centering is done */
           /* Case 2 above */
           if (oldh > Resrcs.rows * cell_height)
               do_clear = 1;
       }
       XtVaSetValues (vsb,
           XmNsliderSize,    max (view_height, 1),
           XmNvalue,         pix_voffset / cell_height,
           XmNpageIncrement, max (view_height - 1, 1),
           NULL);

       /* identical to vertical case above */
       if ( (int) view_width >= Resrcs.cols) {
           /* The width of the viewport is wider than the pixmap, so set
            * pix_hoffset = 0, so the left origin of the pixmap is shown,
            * and the pixmap is centered horizontally in viewport.
            */
           pix_hoffset = 0;
           sw_hoffset = (Resrcs.view_width - Resrcs.cols * cell_width) / 2;
           /* Case 1 above */
           do_clear = 1;
           /* scrollbar is maximum size */
           view_width = Resrcs.cols;
       }
       else {
           /* Pixmap is larger than viewport, so viewport will be completely
            * redrawn on the redisplay.  (So, we don't need to clear window.)
            * Make sure left side has origin of a cell (bitmap).
            */
           pix_hoffset = min (pix_hoffset,
               (Resrcs.cols - view_width) * cell_width);
           sw_hoffset = 0;
           /* Case 2 above */
           if (oldw > Resrcs.cols * cell_width)
               do_clear = 1;
       }
       XtVaSetValues (hsb,
           XmNsliderSize,    max (view_width, 1),
           XmNvalue,         pix_hoffset / cell_width,
           XmNpageIncrement, max (view_width - 1, 1),
           NULL);

       if (do_clear)
           /* XClearWindow() doesn't generate an ExposeEvent */
           XClearArea (dpy, cbs->window, 0, 0, 0, 0, True);
   }

   void
   redraw(window)
   Window window;
   {
       XCopyArea (dpy, pixmap, window, gc, pix_hoffset, pix_voffset,
           Resrcs.view_width, Resrcs.view_height, sw_hoffset, sw_voffset);
   }
The output of the example is shown in the figure.

figs.eps/V6a.appa.02.eps.png
Output of xshowbitmap.c


28.3 A Memo Calendar

The xmemo program creates a main application window that contains a calendar and a list of months. Selecting a month changes the calendar, while selecting a day causes that date to become activated. When a date is activated, the application displays another window that contains a Text widget. The Text widget could be used to keep a memo for that day if you were to add code to save and retrieve the contents of the memo. If you select the same day a second time, the window is popped down. the figure shows the output of the program.

The program shown in the source code demonstrates a number of very subtle quirks about X and Motif programming. What separates simple programs from sophisticated ones is how well you get around quirks like the ones demonstrated in this example. For example, the way the dates in the calendar are handled is not as simple as it might appear. Unlike the xcal example in Chapter 11, Labels and Buttons, which used a single Label widget as the calendar, here each date in a month is a separate PushButton widget. To give the appearance that the calendar is a single flat area, the XmNShadowThickness of each PushButton is initialized to 0. When a date is selected, the shadow thickness for that PushButton is reset to 2 (the default) to provide visual feedback that there is a memo associated with it. XtSetLanguageProc() is only available in X11R5; there is no corresponding function in X11R4. XmStringCreateLocalized() is only available in Motif 1.2; XmStringCreateSimple() is the corresponding function in Motif 1.1.

figs.eps/V6a.appa.03.eps.png
Output of xmemo.c


   /* xmemo.c -- a memo calendar program that creates a calendar on the
    * left and a list of months on the right.  Selecting a month changes
    * the calendar.  Selecting a day causes that date to become activated
    * and a popup window is displayed that contains a text widget.  This
    * widget is presumably used to keep memos for that day.  You can pop
    * up and down the window by continuing to select the date on that month.
    */
   #include <stdio.h>
   #include <X11/Xos.h>
   #include <Xm/List.h>
   #include <Xm/Frame.h>
   #include <Xm/LabelG.h>
   #include <Xm/PushB.h>
   #include <Xm/RowColumn.h>
   #include <Xm/Form.h>
   #include <Xm/Text.h>

   int year;
   void XmStringFreeTable(), date_dialog(), set_month();
   Widget list_w, month_label;

   typedef struct _month {
       char *name;
       Widget form, dates[6][7];
   } Month;

   Month months[] = { /* only initialize "known" data */
       { "January" }, { "February" }, { "March" }, { "April" },
       { "May" }, { "June" }, { "July" }, { "August" }, { "September" },
       { "October" }, { "November" }, { "December" }
   };

   /* These only take effect if the app-defaults file is not found */
   String fallback_resources[] = {
       "*XmPushButton.fontList: -*-courier-bold-r-*--18-*",
       "*XmLabelGadget.fontList: -*-courier-bold-r-*--18-*",
       "*XmList.fontList: -*-courier-medium-r-*--18-*",
       NULL
   };

   main(argc, argv)
   int argc;
   char *argv[];
   {
       Widget toplevel, frame, rowcol, rowcol2;
       XtAppContext app;
       int month;

       XtSetLanguageProc (NULL, NULL, NULL);

       toplevel = XtVaAppInitialize (&app, "XMemo", NULL, 0,
           &argc, argv, fallback_resources, NULL);

       /* The form is the general layout manager for the application.
        * It will contain two widgets (the calendary and the list of months).
        * These widgets are laid out horizontally.
        */
       rowcol = XtVaCreateWidget ("rowcol",
           xmRowColumnWidgetClass, toplevel,
           XmNorientation, XmHORIZONTAL,
           NULL);

       /* Place a frame around the calendar... */
       frame = XtVaCreateManagedWidget ("frame1",
           xmFrameWidgetClass, rowcol, NULL);
       /* the calendar is placed inside of a RowColumn widget */
       rowcol2 = XtVaCreateManagedWidget ("rowcol2",
           xmRowColumnWidgetClass, frame, NULL);
       /* the month label changes dynamically as each month is selected */
       month_label = XtVaCreateManagedWidget ("month_label",
           xmLabelGadgetClass, rowcol2, NULL);
       XtVaCreateManagedWidget (" Su Mo Tu  We Th  Fr Sa",
           xmLabelGadgetClass, rowcol2, NULL);

       /* Create a ScrolledText that contains the months.  You probably won't
        * see the ScrollBar unless the list is resized so that not all of
        * the month names are visible.
        */
       {
           XmString strs[XtNumber (months)];
           for (month = 0; month < XtNumber (months); month++)
               strs[month] = XmStringCreateLocalized (months[month].name);
           list_w = XmCreateScrolledList (rowcol, "list", NULL, 0);
           XtVaSetValues (list_w,
               XmNitems,      strs,
               XmNitemCount,  XtNumber (months),
               NULL);
           for (month = 0; month < XtNumber (months); month++)
               XmStringFree (strs[month]);
           XtAddCallback (list_w, XmNbrowseSelectionCallback, set_month, NULL);
           XtManageChild (list_w);
       }

       /* Determine the year we're dealing with and establish today's month */
       if (argc > 1)
           year = atoi (argv[1]);
       else {
           long time(), t = time (0);
           struct tm *today = localtime (&t);
           year = 1900 + today->tm_year;
           month = today->tm_mon + 1;
       }
       XmListSelectPos (list_w, month, True);

       XtManageChild (rowcol);

       XtRealizeWidget (toplevel);
       XtAppMainLoop (app);
   }

   /* set_month() -- callback routine for when a month is selected.
    * Each month is a separate, self-contained widget that contains the
    * dates as PushButton widgets.  New months do not overwrite old ones,
    * so the old month must be "unmanaged" before the new month is managed.
    * If the month has not yet been created, then figure out the dates and
    * which days of the week they fall on using clever math computations...
    */
   void
   set_month(w, client_data, call_data)
   Widget w;
   XtPointer client_data;
   XtPointer call_data;
   {
       XmListCallbackStruct *list_cbs = (XmListCallbackStruct *) call_data;
       char text[BUFSIZ];
       register char *p;
       int i, j, m, tot, day;
       static int month = -1;

       if (list_cbs->item_position == month + 1)
           return; /* same month, don't bother redrawing */

       if (month >= 0 && months[month].form)
           XtUnmanageChild (months[month].form); /* unmanage last month */
       month = list_cbs->item_position - 1; /* set new month */
       sprintf (text, "%s  %d", months[month].name, year);
       XtVaSetValues (month_label,
           XtVaTypedArg, XmNlabelString, XmRString, text, strlen (text) + 1,
           NULL);
       if (months[month].form) {
           /* it's already been created -- just manage and return */
           XtManageChild (months[month].form);
           return;
       }

       /* Create the month Form widget and dates PushButton widgets */
       months[month].form = XtVaCreateWidget ("month_form",
           xmRowColumnWidgetClass, XtParent (month_label),
           XmNorientation,    XmHORIZONTAL,
           XmNnumColumns,     6,
           XmNpacking,        XmPACK_COLUMN,
           NULL);

       /* calculate the dates of the month using science */
       /* day_number() takes day-of-month (1-31), returns day-of-week (0-6) */
       m = day_number (year, month + 1, 1);
       tot = days_in_month (year, month + 1);

       /* We are creating a whole bunch of PushButtons, but not all of
        * them have dates associated with them.  The buttons that have
        * dates get the number sprintf'ed into it.  All others get two blanks.
        */
       for (day = i = 0; i < 6; i++) {
           for (j = 0; j < 7; j++, m += (j > m && --tot > 0)) {
               char *name;
               if (j != m || tot < 1)
                   name = "  ";
               else {
                   sprintf(text, "%2d", ++day);
                   name = text;
               }
               months[month].dates[i][j] =
                   XtVaCreateManagedWidget (name,
                       xmPushButtonWidgetClass, months[month].form,
                       /* this is where we will hold the dialog later. */
                       XmNuserData,      NULL,
                       XmNsensitive,     (j % 7 == m && tot > 0),
                       XmNshadowThickness, 0,
                       NULL);
               XtAddCallback (months[month].dates[i][j],
                   XmNactivateCallback, date_dialog, day);
           }
           m = 0;
       }
       XtManageChild (months[month].form);

       /* The RowColumn widget creates equally sized boxes for each child
        * it manages.  If one child is bigger than the rest, all children
        * are that big.  If we create all the PushButtons with a 0 shadow
        * thickness, as soon as one PushButton is selected and its thickness
        * is set to 2, the entire RowColumn resizes itself.  To compensate
        * for the problem, we need to set the shadow thickness of at least
        * one of the buttons to 2, so that the entire RowColumn is
        * initialized to the right size.  But this will cause the button to
        * have a visible border and make it appear preselected, so, we have
        * to make it appear invisible.  If it is invisible then it cannot be
        * selected, but it just so happens that the last 5 days in
        * the month will never have selectable dates, so we can use any one
        * of those.  To make the button invisible, we need to unmap the
        * widget.  We can't simply unmanage it or the parent won't consider
        * its size, which defeats the whole purpose.  We can't create the
        * widget and then unmap it because it has not been realized, so it
        * does not have a window yet.  We don't want to realize and manage
        * the entire application just to realize this one widget, so we
        * set XmNmappedWhenManaged to False along with the shadow thickness
        * being set to 2.  Now the RowColumn is the right size.
        */
       XtVaSetValues (months[month].dates[5][6],
           XmNshadowThickness, 2,
           XmNmappedWhenManaged, False,
           NULL);
   }

   /* date_dialog() -- when a date is selected, this function is called.
    * Create a dialog (toplevel shell) that contains a multiline text
    * widget for memos about this date.
    */
   void
   date_dialog(w, client_data, call_data)
   Widget w;
   XtPointer client_data;
   XtPointer call_data;
   {
       int date = (int) client_data;
       Widget dialog;
       XWindowAttributes xwa;

       /* the dialog is stored in the PushButton's XmNuserData */
       XtVaGetValues (w, XmNuserData, &dialog, NULL);
       if (!dialog) {
           /* it doesn't exist yet, create it. */
           char buf[32];
           Arg args[5];
           int n, n_pos, *list;

           /* get the month that was selected -- we just need it for its name */
           if (!XmListGetSelectedPos (list_w, &list, &n_pos))
               return;
           sprintf (buf, "%s %d %d", months[list[0]-1].name, date, year);
           XtFree (list);
           dialog = XtVaCreatePopupShell ("popup",
               topLevelShellWidgetClass, XtParent (w),
               XmNtitle,            buf,
               XmNallowShellResize, True,
               XmNdeleteResponse,   XmUNMAP,
               NULL);
           n = 0;
           XtSetArg (args[n], XmNrows,     10); n++;
           XtSetArg (args[n], XmNcolumns,  40); n++;
           XtSetArg (args[n], XmNeditMode, XmMULTI_LINE_EDIT); n++;
           XtManageChild (XmCreateScrolledText (dialog, "text", args, n));
           /* set the shadow thickness to 2 so user knows there is a memo
            * attached to this date.
            */
           XtVaSetValues (w,
               XmNuserData, dialog,
               XmNshadowThickness, 2,
               NULL);
       }
       /* See if the dialog is realized and is visible.  If so, pop it down */
       if (XtIsRealized (dialog) && XGetWindowAttributes
               (XtDisplay (dialog), XtWindow (dialog), &xwa) &&
               xwa.map_state == IsViewable)
           XtPopdown (dialog);
       else
           XtPopup (dialog, XtGrabNone);
   }

   /* the rest of the file is junk to support finding the current date. */

   static int mtbl[] = { 0,31,59,90,120,151,181,212,243,273,304,334,365 };

   int
   days_in_month(year, month)
   int year, month;
   {
       int days;

       days = mtbl[month] - mtbl[month - 1];
       if (month == 2 && year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))
           days++;
       return days;
   }

   int
   day_number(year, month, day)
   int year, month, day;
   {
       /* Lots of foolishness with casts for Xenix-286 16-bit ints */

       long days_ctr;      /* 16-bit ints overflowed Sept 12, 1989 */

       year -= 1900;
       days_ctr = ((long)year * 365L) + ((year + 3) / 4);
       days_ctr += mtbl[month - 1] + day + 6;
       if (month > 2 && (year % 4 == 0))
           days_ctr++;
       return (int) (days_ctr % 7L);
   }

Contents Previous