Path: utzoo!attcan!uunet!unisoft!fai!hadar!charlesb From: charlesb@tdd.fai.com (Charles Brauer) Newsgroups: comp.windows.x Subject: Sample widget menu program. Message-ID: <7@hadar.tdd.fai.com> Date: 18 Jan 89 00:51:16 GMT Reply-To: charlesb@hadar (Charles Brauer) Organization: Fujitsu America, San Jose Lines: 778 Dear comp.windows.x: Before Christmas, I asked Bob Scheifler and Ralph Swick for help on an interesting problem I had using widgets. I feel that the results of Ralph's reply may be of interest to this news group, so I would like to offer the enclosed program as an example of widget programming. Comments and suggestions would be appreciated. The problem is this: about a year ago, Mr. Paul Vixie helped me put together a "poor man's" marching menu system, using Athena Widgets. At that time, we were running on R2 and Paul had to modify the source to implement our menu system. When R3 came along, the R2 patches would no longer work, and, besides, I wanted to implement the menu's without modifying the R3 source code. Our approach is to display a set of menus that are composed of command widgets inside a box widget. When one of the command widgets is selected, a new sub-menu should appear to the right (or down from) the first menu. Suppose that you change your mind and go back to the original menu, and select another item. You would expect the sub-menu to to disapear, and a new sub-menu to appear in its place. The problem is that you are inside a callback when you want to destroy the sub-menu. The enclosed program shows how to do it, and also, I have included Ralph's reply to me when I complained to him that the old and new sub-menu's were both "flashing" on the screen in a temporary location before they were moved to their final location. I would publically like to thank Ralph Swick for his help, and I hope he dosn't mind if I include his remarks. Ralph's reply: The most obvious way to execute the callbacks after XtDispatchEvent returns is to write your own version of XtMainLoop() and check a queue of button callbacks on each cycle. A quick hack, given below, is to take advantage of the WorkProc functions to use an already existing mechanism. You'll have to decide yourself if the use of WorkProcs is acceptable in the finished application; if the user does 'mouse-ahead', you'll wind up processing the new events before executing the previous callback. You may, however, be able to use this to your advantage. Here is a "shar" file that contains the code to solve the above problem. You should cut it off and feed it to "sh" for unpacking. Enjoy. Charles Brauer Mail Stop: B2-8 uunet.UU.NET!fai!charlesb 3055 Orchard Drive Fujitsu America, Inc. (408) 432-1300 Ext: 5226 San Jose, California 95134 -------------------------------- cut -------------------------------------- #! /bin/sh ## This is a shell archive. Remove anything before this line, then unpack ## it by saving it into a file and typing "sh file". To overwrite existing ## files, type "sh file -c". You can also feed this as standard input via ## unshar, or by typing "sh Readme <<'END_OF_Readme' X XTo make the test program, do: X X make X XThen do: X X xrdb Resources X tst X XYou should see the Menu appear in the upper Xleft corner of your screen. X XCharles Brauer END_OF_Readme if test 164 -ne `wc -c Makefile <<'END_OF_Makefile' XCFLAGS = -g XLFLAGS = -g XLIBS = -lXaw -lXmu -lXt -lX XSRC = tst.c Menu.c /X/lib/Xaw/*.c /X/lib/Xt/*.c X Xall: tst X Xtst: tst.o Menu.o X cc -o tst tst.o Menu.o $(LDFLAGS) $(LIBS) X Xsaber: $(SRC) X #load $(CFLAGS) $(SRC) -lXmu -lX X Xclean: ; rm -f *.o core tst END_OF_Makefile if test 271 -ne `wc -c Resources <<'END_OF_Resources' Xtst*font: 9x15 Xtst*geometry: 300x300+10+10 Xtst*Background: Wheat X Xtst*title.Background: Red Xtst*title.Foreground: White X Xtst*mainMenu*Background: Blue Xtst*mainMenu*Foreground: White X Xtst*levelOneMenu*Background: Green Xtst*levelOneMenu*Foreground: Black X Xtst*levelTwoMenu*Background: Tan Xtst*levelTwoMenu*Foreground: Black END_OF_Resources if test 365 -ne `wc -c Menu.c <<'END_OF_Menu.c' X#include X#include X#include X#include X#include X#include X#include X#include X#include X X#define MAX_BUTTONS 50 X#define MAX_MENUS 20 X#define MENU_BORDER_H 10 X Xtypedef void (*voidptr)(); X Xtypedef struct _menuRec { X Widget widget; /* this menu */ X Widget buttons[MAX_BUTTONS]; /* buttons in our menu */ X voidptr buttonCall[MAX_BUTTONS]; X int buttonN; /* how many used in above array */ X int active; /* an active button, -1==no active */ X} menuRec; X Xextern menuRec menus[MAX_MENUS+1]; Xextern int menuN; X Xextern void enterNewLevel(); Xextern Widget InitMenus(); XDisplay *display; X Xtypedef struct _workRec { X voidptr buttonCall; X Widget widget; X Widget closure; X caddr_t call_data; X} workRec; X XBoolean CallButtonCallback(client_data) Xcaddr_t client_data; X{ X workRec *workP = (workRec*)client_data; X (*workP->buttonCall) (workP->widget, workP->closure, workP->call_data); X return True; X} X X/************************************************************************* X * Highlight or unhighlight the given button. Used by buttonPressed. X */ X Xstatic void FlipColors(button) X Widget button; X{ X static Pixel foreground, background; X static Arg args[] = { X { XtNforeground, (XtArgVal) &foreground }, X { XtNbackground, (XtArgVal) &background } }; X X args[0].value = (XtArgVal) & foreground; X args[1].value = (XtArgVal) & background; X XtGetValues(button, args, XtNumber(args)); X X args[0].value = (XtArgVal) background; X args[1].value = (XtArgVal) foreground; X XtSetValues(button, args, XtNumber(args)); X} X X/************************************************************************* X * buttonPressed: This is a callback routine used by addExistingButton. X */ X Xstatic void buttonPressed(widget, closure, callData) X Widget widget; X Widget closure; X caddr_t callData; X{ X /* X * This routine is called on all menu button presses. Since all the X * menu buttons are always sensitive, the user may select something X * on a menu as a way to abandon something else selected elsewhere. X * X * We need to destroy any menus or applications "beyond" the menu where X * this button press occurred; they will have destroy callbacks if X * they care about this. X * X * After we've pruned the widget tree back to the point of our menu, we X * need to highlight our button and call its callback. remember that X * we are intercepting the button callback to be here in the first place. X */ X X void destroyCallback(); X Widget daddy; X menuRec *daddy_menu; X int i, k, daddy_level; X X /* daddy is the widget that this button is a member of. scan menus[] for him. */ X X daddy = XtParent(widget); X for (i = 0; i < menuN && menus[i].widget != daddy; i++) X { X } X daddy_level = i; X daddy_menu = &menus[daddy_level]; X X /* unhighlight the previously selected button. */ X if (daddy_menu->active != -1) X { X FlipColors(daddy_menu->buttons[daddy_menu->active]); X daddy_menu->active = -1; X } X X /* every widget further up the menus[] array than daddy needs to be nuked. */ X for (; menuN - 1 > daddy_level; menuN--) X { X if (menuN < 0) exit(0); X XtRemoveCallback(menus[menuN - 1].widget, XtNdestroyCallback, X destroyCallback, NULL); X X XtDestroyWidget(menus[menuN - 1].widget); X } X X FlipColors(widget); X X /* we know our widget is one of the ones in the button list for this */ X /* menu; scan for it, and then call the corresponding callback. */ X daddy_menu->active = -1; X for (i = daddy_menu->buttonN - 1; i >= 0; i--) X { X if (daddy_menu->buttons[i] == widget) X { X workRec *workP = XtNew(workRec); X daddy_menu->active = i; X workP->buttonCall = daddy_menu->buttonCall[i]; X workP->widget = widget; X workP->closure = closure; X workP->call_data = callData; X (void) XtAddWorkProc(CallButtonCallback, workP); X } X } X} X X/************************************************************************* X * widthOfBox: called by _addTitleToBox. X */ X Xint widthOfBox(w) X Widget w; X{ X static Dimension width, borderWidth; X static int wBox, hSpace; X static Arg args[] = { X { XtNwidth, (XtArgVal) &width }, X { XtNhSpace, (XtArgVal) &hSpace }, X { XtNborderWidth, (XtArgVal) &borderWidth } }; X X XtGetValues(w, args, XtNumber(args)); X wBox = width - (2 * hSpace) - (2 * borderWidth); X return wBox; X} X X/*************************************************************************/ X Xstatic void destroyCallback(widget, closure, callData) X Widget widget; X caddr_t closure; X caddr_t callData; X{ X int n, my_level; X menuRec *menu; X X my_level = -1; X for (n = 0, menu = menus; n < menuN; n++, menu++) X { X if (menu->widget == widget) X { X my_level = n; X break; X } X } X if (my_level == -1) X { X printf("destroyCallback: can't find my level.\n"); X return; X } X X for (n = my_level; n < menuN - 2; n++) X menus[n] = menus[n + 1]; X menuN--; X} X X/************************************************************************* X * _titleCallBack: used by _addTitleToBox. X */ X X Xstatic void _titleCallback(widget, closure, callData) X Widget widget; /* not used */ X caddr_t closure; /* widget to raise */ X caddr_t callData; /* not used */ X{ X Widget widget_to_raise = (Widget) closure; X Display *d = XtDisplay(widget_to_raise); X Window w = XtWindow(widget_to_raise); X X /* X * This is a hack. We are raising the window of the widget passed as X * our closure; as seen below, this widget is the parent of the box X * to which the title is being added. This is the shell widget, X * which it pretty much has to be. This all needs to change, such X * that addTitleToBox is renamed to initializeMenus() or some such. X * It's name presently indicates that it would work for any box, X * which is FALSE FALSE FALSE. X */ X X XRaiseWindow(d, w); X} X X/************************************************************************* X * _addTitleToBox: Used by InitMenus. X */ X Xstatic void _addTitleToBox(title, box) X char *title; X Widget box; X{ X Arg arg[10]; X int n; X static XtCallbackRec callback[2]; X X n = 0; X XtSetArg(arg[n], XtNwidth, widthOfBox(box)); n++; X XtSetArg(arg[n], XtNlabel, title); n++; X XtSetArg(arg[n], XtNjustify, XtJustifyLeft); n++; X callback[0].callback = _titleCallback; X callback[0].closure = (caddr_t) XtParent(box); X XtSetArg(arg[n], XtNcallback, callback); n++; X XtCreateManagedWidget("title", commandWidgetClass, box, arg, n); X} X X/************************************************************************* X * _openMenu: Used by openMenu, and by openWideMenu. X */ X X#define opt_wide 0x0001 /* default: narrow */ X Xstatic void _openMenu(parent, command, name, options) X Widget parent; X Widget command; X char *name; X int options; X{ X void enterNewLevel(); X Arg arg[10]; X int n; X X n = 0; X if (options & opt_wide) X { X XtSetArg(arg[n], XtNwidth, widthOfBox(parent)); n++; X } X else X { X /* XtSetArg(arg[n], XtNalignVert, TRUE); n++; */ X } X enterNewLevel(XtCreateWidget(name, boxWidgetClass, parent, arg, n)); X} X X X/************************************************************************* X * addExistingButton: routine used by: addButton. X */ X Xvoid addExistingButton(button, func, widget) X Widget button; X void (*func) (); X Widget widget; X{ X Arg arg[10]; X int n = 0; X menuRec *menu = &menus[menuN-1]; X X if (menuN-1 < 0) exit(0); X X XtSetArg(arg[n], XtNgrayWhenInsensitive, False); n++; X XtSetArg(arg[n], XtNsensitive, (func) ? True : False); n++; X XtSetValues(button, arg, n); X X if (func) X XtAddCallback(button, XtNcallback, buttonPressed, widget); X X menu->buttons[menu->buttonN] = button; X menu->buttonCall[menu->buttonN] = func; X menu->buttonN++; X} X X/************************************************************************* X * enterNewLevel: Used by _openMenu. (applic_widg) this function should be X * called by any application code which creates a new widget X * (form, etc) that shares an outer box with some Menus. X */ X Xvoid enterNewLevel(w) X Widget w; X{ X menuRec *menu = &menus[menuN]; X X menu->widget = w; X menu->buttonN = 0; X menu->active = -1; X menuN++; X XtAddCallback(w, XtNdestroyCallback, destroyCallback, NULL); X} X Xstatic XrmOptionDescRec options[] = { X {"-label", XtNlabel, XrmoptionSepArg, NULL} X}; X X/************************************************************************* X * The following routines are called by application programs. Routines X * above this point a supporting routines only, and not known by the X * application programmer. X */ X Xstatic Boolean scroll; X Xstatic XtResource resources[] = { X {"scroll", "Scroll", XtRBoolean, sizeof(Boolean), X (Cardinal)&scroll, XtRString, "False"}, X}; X XWidget InitMenus(application_name, menu_title, p_argc, argv) X char *application_name; X char *menu_title; X int *p_argc; X char *argv[]; X{ X Arg arg[10]; X int n; X Widget outer, toplevel; X X toplevel = XtInitialize("main", application_name, options, XtNumber(options), X p_argc, argv); X X XtGetApplicationResources( toplevel, (caddr_t)NULL, X resources, XtNumber(resources), X NULL, ZERO ); X X display = XtDisplay(toplevel); X X n = 0; X XtSetArg(arg[n], XtNallowShellResize, TRUE); n++; X XtSetValues(toplevel, arg, n); X X n = 0; X XtSetArg(arg[n], XtNx, 200); n++; X XtSetArg(arg[n], XtNy, 200); n++; X /* XtSetArg(arg[n], XtNalignVert, FALSE); n++; */ X XtSetArg(arg[n], XtNshrinkToFit, FALSE); n++; X outer = XtCreateManagedWidget(NULL, boxWidgetClass, toplevel, arg, n); X X XtRealizeWidget(toplevel); X X _addTitleToBox(menu_title, outer); X return outer; X} X X/***********************************************************************/ X XopenMenu(parent, command, name) X Widget parent; X Widget command; X char *name; X{ X _openMenu(parent, command, name, 0); X} X X/*************************************************************************/ X Xvoid openWideMenu(parent, command, name) X Widget parent; X Widget command; X char *name; X{ X _openMenu(parent, command, name, opt_wide); X} X/*************************************************************************/ X XWidget addButton(label, func, widget) X char *label; X void (*func) (); X Widget widget; X{ X Widget button; X menuRec *menu = &menus[menuN-1]; X Arg arg[10]; X int n; X X if (menuN < 0) exit(0); X n = 0; X button = XtCreateManagedWidget(label, commandWidgetClass, menu->widget, arg, n); X X addExistingButton(button, func, widget); X return button; X} X X/*************************************************************************/ X Xstatic void SetInsensitiveBorder(w, p) X Widget w; X Pixmap p; X{ X Arg arg[10]; X int n; X X n = 0; X XtSetArg(arg[n], XtNinsensitiveBorder, p); n++; X XtSetValues(w, arg, n); X} X X/*************************************************************************/ X Xvoid closeMenu() X{ X menuRec *menu = &menus[menuN-1]; X X if (menuN < 0) exit(0); X XtManageChild(menu->widget); X} END_OF_Menu.c if test 11668 -ne `wc -c tst.c <<'END_OF_tst.c' X/******************************************************************************* X * tst - test the Menu routines. X */ X X#include X#include X#include X#include X#include X#include X#include X#include X X#define MAX_BUTTONS 50 X#define MAX_MENUS 20 X#define MENU_BORDER_H 10 X Xtypedef void (*voidptr)(); X Xtypedef struct _menuRec { X Widget widget; /* this menu */ X Widget buttons[MAX_BUTTONS]; /* buttons in our menu */ X voidptr buttonCall[MAX_BUTTONS]; X int buttonN; /* how many used in above array */ X int active; /* an active button, -1==no active */ X} menuRec; X XmenuRec menus[MAX_MENUS+1]; Xint menuN; X Xextern void enterNewLevel(); Xextern Widget InitMenus(); XDisplay *display; X X/******************************************************************************/ X Xvoid Destroyed(widget, closure, callData) XWidget widget; Xcaddr_t closure; Xcaddr_t callData; X{ X exit(0); X} X X/******************************************************************************/ X Xvoid quitCommand(widget, closure, callData) XWidget widget; XWidget closure; Xcaddr_t callData; X{ X XtDestroyWidget( closure ); X} X X/******************************************************************************/ X Xvoid levelTwoMenu(widget, parent, callData) XWidget widget, parent; Xcaddr_t callData; X{ X Widget addButton(); X void closeMenu(); X X openMenu(parent, widget, "levelTwoMenu"); X X addButton("Button 1", NULL, NULL); X addButton("Quit", quitCommand, parent); X X closeMenu(); X} X X/******************************************************************************/ X Xvoid levelOneMenu(widget, parent, callData) XWidget widget, parent; Xcaddr_t callData; X{ X Widget addButton(); X void closeMenu(); X X openMenu(parent, widget, "levelOneMenu"); X X addButton("Go To Level 2", levelTwoMenu, parent); X addButton("Quit", quitCommand, parent); X X closeMenu(); X} X X/******************************************************************************/ X Xvoid DoList(widget, parent, callData) XWidget widget; XWidget parent; Xcaddr_t callData; X{ X PrintFile("/etc/printcap", parent); X} X X/******************************************************************************/ X XPrintFile(filename,parent) Xcaddr_t filename; XWidget parent; X{ X static XtCallbackRec callback[2]; X char file_name[100]; X Arg arg[100]; X int n; X Widget pWidget, scrollBar, formWidget; X FILE *fp, *fopen(); X X fp = fopen(filename,"r"); X if ( fp == 0 ) X { X fprintf(stderr, "/etc/printcap NOT EXIST"); X exit(0); X } X X n = 0; X formWidget = XtCreateWidget("scrollwindow",formWidgetClass, parent, arg, n); X enterNewLevel(formWidget); X X n = 0; X XtSetArg(arg[n], XtNtextOptions, (scrollVertical|resizeWidth)); n++; X XtSetArg(arg[n], XtNheight, 400); n++; X XtSetArg(arg[n], XtNfile, file_name); n++; X XtSetArg(arg[n], XtNwidth, 500); n++; X XtSetArg(arg[n], XtNeditType, XttextRead); n++; X X strcpy(file_name,filename); X scrollBar = XtCreateManagedWidget( NULL, asciiDiskWidgetClass, X formWidget, arg, n ); X X n = 0; X XtSetArg(arg[n], XtNlabel, "Printing Out ?"); n++; X callback[0].callback = quitCommand; X callback[0].closure = (caddr_t)parent; X XtSetArg(arg[n], XtNcallback, callback); n++; X XtSetArg(arg[n], XtNfromVert, scrollBar); n++; X XtSetArg(arg[n], XtNvertDistance, 5); n++; X pWidget = XtCreateManagedWidget(NULL,commandWidgetClass,formWidget,arg,n); X X n = 0; X XtSetArg(arg[n], XtNlabel, "Quit ?"); n++; X callback[0].callback = quitCommand; X callback[0].closure = (caddr_t)parent; X XtSetArg(arg[n], XtNcallback, callback); n++; X XtSetArg(arg[n], XtNfromVert, scrollBar); n++; X XtSetArg(arg[n], XtNfromHoriz, pWidget); n++; X XtSetArg(arg[n], XtNvertDistance, 5); n++; X XtCreateManagedWidget(NULL, commandWidgetClass, formWidget, arg, n); X X XtManageChild( formWidget ); X} X X/******************************************************************************/ X Xvoid MainMenu(outer) XWidget outer; X{ X Widget addButton(); X void closeMenu(); X X openMenu(outer, NULL, "mainMenu"); X X addButton("Go to Level 1", levelOneMenu, outer); X addButton("Go to Level 2", levelTwoMenu, outer); X addButton("List", DoList, outer); X addButton("Quit", quitCommand, outer); X X closeMenu(); X} X X/******************************************************************************/ X Xmain(argc, argv) Xint argc; Xchar **argv; X{ X Widget outer; X X outer = InitMenus("tst", "Test Program", &argc, argv); X X XtAddCallback(outer, XtNdestroyCallback, Destroyed, NULL); X MainMenu(outer); X X XtMainLoop(); X} END_OF_tst.c if test 4831 -ne `wc -c