/* Written by Dan Heller and Paula Ferguson. * Copyright 1994, O'Reilly & Associates, Inc. * Permission to use, copy, and modify this program without * restriction is hereby granted, as long as this copyright * notice appears in each copy of the program source code. * 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. */ /* app_box.c -- make an array of DrawnButtons that, when activated, * executes a program. When the program is running, the drawn button * associated with the program is insensitive. When the program dies, * reactivate the button so the user can select it again. */ #include <Xm/DrawnB.h> #include <Xm/RowColumn.h> #include#ifndef SYSV #include #else #define SIGCHLD SIGCLD #endif /* SYSV */ #define MAIL_PROG "/bin/mail" typedef struct { Widget drawn_w; char *pixmap_file; char *exec_argv[6]; /* 6 is arbitrary, but big enough */ int pid; } ExecItem; ExecItem prog_list[] = { { NULL, "terminal", { "xterm", NULL }, 0 }, { NULL, "flagup", { "xterm", "-e", MAIL_PROG, NULL }, 0 }, { NULL, "calculator", { "xcalc", NULL }, 0 }, { NULL, "xlogo64", { "foo", NULL }, 0 } }; XtAppContext app; /* application context for the whole program */ GC gc; /* used to render pixmaps in the widgets */ void reset(int), reset_btn(), redraw_button(), exec_prog(); main(argc, argv) int argc; char *argv[]; { Widget toplevel, rowcol; Pixmap pixmap; Pixel fg, bg; int i; /* we want to be notified when child programs die */ signal (SIGCHLD, reset); XtSetLanguageProc (NULL, NULL, NULL); toplevel = XtVaAppInitialize (&app, "Demos", NULL, 0, &argc, argv, NULL, NULL); rowcol = XtVaCreateWidget ("rowcol", xmRowColumnWidgetClass, toplevel, XmNorientation, XmHORIZONTAL, NULL); /* get the foreground and background colors of the rowcol * so the gc (DrawnButtons) will use them to render pixmaps. */ XtVaGetValues (rowcol, XmNforeground, &fg, XmNbackground, &bg, NULL); gc = XCreateGC (XtDisplay (rowcol), RootWindowOfScreen (XtScreen (rowcol)), NULL, 0); XSetForeground (XtDisplay (rowcol), gc, fg); XSetBackground (XtDisplay (rowcol), gc, bg); for (i = 0; i < XtNumber (prog_list); i++) { /* the pixmap is taken from the name given in the structure */ pixmap = XmGetPixmap (XtScreen (rowcol), prog_list[i].pixmap_file, fg, bg); /* Create a drawn button 64x64 (arbitrary, but sufficient) * shadowType has no effect till pushButtonEnabled is false. */ prog_list[i].drawn_w = XtVaCreateManagedWidget ("dbutton", xmDrawnButtonWidgetClass, rowcol, XmNwidth, 64, XmNheight, 64, XmNpushButtonEnabled, True, XmNshadowType, XmSHADOW_ETCHED_OUT, NULL); /* if this button is selected, execute the program */ XtAddCallback (prog_list[i].drawn_w, XmNactivateCallback, exec_prog, &prog_list[i]); /* when the resize and expose events come, redraw pixmap */ XtAddCallback (prog_list[i].drawn_w, XmNexposeCallback, redraw_button, (XtPointer)pixmap); XtAddCallback (prog_list[i].drawn_w, XmNresizeCallback, redraw_button, (XtPointer)pixmap); } XtManageChild (rowcol); XtRealizeWidget (toplevel); XtAppMainLoop (app); } /* redraw_button() -- draws the pixmap into its DrawnButton * using the global GC. Get the width and height of the pixmap * being used so we can either center it in the button or clip it. */ void redraw_button(button, client_data, call_data) Widget button; XtPointer client_data; XtPointer call_data; { Pixmap pixmap = (Pixmap) client_data; XmDrawnButtonCallbackStruct *cbs = (XmDrawnButtonCallbackStruct *) call_data; int srcx, srcy, destx, desty, pix_w, pix_h; int drawsize, border; Dimension bdr_w, w_width, w_height; short hlthick, shthick; Window root; /* get width and height of the pixmap. don't use srcx and root */ XGetGeometry (XtDisplay (button), pixmap, &root, &srcx, &srcx, &pix_w, &pix_h, &srcx, &srcx); /* get the values of all the resources that affect the entire * geometry of the button. */ XtVaGetValues (button, XmNwidth, &w_width, XmNheight, &w_height, XmNborderWidth, &bdr_w, XmNhighlightThickness, &hlthick, XmNshadowThickness, &shthick, NULL); /* calculate available drawing area, width 1st */ border = bdr_w + hlthick + shthick; /* if window is bigger than pixmap, center it; else clip pixmap */ drawsize = w_width - 2 * border; if (drawsize > pix_w) { srcx = 0; destx = (drawsize - pix_w) / 2 + border; } else { srcx = (pix_w - drawsize) / 2; pix_w = drawsize; destx = border; } drawsize = w_height - 2 * border; if (drawsize > pix_h) { srcy = 0; desty = (drawsize - pix_h) / 2 + border; } else { srcy = (pix_h - drawsize) / 2; pix_h = drawsize; desty = border; } XCopyArea (XtDisplay (button), pixmap, cbs->window, gc, srcx, srcy, pix_w, pix_h, destx, desty); } /* exec_proc() -- the button has been pressed; fork() and call * execvp() to start up the program. If the fork or the execvp * fails (program not found?), the sigchld catcher will get it * and clean up. If the program is successful, set the button's * sensitivity to False (to prevent the user from execing again) * and set pushButtonEnabled to False to allow shadowType to work. */ void exec_prog(drawn_w, client_data, call_data) Widget drawn_w; XtPointer client_data; XtPointer call_data; { ExecItem *program = (ExecItem *) client_data; XmDrawnButtonCallbackStruct *cbs = (XmDrawnButtonCallbackStruct *) call_data; switch (program->pid = fork ()) { case 0: /* child */ execvp (program->exec_argv[0], program->exec_argv); perror (program->exec_argv[0]); /* command not found? */ _exit (255); case -1: printf ("fork() failed.\n"); } /* The child is off executing program... parent continues */ if (program->pid > 0) { XtVaSetValues (drawn_w, XmNpushButtonEnabled, False, NULL); XtSetSensitive (drawn_w, False); } } /* reset() -- a program died, so find out which one it was and * reset its corresponding DrawnButton widget so it can be reselected */ void reset(dummy) int dummy; { int pid, i; #if defined(SYSV) || defined(_CH_) int status; #else union wait status; #endif /* SYSV */ printf("reset() with signal(SICHLD, reset) called\n"); /* we want to be notified when child programs die */ signal (SIGCHLD, reset); if ((pid = wait (&status)) == -1) /* an error of some kind (fork probably failed); ignore it */ return; for (i = 0; i < XtNumber (prog_list); i++) if (prog_list[i].pid == pid) { /* program died -- now reset item. But not here! */ XtAppAddTimeOut (app, 0, reset_btn, prog_list[i].drawn_w); return; } printf ("Pid #%d ???\n", pid); /* error, but not fatal */ } /* reset_btn() -- reset the sensitivity and pushButtonEnabled resources * on the drawn button. This cannot be done within the signal * handler or we might step on an X protocol packet since signals are * asynchronous. This function is safe because it's called from a timer. */ void reset_btn(drawn_w) Widget drawn_w; /* client_data from XtAppAddTimeOut() */ { XtVaSetValues(drawn_w, XmNpushButtonEnabled, True, NULL); XtSetSensitive (drawn_w, True); }