solomon@speedy.cs.wisc.edu (Marvin Solomon) (07/19/88)
A little while back somebody asked how to create popup menus and got a humorous reply that the X Consortium decided that menus are a bad idea. Well I decided I really wanted to provide popup menus with my application (a troff previewer), so I spend a week wrestling with the X Toolkit trying to figure out how to do it. I've had some limited success, but what I did was by no means straightforward, so I thought I'd share my experiences with this newsgroup in order that 1. Others can benfit from my experience 2. Somebody can tell me how I really SHOULD have done it 3. Perhaps the next version of the intrinsics will may things a bit easier. First, I looked at all the examples of applications I could find to see how others did it, and discovered that although the Toolkit intrinsics have all kinds of stuff that seems to be designed for the purpose, ABSOLUTELY NOBODY USES IT! Among other places, I looked in xterm, twm, and awm (which uses a modified version of the rtl menu package), xman, and xmh. The stuff in contrib/menus seems to use some of it, but that whole directory seems not to have been updated to V11R2. I can't get close to getting it to compile. All the working applications use raw Xlib functions to draw and manage their menus (and they each do it differently). All that stuff in Chapter 7 (Popup Widgets) seems to be virtually unused. Moreoever, I've come to the conclusion that at least some of it is unusable. But more on that later. For concreteness sake, suppose I want a Macintosh-like interface: There's a menu bar with a bunch of buttons. A mouse press in one of these buttons pops up a menu. Dragging the cursor through the menu without releasing the mouse button highlights menu items. Releasing the button while one of the menu items is highlighted invokes a corresponding action and makes the popup menu go away. Dragging the cursor outside the popup menu without releasing the button makes the menu go away without causing any action. For the menu, bar I create a Box widget with some Command widgets inside it. At initialization time, I create the menu itself as a popup shell containing a Box containing a set of Buttons. (By the way, the documentation gives no advice on what widget_class to use for the popup-shell. I found that I had to make it overrideShellWidgetClass to prevent the window manager from getting into the act when the window pops up). The first problem is how to get the menu to pop up. According to the manual, there are three ways to get the menu to pop up: By calling XtPopup() explicitly (from inside a callback), by using XtCallbackExclusive() (or one of its friends) as a callback procedure, or by using MenuPopup() in a translation. The first two approaches create the popup only when the Command widget does its callbacks--by default, when the mouse button is released. I tried using XtOverrideTranslations to override the default "<Btn1Down>: set()\n <Btn1Up>: notify() unset()" (NB: this is from the code; the manual has it wrong) with the actions "<Btn1Down>: set() notify()\n <Btn1Up>: unset()" but somehow, this doesn't work quite right (more on this later). The third approach worked better. I use "<Btn1Down>: set() MenuPopup(menu)\n" where "menu" is the name of the popup shell. The second problem is how to get the menu to pop up in a reasonable place-- i.e., near the cursor. If the shell widget is not of class overrideShellWidgetClass, the window manager kindly askes me to size and locate the menu, which is clearly ridiculous. Actions set up using the translation manager are passed an event structure, which contains the right information, but unfortunatly the buildin action for MenuPopup() throws this information away. What I settled on was to add an XtNpopupCallback procedure to set the XtNx and XtNy attributes of the popup shell. I pass a pointer to the Command widget that caused the popup as the 'closure' of the callback. It queries the (x,y) position of that widget and makes the menu pop up slightly offset from there. On reflection, I might have been better off to define my own popup action and ignore the "handy" MenuPopup(). The third problem is to make the popup menu behave properly. That was relatively easy. I overrode its transations with "<EnterWindow>: highlight() set()" The fourth and hardest problem was to make the menu go away. Once again, there are three methods documented, none of which seems to work quite right: call XtPopdown() from a callback, use XtCallbackPopdown() as a callback, or use MenuPopdown() as an action. The latter would seem to be the best, but I couldn't figure out how to use it. As a matter of fact, I've convinced myself that MenuPopdown() is totally unusable. The only reasonable action to bind it to would be <Btn1Up>. MenuPopdown() complains if the target widget is not a shell widget. Since MenuPopdown() has no arguments, its target widget is always the widget to which the translation is bound. But if I set a translation for <Btn1UP> in the popup shell, the Command buttons in the window never see the button up events, so I can't select anything with them. I can't modify them to activate on button down, since the butten is already down, and activating them on EnterWidow is obviously wrong too. The solution I finally came up with is to add an XtCallbackPopdown() action to EACH AND EVERY button in the menu, passing an XtPopdownID as the closure. One final problem is how to make the popup menu go way if the user doesn't choose any of its options. I still haven't got that to work, but I have one more trick I might try: Define a translation for LeaveWindow on the Box widget containing the menu. Right now, if I pop up the menu with a button press and then release the button somewhere other than one of the menu items, the menu stays popped up. If I go back and click in one of the menu items, the corresponding action is taken and the menu goes away. Curiously enough, there doesn't seem to be any grab active: I can still activate other buttons in the menu bar. All this despite that fact that the builtin action for MenuPopup() sets "spring-loaded" and (so far as I can make out) does an exclusive grab. The same thing happend if I use XtCallbackExclusive() instead of MenuPopup() to raise the menu. Only in this case, the menu does not "feel" the mouse until I release the button for the first time. I intended to include the whole test program at the end of this message, but as it's already too long, I'll send the test program under separate cover. Marvin Solomon Computer Sciences Department University of Wisconsin, Madison WI solomon@cs.wisc.edu or seismo!uwvax!solomon
solomon@speedy.cs.wisc.edu (Marvin Solomon) (07/19/88)
Here is my best effort at popup menus using Xt and Xaw. See my previous message for discussion. (If you haven't seen the "previous message", just wait a bit). ------------------------------- #include <X11/Intrinsic.h> #include <X11/StringDefs.h> #include <X11/Cardinals.h> #include <X11/Command.h> #include <X11/Box.h> #include <X11/Label.h> #include <X11/Shell.h> #include <stdio.h> /* Command line options table. Only resources are entered here...there is a pass over the remaining options after XtParseCommand is let loose. */ static XrmOptionDescRec options[] = { {"-label", "*label", XrmoptionSepArg, NULL} }; /* * Report the syntax for calling xlabel */ Syntax(call) char *call; { fprintf( stderr, "Usage: %s\n", call ); } /* ARGSUSED */ void callback(w, str, dummy) Widget w; char *str; int dummy; { printf("%s button\n", str); fflush(stdout); } /* ARGSUSED */ void done(w, str, dummy) Widget w; char *str; int dummy; { exit(0); } /* ARGSUSED */ void set_menu_position(shell_widget, enable_widget, dummy) Widget enable_widget, shell_widget; int dummy; { Position x, y; Arg args[2]; XtTranslateCoords(enable_widget, (Position)5, (Position)5, &x, &y); XtSetArg(args[0], XtNx, x); XtSetArg(args[1], XtNy, y); XtSetValues(shell_widget, args, TWO); } void main(argc, argv) unsigned int argc; char **argv; { Widget toplevel, menu_bar, button, popupshell, menu, popupbutton; XtPopdownIDRec popdown_pair; XtTranslations button_actions, popup_actions; Arg arg; toplevel = XtInitialize( "popup_test", "XPopupTest", options, XtNumber(options), &argc, argv ); if (argc != 1) Syntax(argv[0]); button_actions = XtParseTranslationTable( "<EnterWindow>: highlight() set()\n"); popup_actions = XtParseTranslationTable( "<Btn1Down>: set() MenuPopup(menu)\n"); /**** menu bar ****/ menu_bar = XtCreateManagedWidget( "menu_bar", boxWidgetClass, toplevel, (Arg *)0, ZERO); /**** menu bar items ****/ button = XtCreateManagedWidget( "yes", commandWidgetClass, menu_bar, (Arg *)0, ZERO ); XtAddCallback(button, XtNcallback, callback, (caddr_t)"yes"); popupbutton = XtCreateManagedWidget( "ShowMenu", commandWidgetClass, menu_bar, (Arg *)0, ZERO ); XtOverrideTranslations(popupbutton, popup_actions); /**** popup menu ****/ popupshell = XtCreatePopupShell("menu", overrideShellWidgetClass, toplevel, (Arg *)0, ZERO); menu = XtCreateManagedWidget( "PopupMenu", boxWidgetClass, popupshell, (Arg *)0, ZERO); XtAddCallback(popupshell, XtNpopupCallback, set_menu_position, (caddr_t)popupbutton); /* XtAddCallback(popupbutton, XtNcallback, XtCallbackExclusive, (caddr_t)popupshell); */ popdown_pair.enable_widget = popupbutton; popdown_pair.shell_widget = popupshell; /**** popup menu items ****/ /* menu option 1 */ button = XtCreateManagedWidget( "choice1", commandWidgetClass, menu, (Arg *)0, ZERO ); XtAddCallback(button, XtNcallback, callback, (caddr_t)"button1"); XtAddCallback(button, XtNcallback, XtCallbackPopdown, (caddr_t)&popdown_pair); XtOverrideTranslations(button, button_actions); /* menu option 2 */ button = XtCreateManagedWidget( "choice2", commandWidgetClass, menu, (Arg *)0, ZERO ); XtAddCallback(button, XtNcallback, callback, (caddr_t)"button2"); XtAddCallback(button, XtNcallback, XtCallbackPopdown, (caddr_t)&popdown_pair); XtOverrideTranslations(button, button_actions); /* menu option 3 */ button = XtCreateManagedWidget( "quit", commandWidgetClass, menu, (Arg *)0, ZERO ); XtAddCallback(button, XtNcallback, done, (caddr_t)"done"); XtOverrideTranslations(button, button_actions); XtRealizeWidget(toplevel); XtMainLoop(); } Marvin Solomon Computer Sciences Department University of Wisconsin, Madison WI solomon@cs.wisc.edu or seismo!uwvax!solomon
swick@ATHENA.MIT.EDU (Ralph R. Swick) (07/20/88)
> First, I looked at all the examples of applications I could find to see > how others did it, and discovered that although the Toolkit intrinsics > have all kinds of stuff that seems to be designed for the purpose, ABSOLUTELY > NOBODY USES IT! You have here only another instance of resource allocation and scheduling conflicts. The clients you cite were all built in pre-Xt days and therefore had to (independently) do everything the hard way. The task of fixing things that work has been very low on our priority list. The popup support in Xt does work. There are some kinks that will be smoothed out in R3, as well as widgets which have more mechanism (and less policy :-) for using the popup support in Xt. It sounds like you are basically on the right track in your adopted role as widget writer trying to figure out how to use the mechanisms. Your frustration in being forced to play that role is understandable. We will be happy to deal with specific bugs in Xt and Xaw as they are brought to our attention.
blumen@parcvax.Xerox.COM (Robert Blumen) (07/20/88)
In article <6005@spool.cs.wisc.edu> solomon@speedy.cs.wisc.edu (Marvin Solomon) writes: >A little while back somebody asked how to create popup menus and got a humorous >reply that the X Consortium decided that menus are a bad idea. Well I decided >I really wanted to provide popup menus with my application (a troff previewer), >so I spend a week wrestling with the X Toolkit trying to figure out how to >do it. I've had some limited success, but what I did was by no means >straightforward, so I thought I'd share my experiences with this newsgroup >in order that > 2. Somebody can tell me how I really SHOULD have done it ok... >For concreteness sake, suppose I want a Macintosh-like interface: There's >a menu bar with a bunch of buttons. A mouse press in one of these buttons >pops up a menu. Dragging the cursor through the menu without releasing the >mouse button highlights menu items. Releasing the button while one of the menu >items is highlighted invokes a corresponding action and makes the popup menu >go away. Dragging the cursor outside the popup menu without releasing the >button makes the menu go away without causing any action. > I used the same, except for the last sentence. > >The second problem is how to get the menu to pop up in a reasonable place-- >i.e., near the cursor.What I settled on was to add an >XtNpopupCallback procedure to set the XtNx and XtNy attributes of the popup >shell. What I don't like about this is that XtNpopupCallback should be left to the application programmer, and I think that menus should be provided at a lower level. What I did is not much better... > >The fourth and hardest problem was to make the menu go away. Once again, >there are three methods documented, none of which seems to work quite right: >call XtPopdown() from a callback, use XtCallbackPopdown() as a callback, or >use MenuPopdown() as an action. The latter would seem to be the best, but >I couldn't figure out how to use it. Use MenuPopdown as a translation for the Unmap action on the override shell. > Marvin Solomon My solution is the following: ------- I created three new widget classes: menuShell, menuBox, and menuButton. ------- MenuShell is a subclass of overrideShell with the following changes: 1. MenuShellClassPart has one field that does nothing. It has a MenuShellPart as follows : typedef struct _MenuShell_Part { Boolean putback_cursor; /* whether to put cursor back after popdown */ Boolean menu_under_cursor; /* whether to pup menu up under the cursor*/ /* private */ Position cursor_x, cursor_y; } MenuShellPart; 2. It has the DEFAULT translations static char defaultTranslations[] = "<BtnUp>: MenuPopdown() select()\n\ <Map>: setup()"; Where Select is a function that does a depth-first search of the menuShell's widget tree until it finds one that is a menuButton Widget that has its command.set field == TRUE. It then calls XtCallCallbacks for this widget. Setup is a function that calls XQueryPointer to find the pointer location, and sets the cursor_x and cursor_y to the cursor position. 3. The Initialize routine sets the shell.create_popup_child_proc to the function "GetLocation". The create_popup_child_proc is called everytime that the popup is mapped. GetLocation uses XQueryPointer to find the pointer location, then calls XtMakeGeometryRequest with request_mode = CWX | CWY and the x and y fields set to positions near the cursor. This positions the menu properly, near the cursor. ------- MenuBox is a subclass of box with the following changes: 1. The MenuBoxClassRec has one field that does nothing. The menuBoxPart has the same. 2. MenuBox has its own change_managed procedure which makes sure that all of the buttons in the menu have the same width. The way it does this is that it calls XtQueryGeometry on all of its managed children to find out their preferred width, and keeps track of the maximum width of any child. Then, for any children that are smaller than the max, it calls XtResizeWidget on that child. Then, it calls its superclass's change_managed to do the layout of the box. ------- MenuButton is a subclass of Command with the following changes: 1. MenuButtonClassPart has one field that does nothing MenuButtonPart is the same. 2. MenuButton has new default translations: static char defaultTranslations[] = "<EnterWindow>: set() highlight()\n\ <LeaveWindow>: unset(NoRedisplay) unhighlight()"; So when the cursor enters the menu button, it is both highlighted and inverted. ------- How to use them: Create a menu shell with XtCreatePopupShell. Create a menu box as a managed child of the menu shell with XtCreateManagedWidget. I know that the manual says not to use XtCreateManagedWidget for a popup. It doesn't seem to work their way. Create a labelWidget as a managed child of the menu box, if desired. This will be the title of the menu. Create as many menu buttons as managed children of the menu box. For each button, add a callback which will be called when that button is selected. To get the menu to pop up, create a Command widget, and add the following translation with XtOverrrideTranslations: "<BtnDown>: MenuPopup( menu_name_goes_here )"; ------- What's so great about this? Now that the widgets are incorporated into widgets, it is very easy to create menus without understanding how they work or worrying about callbacks, translations, etc. They have the desired behavior, and they fit right into the widget hierarchy, simplifying future programming tasks. Robert Blumen blumen.pa@xerox.com
hds@io.UUCP (H.David Scarbro) (07/20/88)
The following is from Marvin Solomon's recent netnews message: >One final problem is how to make the popup menu go way if the user doesn't >choose any of its options. I still haven't got that to work, but I have >one more trick I might try: Define a translation for LeaveWindow on the >Box widget containing the menu. Right now, if I pop up the menu with a button >press and then release the button somewhere other than one of the menu items, >the menu stays popped up. If I go back and click in one of the menu items, >the corresponding action is taken and the menu goes away. Curiously enough, >there doesn't seem to be any grab active: I can still activate other buttons >in the menu bar. All this despite that fact that the builtin action for >MenuPopup() sets "spring-loaded" and (so far as I can make out) does an >exclusive grab. The same thing happend if I use XtCallbackExclusive() >instead of MenuPopup() to raise the menu. Only in this case, the menu does >not "feel" the mouse until I release the button for the first time. The following is from Ralph Swick's response: > The popup support in Xt does work. There are some kinks that will be > smoothed out in R3, as well as widgets which have more mechanism > (and less policy :-) for using the popup support in Xt. Using Marvin's example program, I found one of the "kinks" with MenuPopup(). The line in XtConvertStringToBoolean() in TMstate.c which is currently: *bP = (Boolean) *(int *)toVal.addr; should read: *bP = *(Boolean*)toVal.addr; By ironing out this "kink" and adding an override to call MenuPopdown() on <Btn1Up> I made Marvin's example program work correctly. This change doesn't fix the "kinks" in XtCallbackExclusive. From looking at this code, it appears there might be some state information that the translation manager is changing as a part of the MenuPopup() that needs to be changed when XtCallbackExclusive() is called. I've already sent the MenuPopup() fix to xbugs. ---- David Scarbro UUCP: ..!{sun!sunne,mit-eddie}!ileaf!hds Interleaf, Inc. Internet: hds@ileaf.com Ten Canal Park Phone: (617)577-9800 x6608 Cambridge, MA 02141
solomon@speedy.cs.wisc.edu (Marvin Solomon) (07/21/88)
In article <8807191717.AA00552@LYRE.MIT.EDU> swick@ATHENA.MIT.EDU (Ralph R. Swick) writes: >> First, I looked at all the examples of applications I could find to see >> how others did it, and discovered that although the Toolkit intrinsics >> have all kinds of stuff that seems to be designed for the purpose, ABSOLUTELY >> NOBODY USES IT! > >You have here only another instance of resource allocation and scheduling >conflicts. The clients you cite were all built in pre-Xt days and therefore >had to (independently) do everything the hard way. The task of fixing >things that work has been very low on our priority list. > >The popup support in Xt does work. There are some kinks that will be >smoothed out in R3, as well as widgets which have more mechanism >(and less policy :-) for using the popup support in Xt. > >It sounds like you are basically on the right track in your adopted role >as widget writer trying to figure out how to use the mechanisms. Your >frustration in being forced to play that role is understandable. We >will be happy to deal with specific bugs in Xt and Xaw as they are >brought to our attention. David Scarbro of InterLeaf suggested an enhancement for my sample program. It now works exactly as I want it. It involves fixing a specific bug in Xt. More on this below. As the above message (as well as one by Robert Blumen of Xerox PARC) suggests, my problem is that I'm trying to take on the role of widget creater when I'd much rather be widget user. The main problem is that right now, the set of Xt-conforming widgets (i.e., lib/Xaw) is quite small. Hopefully, that will change in the near future. In the mean time, we've got lots of mechanism and precious little policy. Otherwise stated: The Intrinsics make everything possible, and nothing easy :-). A philosophical question: Where is the Toolkit (with a capital "T") headed? It seems to me that the "object-oriented" design dictates that there be no such thing as "application programs". Rather, each piece of application functionality is realized as a new widget class. This is the way xterm is (more or less) designed, and it is the design I finally came up with for my troff previewer: I defined a new Proof widget class. The justification for this approach goes as follows: If a widget class is only desiged to be instantiated (as opposed to inherited), it is likely to be either too inflexible, or loaded down with a zillion options, which make it complicated to understand and use. The object-oriented approach says that instead of giving a widget an option (resource) for every conceivable way that it might be customized, it is better to create custom versions by inheritence. For example, the Box widget class could be extended with a XtNlayout resource with values of "vertical", "horizontal", or "unconstrained" (the current behavior), which would make it easier to use as a popup menu. But a much better approach is to define a MenuBox subclass, as Robert Blumen suggests, which is not only vertical, but also resizes the members so they're all the same width. There are two drawbacks to this approach. First, it is an unfamiliar style of programming. However, once people get used to it, I think they'll like it. A little discussion in the Widgets document (including at least one realistic example) can go a long way to ease the path. The second problem is more serious: The Toolkit (both intrinsics and existing Athena widgets) seem to make the assumption that applications programmers are simpletons that need to be lead by the hand, whereas widget programmers are gurus initiated into the archane rites of toolkitism. The object-oriented approach erases the distinction. Existing widgets are admirably easy to use if they do what you want by default, and moderately easy to use if they can be customized by XtSetAttributes(). Creating a new widget class is mildly painful if you can find an existing class definition that is "close enough" to hack on (my Proof widget is a hacked Mailbox widget, which is itself a hack of the Clock widget, according to a comment in Mailbox.h). Creating a new widget class from scratch is VERY confusing to the uninitiated. It occurs to me that a lot of pain is caused by trying to do object-oriented programming in a language that doesn't support it. Is there likely to be a C++ version of the Toolkit? Yes I know about InterViews, but it seems to be totally incompatible with the Intrisics, and it is not clear to me how to mix widgets from the two worlds. Another approach might be a pre-processor that takes a specification, looking something like SmallTalk, and translates into FooP.h, Foo.h, and Foo.c. But I'm getting too far afield. Several people have indicated privately that they appreciate my sample popup program. Fixes mailed to me by David Scarbro make it much better. He found a bug in the Intrinsics that seems to be the root of all my problems with MenuPopdown(). With that bug fixed, adding MenuPopdown() to my program is straightforward. I quote both fixes below (without permission--I hope he doesn't mind). I hope he reported the bug fix to bugs@mit. If not, perhaps somebody at MIT reading this will take it as a bug report. > I found your description of the perils of trying to use Xt to create popups > very helpful. I been playing with the Xt and had just started to try building > a popup out of widgets. Your message saved me a lot of time. Thanks. > > I think I solved the problem that you were having with MenuPopup(). By using > a debugger, I discovered that _XtMenuPopup() was calling _XtPopup as follows: > _XtPopup(popup_shell, XtGrabNonexclusive, FALSE). So your hunch was correct, > no grab was active. I chased this problem to a bug in > XtConvertStringToBoolean(). To fix this bug, I changed: > *bP = (Boolean) *(int*)toVal.addr; > to > *bP = *(Boolean*)toVal.addr; > Now _XtPopup appears to be getting called correctly. > > With one additional change to your program, popups seem work. I had to add > another translation mapping. > > ---- > H. David Scarbro UUCP: ..!{sun!sunne,mit-eddie}!ileaf!hds > Interleaf, Inc. Internet: hds@ileaf.com > Ten Canal Park Phone: (617)577-9800 x6608 > Cambridge, MA 02141 The changes to my test program follow. I'm still not happy with the kludge I use for placement of the popup. I may take a hint from Robert Blumen and use XQueryPointer and requests to a geometry manager rather than XtTranslateCoords and XtSetValues for XtNx, and XtNy. ========================= cut here, use patch ============== RCS file: popup.c,v retrieving revision 1.2 diff -b -c -r1.2 popup.c *** /tmp/,RCSt1016586 Wed Jul 20 12:04:02 1988 --- popup.c Wed Jul 20 11:11:50 1988 *************** *** 64,70 { Widget toplevel, menu_bar, button, popupshell, menu, popupbutton; XtPopdownIDRec popdown_pair; ! XtTranslations button_actions, popup_actions; Arg arg; toplevel = XtInitialize( "popup_test", "XPopupTest", --- 64,70 ----- { Widget toplevel, menu_bar, button, popupshell, menu, popupbutton; XtPopdownIDRec popdown_pair; ! XtTranslations button_actions, popup_actions, popdown_actions; Arg arg; toplevel = XtInitialize( "popup_test", "XPopupTest", *************** *** 77,82 "<EnterWindow>: highlight() set()\n"); popup_actions = XtParseTranslationTable( "<Btn1Down>: set() MenuPopup(menu)\n"); /**** menu bar ****/ menu_bar = XtCreateManagedWidget( "menu_bar", boxWidgetClass, toplevel, --- 77,84 ----- "<EnterWindow>: highlight() set()\n"); popup_actions = XtParseTranslationTable( "<Btn1Down>: set() MenuPopup(menu)\n"); + popdown_actions = XtParseTranslationTable( + "<Btn1Up>: MenuPopdown()\n"); /**** menu bar ****/ menu_bar = XtCreateManagedWidget( "menu_bar", boxWidgetClass, toplevel, *************** *** 95,100 /**** popup menu ****/ popupshell = XtCreatePopupShell("menu", overrideShellWidgetClass, toplevel, (Arg *)0, ZERO); menu = XtCreateManagedWidget( "PopupMenu", boxWidgetClass, popupshell, (Arg *)0, ZERO); XtAddCallback(popupshell, --- 97,103 ----- /**** popup menu ****/ popupshell = XtCreatePopupShell("menu", overrideShellWidgetClass, toplevel, (Arg *)0, ZERO); + XtOverrideTranslations(popupshell, popdown_actions); menu = XtCreateManagedWidget( "PopupMenu", boxWidgetClass, popupshell, (Arg *)0, ZERO); XtAddCallback(popupshell, ========== end of patch ============= Marvin Solomon Computer Sciences Department University of Wisconsin, Madison WI solomon@cs.wisc.edu or seismo!uwvax!solomon
v40f00@exunido.uucp (Franz-Josef Stewing) (07/26/88)
Hello, Let us add some more on the guessing about how to create popup menus with Xt: With the help of the notes by Marvin Solomon, H.David Scarbro and Robert Blumen we found following solution: ===================================================================== #include <X11/Intrinsic.h> #include <X11/StringDefs.h> #include <X11/Cardinals.h> #include <X11/Command.h> #include <X11/Box.h> #include <X11/Label.h> #include <X11/Shell.h> #include <stdio.h> /* Command line options table. Only resources are entered here...there is a pass over the remaining options after XtParseCommand is let loose. */ static XrmOptionDescRec options[] = { {"-label", "*label", XrmoptionSepArg, NULL} }; Widget toplevel, menu_bar, button, popupshell, menu, popupbutton; /* * Report the syntax for calling xlabel */ Syntax(call) char *call; { fprintf( stderr, "Usage: %s\n", call ); } /* ARGSUSED */ void callback(w, str, dummy) Widget w; char *str; int dummy; { printf("%s button\n", str); fflush(stdout); } /* ARGSUSED */ void done(w, str, dummy) Widget w; char *str; int dummy; { exit(0); } /* ARGSUSED */ void set_pos(w, event, params, num_params) Widget w; XEvent *event; String params; Cardinal *num_params; { Position x, y; Arg args[2]; Window win; XTranslateCoordinates(XtDisplay(w), XtWindow(w), XDefaultRootWindow(XtDisplay(w)), event->xbutton.x, event->xbutton.y, &x, &y, &win); XtMoveWidget(popupshell, x, y); } void main(argc, argv) unsigned int argc; char **argv; { XtTranslations button_actions, popup_actions, popdown_actions; Arg arg; static XtActionsRec action_table[] = { {"set_pos", (XtActionProc)set_pos} }; XtAddActions(action_table, ONE); toplevel = XtInitialize( "popup_test", "XPopupTest", options, XtNumber(options), &argc, argv ); if (argc != 1) Syntax(argv[0]); button_actions = XtParseTranslationTable( "<EnterWindow>: highlight() set()\n"); popup_actions = XtParseTranslationTable( "<Btn1Down>: set() set_pos() MenuPopup(menu)\n"); popdown_actions = XtParseTranslationTable( "<Btn1Up>: MenuPopdown()\n"); /**** menu bar ****/ menu_bar = XtCreateManagedWidget( "menu_bar", boxWidgetClass, toplevel, (Arg *)0, ZERO); /**** menu bar items ****/ button = XtCreateManagedWidget( "yes", commandWidgetClass, menu_bar, (Arg *)0, ZERO ); XtAddCallback(button, XtNcallback, callback, (caddr_t)"yes"); popupbutton = XtCreateManagedWidget( "ShowMenu", commandWidgetClass, menu_bar, (Arg *)0, ZERO ); XtOverrideTranslations(popupbutton, popup_actions); /**** popup menu ****/ popupshell = XtCreatePopupShell("menu", overrideShellWidgetClass, toplevel, (Arg *)0, ZERO); XtOverrideTranslations(popupshell, popdown_actions); menu = XtCreateManagedWidget( "PopupMenu", boxWidgetClass, popupshell, (Arg *)0, ZERO); /**** popup menu items ****/ /* menu option 1 */ button = XtCreateManagedWidget( "choice1", commandWidgetClass, menu, (Arg *)0, ZERO ); XtAddCallback(button, XtNcallback, callback, (caddr_t)"button1"); XtOverrideTranslations(button, button_actions); /* menu option 2 */ button = XtCreateManagedWidget( "choice2", commandWidgetClass, menu, (Arg *)0, ZERO ); XtAddCallback(button, XtNcallback, callback, (caddr_t)"button2"); XtOverrideTranslations(button, button_actions); /* menu option 3 */ button = XtCreateManagedWidget( "quit", commandWidgetClass, menu, (Arg *)0, ZERO ); XtAddCallback(button, XtNcallback, done, (caddr_t)"done"); XtOverrideTranslations(button, button_actions); XtRealizeWidget(toplevel); XtMainLoop(); } ========================================================================== The main changes to Marvin's second program were - changing the Callback function "set_menu_position" to the function "set_pos" which was added to a translation table and - using XtMoveWidget to let the menu popup at an appropiate place. This programm should work properly, i.e. let menus disappear when releasing the button _and_ appear where you expect them to. (Of course only with the change to the Toolkit function "XtConvertStringToBoolean" which David Scarbro suggested) Since we are rather new at the Toolkit (and didn't understand Chapter 7) we expect our solution a bit clumsy and we hope for some suggestions how to improve it. Lutz Wiggershaus University of Dortmund, Informatics IV. email: wiggersh@balduin.ls4.informatik.uni-dortmund.de