Path: utzoo!attcan!uunet!lll-winken!lll-tis!helios.ee.lbl.gov!pasteur!ames!sgi!parcvax!blumen From: blumen@parcvax.Xerox.COM (Robert Blumen) Newsgroups: comp.windows.x Subject: Re: How do I create popup menus with Xt? Keywords: menus popup toolkit x11 widgets Message-ID: <926@parcvax.Xerox.COM> Date: 19 Jul 88 17:25:35 GMT References: <6005@spool.cs.wisc.edu> Reply-To: blumen@parcvax.xerox.com.UUCP (Robert Blumen) Organization: Xerox PARC Lines: 148 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[] = ": MenuPopdown() select()\n\ : 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[] = ": set() highlight()\n\ : 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: ": 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