diff options
author | Andrew Choi <akochoi@shaw.ca> | 2002-04-26 23:39:06 +0000 |
---|---|---|
committer | Andrew Choi <akochoi@shaw.ca> | 2002-04-26 23:39:06 +0000 |
commit | e0f712ba55fa0d073f6ab93606e428f61fc7caf2 (patch) | |
tree | 7dc6d3403fafcbee1a83288ac840f7eba1d92b44 /src/macmenu.c | |
parent | 501d8923ae2cdec4ef50f050bb66d3715ba2a8f6 (diff) | |
download | emacs-e0f712ba55fa0d073f6ab93606e428f61fc7caf2.tar.gz |
Patch for building Emacs on Mac OS X. April 26, 2002. See ChangeLog,
lisp/ChangeLog, and src/ChangeLog for list of changes.
Diffstat (limited to 'src/macmenu.c')
-rw-r--r-- | src/macmenu.c | 2346 |
1 files changed, 2346 insertions, 0 deletions
diff --git a/src/macmenu.c b/src/macmenu.c new file mode 100644 index 00000000000..f9498cda67d --- /dev/null +++ b/src/macmenu.c @@ -0,0 +1,2346 @@ +/* Menu support for GNU Emacs on the for Mac OS. + Copyright (C) 2000, 2001, 2002 Free Software Foundation, Inc. + +This file is part of GNU Emacs. + +GNU Emacs is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Emacs is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Emacs; see the file COPYING. If not, write to +the Free Software Foundation, Inc., 59 Temple Place - Suite 330, +Boston, MA 02111-1307, USA. */ + +/* Contributed by Andrew Choi (akochoi@mac.com). */ + +#include <config.h> +#include <signal.h> + +#include <stdio.h> +#include "lisp.h" +#include "termhooks.h" +#include "keyboard.h" +#include "keymap.h" +#include "frame.h" +#include "window.h" +#include "blockinput.h" +#include "buffer.h" +#include "charset.h" +#include "coding.h" + +#ifdef MAC_OSX +#undef mktime +#undef DEBUG +#undef Z +#undef free +#undef malloc +#undef realloc +/* Macros max and min defined in lisp.h conflict with those in + precompiled header Carbon.h. */ +#undef max +#undef min +#include <Carbon/Carbon.h> +#undef Z +#define Z (current_buffer->text->z) +#undef free +#define free unexec_free +#undef malloc +#define malloc unexec_malloc +#undef realloc +#define realloc unexec_realloc +#undef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#undef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#else /* not MAC_OSX */ +#include <MacTypes.h> +#include <Menus.h> +#include <QuickDraw.h> +#include <ToolUtils.h> +#include <Fonts.h> +#include <Controls.h> +#include <Windows.h> +#include <Events.h> +#if defined (__MRC__) || (__MSL__ >= 0x6000) +#include <ControlDefinitions.h> +#endif +#endif /* not MAC_OSX */ + +/* This may include sys/types.h, and that somehow loses + if this is not done before the other system files. */ +#include "macterm.h" + +/* Load sys/types.h if not already loaded. + In some systems loading it twice is suicidal. */ +#ifndef makedev +#include <sys/types.h> +#endif + +#include "dispextern.h" + +#define POPUP_SUBMENU_ID 235 +#define MIN_MENU_ID 256 +#define MIN_SUBMENU_ID 1 + +#define DIALOG_WINDOW_RESOURCE 130 + +#define HAVE_DIALOGS 1 + +#undef HAVE_MULTILINGUAL_MENU +#undef HAVE_DIALOGS /* TODO: Implement native dialogs. */ + +/******************************************************************/ +/* Definitions copied from lwlib.h */ + +typedef void * XtPointer; + +enum button_type +{ + BUTTON_TYPE_NONE, + BUTTON_TYPE_TOGGLE, + BUTTON_TYPE_RADIO +}; + +/* This structure is based on the one in ../lwlib/lwlib.h, modified + for Mac OS. */ +typedef struct _widget_value +{ + /* name of widget */ + char* name; + /* value (meaning depend on widget type) */ + char* value; + /* keyboard equivalent. no implications for XtTranslations */ + char* key; + /* Help string or nil if none. + GC finds this string through the frame's menu_bar_vector + or through menu_items. */ + Lisp_Object help; + /* true if enabled */ + Boolean enabled; + /* true if selected */ + Boolean selected; + /* The type of a button. */ + enum button_type button_type; + /* true if menu title */ + Boolean title; +#if 0 + /* true if was edited (maintained by get_value) */ + Boolean edited; + /* true if has changed (maintained by lw library) */ + change_type change; + /* true if this widget itself has changed, + but not counting the other widgets found in the `next' field. */ + change_type this_one_change; +#endif + /* Contents of the sub-widgets, also selected slot for checkbox */ + struct _widget_value* contents; + /* data passed to callback */ + XtPointer call_data; + /* next one in the list */ + struct _widget_value* next; +#if 0 + /* slot for the toolkit dependent part. Always initialize to NULL. */ + void* toolkit_data; + /* tell us if we should free the toolkit data slot when freeing the + widget_value itself. */ + Boolean free_toolkit_data; + + /* we resource the widget_value structures; this points to the next + one on the free list if this one has been deallocated. + */ + struct _widget_value *free_list; +#endif +} widget_value; + +/* Assumed by other routines to zero area returned. */ +#define malloc_widget_value() (void *)memset (xmalloc (sizeof (widget_value)),\ + 0, (sizeof (widget_value))) +#define free_widget_value(wv) xfree (wv) + +/******************************************************************/ + +#ifndef TRUE +#define TRUE 1 +#define FALSE 0 +#endif /* no TRUE */ + +Lisp_Object Vmenu_updating_frame; + +Lisp_Object Qdebug_on_next_call; + +extern Lisp_Object Qmenu_bar; +extern Lisp_Object Qmouse_click, Qevent_kind; + +extern Lisp_Object QCtoggle, QCradio; + +extern Lisp_Object Voverriding_local_map; +extern Lisp_Object Voverriding_local_map_menu_flag; + +extern Lisp_Object Qoverriding_local_map, Qoverriding_terminal_local_map; + +extern Lisp_Object Qmenu_bar_update_hook; + +void set_frame_menubar (); + +static void push_menu_item P_ ((Lisp_Object, Lisp_Object, Lisp_Object, + Lisp_Object, Lisp_Object, Lisp_Object, + Lisp_Object, Lisp_Object)); +#ifdef HAVE_DIALOGS +static Lisp_Object mac_dialog_show (); +#endif +static Lisp_Object mac_menu_show (); + +static void keymap_panes (); +static void single_keymap_panes (); +static void single_menu_item (); +static void list_of_panes (); +static void list_of_items (); + +static void fill_submenu (MenuHandle, widget_value *, int); +static void fill_menubar (widget_value *); + + +/* This holds a Lisp vector that holds the results of decoding + the keymaps or alist-of-alists that specify a menu. + + It describes the panes and items within the panes. + + Each pane is described by 3 elements in the vector: + t, the pane name, the pane's prefix key. + Then follow the pane's items, with 5 elements per item: + the item string, the enable flag, the item's value, + the definition, and the equivalent keyboard key's description string. + + In some cases, multiple levels of menus may be described. + A single vector slot containing nil indicates the start of a submenu. + A single vector slot containing lambda indicates the end of a submenu. + The submenu follows a menu item which is the way to reach the submenu. + + A single vector slot containing quote indicates that the + following items should appear on the right of a dialog box. + + Using a Lisp vector to hold this information while we decode it + takes care of protecting all the data from GC. */ + +#define MENU_ITEMS_PANE_NAME 1 +#define MENU_ITEMS_PANE_PREFIX 2 +#define MENU_ITEMS_PANE_LENGTH 3 + +enum menu_item_idx +{ + MENU_ITEMS_ITEM_NAME = 0, + MENU_ITEMS_ITEM_ENABLE, + MENU_ITEMS_ITEM_VALUE, + MENU_ITEMS_ITEM_EQUIV_KEY, + MENU_ITEMS_ITEM_DEFINITION, + MENU_ITEMS_ITEM_TYPE, + MENU_ITEMS_ITEM_SELECTED, + MENU_ITEMS_ITEM_HELP, + MENU_ITEMS_ITEM_LENGTH +}; + +static Lisp_Object menu_items; + +/* Number of slots currently allocated in menu_items. */ +static int menu_items_allocated; + +/* This is the index in menu_items of the first empty slot. */ +static int menu_items_used; + +/* The number of panes currently recorded in menu_items, + excluding those within submenus. */ +static int menu_items_n_panes; + +/* Current depth within submenus. */ +static int menu_items_submenu_depth; + +/* Flag which when set indicates a dialog or menu has been posted by + Xt on behalf of one of the widget sets. */ +static int popup_activated_flag; + +static int next_menubar_widget_id; + +/* This is set nonzero after the user activates the menu bar, and set + to zero again after the menu bars are redisplayed by prepare_menu_bar. + While it is nonzero, all calls to set_frame_menubar go deep. + + I don't understand why this is needed, but it does seem to be + needed on Motif, according to Marcus Daniels <marcus@sysc.pdx.edu>. */ + +int pending_menu_activation; + +/* Initialize the menu_items structure if we haven't already done so. + Also mark it as currently empty. */ + +static void +init_menu_items () +{ + if (NILP (menu_items)) + { + menu_items_allocated = 60; + menu_items = Fmake_vector (make_number (menu_items_allocated), Qnil); + } + + menu_items_used = 0; + menu_items_n_panes = 0; + menu_items_submenu_depth = 0; +} + +/* Call at the end of generating the data in menu_items. + This fills in the number of items in the last pane. */ + +static void +finish_menu_items () +{ +} + +/* Call when finished using the data for the current menu + in menu_items. */ + +static void +discard_menu_items () +{ + /* Free the structure if it is especially large. + Otherwise, hold on to it, to save time. */ + if (menu_items_allocated > 200) + { + menu_items = Qnil; + menu_items_allocated = 0; + } +} + +/* Make the menu_items vector twice as large. */ + +static void +grow_menu_items () +{ + Lisp_Object old; + int old_size = menu_items_allocated; + old = menu_items; + + menu_items_allocated *= 2; + menu_items = Fmake_vector (make_number (menu_items_allocated), Qnil); + bcopy (XVECTOR (old)->contents, XVECTOR (menu_items)->contents, + old_size * sizeof (Lisp_Object)); +} + +/* Begin a submenu. */ + +static void +push_submenu_start () +{ + if (menu_items_used + 1 > menu_items_allocated) + grow_menu_items (); + + XVECTOR (menu_items)->contents[menu_items_used++] = Qnil; + menu_items_submenu_depth++; +} + +/* End a submenu. */ + +static void +push_submenu_end () +{ + if (menu_items_used + 1 > menu_items_allocated) + grow_menu_items (); + + XVECTOR (menu_items)->contents[menu_items_used++] = Qlambda; + menu_items_submenu_depth--; +} + +/* Indicate boundary between left and right. */ + +static void +push_left_right_boundary () +{ + if (menu_items_used + 1 > menu_items_allocated) + grow_menu_items (); + + XVECTOR (menu_items)->contents[menu_items_used++] = Qquote; +} + +/* Start a new menu pane in menu_items. + NAME is the pane name. PREFIX_VEC is a prefix key for this pane. */ + +static void +push_menu_pane (name, prefix_vec) + Lisp_Object name, prefix_vec; +{ + if (menu_items_used + MENU_ITEMS_PANE_LENGTH > menu_items_allocated) + grow_menu_items (); + + if (menu_items_submenu_depth == 0) + menu_items_n_panes++; + XVECTOR (menu_items)->contents[menu_items_used++] = Qt; + XVECTOR (menu_items)->contents[menu_items_used++] = name; + XVECTOR (menu_items)->contents[menu_items_used++] = prefix_vec; +} + +/* Push one menu item into the current pane. NAME is the string to + display. ENABLE if non-nil means this item can be selected. KEY + is the key generated by choosing this item, or nil if this item + doesn't really have a definition. DEF is the definition of this + item. EQUIV is the textual description of the keyboard equivalent + for this item (or nil if none). TYPE is the type of this menu + item, one of nil, `toggle' or `radio'. */ + +static void +push_menu_item (name, enable, key, def, equiv, type, selected, help) + Lisp_Object name, enable, key, def, equiv, type, selected, help; +{ + if (menu_items_used + MENU_ITEMS_ITEM_LENGTH > menu_items_allocated) + grow_menu_items (); + + XVECTOR (menu_items)->contents[menu_items_used++] = name; + XVECTOR (menu_items)->contents[menu_items_used++] = enable; + XVECTOR (menu_items)->contents[menu_items_used++] = key; + XVECTOR (menu_items)->contents[menu_items_used++] = equiv; + XVECTOR (menu_items)->contents[menu_items_used++] = def; + XVECTOR (menu_items)->contents[menu_items_used++] = type; + XVECTOR (menu_items)->contents[menu_items_used++] = selected; + XVECTOR (menu_items)->contents[menu_items_used++] = help; +} + +/* Look through KEYMAPS, a vector of keymaps that is NMAPS long, + and generate menu panes for them in menu_items. + If NOTREAL is nonzero, + don't bother really computing whether an item is enabled. */ + +static void +keymap_panes (keymaps, nmaps, notreal) + Lisp_Object *keymaps; + int nmaps; + int notreal; +{ + int mapno; + + init_menu_items (); + + /* Loop over the given keymaps, making a pane for each map. + But don't make a pane that is empty--ignore that map instead. + P is the number of panes we have made so far. */ + for (mapno = 0; mapno < nmaps; mapno++) + single_keymap_panes (keymaps[mapno], + Fkeymap_prompt (keymaps[mapno]), Qnil, notreal, 10); + + finish_menu_items (); +} + +/* This is a recursive subroutine of keymap_panes. + It handles one keymap, KEYMAP. + The other arguments are passed along + or point to local variables of the previous function. + If NOTREAL is nonzero, only check for equivalent key bindings, don't + evaluate expressions in menu items and don't make any menu. + + If we encounter submenus deeper than MAXDEPTH levels, ignore them. */ + +static void +single_keymap_panes (keymap, pane_name, prefix, notreal, maxdepth) + Lisp_Object keymap; + Lisp_Object pane_name; + Lisp_Object prefix; + int notreal; + int maxdepth; +{ + Lisp_Object pending_maps = Qnil; + Lisp_Object tail, item; + struct gcpro gcpro1, gcpro2; + + if (maxdepth <= 0) + return; + + push_menu_pane (pane_name, prefix); + + for (tail = keymap; CONSP (tail); tail = XCDR (tail)) + { + GCPRO2 (keymap, pending_maps); + /* Look at each key binding, and if it is a menu item add it + to this menu. */ + item = XCAR (tail); + if (CONSP (item)) + single_menu_item (XCAR (item), XCDR (item), + &pending_maps, notreal, maxdepth); + else if (VECTORP (item)) + { + /* Loop over the char values represented in the vector. */ + int len = XVECTOR (item)->size; + int c; + for (c = 0; c < len; c++) + { + Lisp_Object character; + XSETFASTINT (character, c); + single_menu_item (character, XVECTOR (item)->contents[c], + &pending_maps, notreal, maxdepth); + } + } + UNGCPRO; + } + + /* Process now any submenus which want to be panes at this level. */ + while (!NILP (pending_maps)) + { + Lisp_Object elt, eltcdr, string; + elt = Fcar (pending_maps); + eltcdr = XCDR (elt); + string = XCAR (eltcdr); + /* We no longer discard the @ from the beginning of the string here. + Instead, we do this in mac_menu_show. */ + single_keymap_panes (Fcar (elt), string, + XCDR (eltcdr), notreal, maxdepth - 1); + pending_maps = Fcdr (pending_maps); + } +} + +/* This is a subroutine of single_keymap_panes that handles one + keymap entry. + KEY is a key in a keymap and ITEM is its binding. + PENDING_MAPS_PTR points to a list of keymaps waiting to be made into + separate panes. + If NOTREAL is nonzero, only check for equivalent key bindings, don't + evaluate expressions in menu items and don't make any menu. + If we encounter submenus deeper than MAXDEPTH levels, ignore them. */ + +static void +single_menu_item (key, item, pending_maps_ptr, notreal, maxdepth) + Lisp_Object key, item; + Lisp_Object *pending_maps_ptr; + int maxdepth, notreal; +{ + Lisp_Object map, item_string, enabled; + struct gcpro gcpro1, gcpro2; + int res; + + /* Parse the menu item and leave the result in item_properties. */ + GCPRO2 (key, item); + res = parse_menu_item (item, notreal, 0); + UNGCPRO; + if (!res) + return; /* Not a menu item. */ + + map = XVECTOR (item_properties)->contents[ITEM_PROPERTY_MAP]; + + if (notreal) + { + /* We don't want to make a menu, just traverse the keymaps to + precompute equivalent key bindings. */ + if (!NILP (map)) + single_keymap_panes (map, Qnil, key, 1, maxdepth - 1); + return; + } + + enabled = XVECTOR (item_properties)->contents[ITEM_PROPERTY_ENABLE]; + item_string = XVECTOR (item_properties)->contents[ITEM_PROPERTY_NAME]; + + if (!NILP (map) && XSTRING (item_string)->data[0] == '@') + { + if (!NILP (enabled)) + /* An enabled separate pane. Remember this to handle it later. */ + *pending_maps_ptr = Fcons (Fcons (map, Fcons (item_string, key)), + *pending_maps_ptr); + return; + } + + push_menu_item (item_string, enabled, key, + XVECTOR (item_properties)->contents[ITEM_PROPERTY_DEF], + XVECTOR (item_properties)->contents[ITEM_PROPERTY_KEYEQ], + XVECTOR (item_properties)->contents[ITEM_PROPERTY_TYPE], + XVECTOR (item_properties)->contents[ITEM_PROPERTY_SELECTED], + XVECTOR (item_properties)->contents[ITEM_PROPERTY_HELP]); + + /* Display a submenu using the toolkit. */ + if (! (NILP (map) || NILP (enabled))) + { + push_submenu_start (); + single_keymap_panes (map, Qnil, key, 0, maxdepth - 1); + push_submenu_end (); + } +} + +/* Push all the panes and items of a menu described by the + alist-of-alists MENU. + This handles old-fashioned calls to x-popup-menu. */ + +static void +list_of_panes (menu) + Lisp_Object menu; +{ + Lisp_Object tail; + + init_menu_items (); + + for (tail = menu; !NILP (tail); tail = Fcdr (tail)) + { + Lisp_Object elt, pane_name, pane_data; + elt = Fcar (tail); + pane_name = Fcar (elt); + CHECK_STRING (pane_name); + push_menu_pane (pane_name, Qnil); + pane_data = Fcdr (elt); + CHECK_CONS (pane_data); + list_of_items (pane_data); + } + + finish_menu_items (); +} + +/* Push the items in a single pane defined by the alist PANE. */ + +static void +list_of_items (pane) + Lisp_Object pane; +{ + Lisp_Object tail, item, item1; + + for (tail = pane; !NILP (tail); tail = Fcdr (tail)) + { + item = Fcar (tail); + if (STRINGP (item)) + push_menu_item (item, Qnil, Qnil, Qt, Qnil, Qnil, Qnil, Qnil); + else if (NILP (item)) + push_left_right_boundary (); + else + { + CHECK_CONS (item); + item1 = Fcar (item); + CHECK_STRING (item1); + push_menu_item (item1, Qt, Fcdr (item), Qt, Qnil, Qnil, Qnil, Qnil); + } + } +} + +DEFUN ("x-popup-menu", Fx_popup_menu, Sx_popup_menu, 2, 2, 0, + doc: /* Pop up a deck-of-cards menu and return user's selection. +POSITION is a position specification. This is either a mouse button +event or a list ((XOFFSET YOFFSET) WINDOW) where XOFFSET and YOFFSET +are positions in pixels from the top left corner of WINDOW's frame +\(WINDOW may be a frame object instead of a window). This controls the +position of the center of the first line in the first pane of the +menu, not the top left of the menu as a whole. If POSITION is t, it +means to use the current mouse position. + +MENU is a specifier for a menu. For the simplest case, MENU is a keymap. +The menu items come from key bindings that have a menu string as well as +a definition; actually, the \"definition\" in such a key binding looks like +\(STRING . REAL-DEFINITION). To give the menu a title, put a string into +the keymap as a top-level element. + +If REAL-DEFINITION is nil, that puts a nonselectable string in the menu. +Otherwise, REAL-DEFINITION should be a valid key binding definition. + +You can also use a list of keymaps as MENU. Then each keymap makes a +separate pane. When MENU is a keymap or a list of keymaps, the return +value is a list of events. + +Alternatively, you can specify a menu of multiple panes with a list of +the form (TITLE PANE1 PANE2...), where each pane is a list of +form (TITLE ITEM1 ITEM2...). +Each ITEM is normally a cons cell (STRING . VALUE); but a string can +appear as an item--that makes a nonselectable line in the menu. +With this form of menu, the return value is VALUE from the chosen item. + +If POSITION is nil, don't display the menu at all, just precalculate the +cached information about equivalent key sequences. */) + (position, menu) + Lisp_Object position, menu; +{ + Lisp_Object keymap, tem; + int xpos = 0, ypos = 0; + Lisp_Object title; + char *error_name; + Lisp_Object selection; + FRAME_PTR f = NULL; + Lisp_Object x, y, window; + int keymaps = 0; + int for_click = 0; + struct gcpro gcpro1; + +#ifdef HAVE_MENUS + if (! NILP (position)) + { + check_mac (); + + /* Decode the first argument: find the window and the coordinates. */ + if (EQ (position, Qt) + || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar) + || EQ (XCAR (position), Qtool_bar)))) + { + /* Use the mouse's current position. */ + FRAME_PTR new_f = SELECTED_FRAME (); + Lisp_Object bar_window; + enum scroll_bar_part part; + unsigned long time; + + if (mouse_position_hook) + (*mouse_position_hook) (&new_f, 1, &bar_window, + &part, &x, &y, &time); + if (new_f != 0) + XSETFRAME (window, new_f); + else + { + window = selected_window; + XSETFASTINT (x, 0); + XSETFASTINT (y, 0); + } + } + else + { + tem = Fcar (position); + if (CONSP (tem)) + { + window = Fcar (Fcdr (position)); + x = Fcar (tem); + y = Fcar (Fcdr (tem)); + } + else + { + for_click = 1; + tem = Fcar (Fcdr (position)); /* EVENT_START (position) */ + window = Fcar (tem); /* POSN_WINDOW (tem) */ + tem = Fcar (Fcdr (Fcdr (tem))); /* POSN_WINDOW_POSN (tem) */ + x = Fcar (tem); + y = Fcdr (tem); + } + } + + CHECK_NUMBER (x); + CHECK_NUMBER (y); + + /* Decode where to put the menu. */ + + if (FRAMEP (window)) + { + f = XFRAME (window); + xpos = 0; + ypos = 0; + } + else if (WINDOWP (window)) + { + CHECK_LIVE_WINDOW (window); + f = XFRAME (WINDOW_FRAME (XWINDOW (window))); + + xpos = (FONT_WIDTH (FRAME_FONT (f)) + * XFASTINT (XWINDOW (window)->left)); + ypos = (FRAME_LINE_HEIGHT (f) + * XFASTINT (XWINDOW (window)->top)); + } + else + /* ??? Not really clean; should be CHECK_WINDOW_OR_FRAME, + but I don't want to make one now. */ + CHECK_WINDOW (window); + + xpos += XINT (x); + ypos += XINT (y); + + XSETFRAME (Vmenu_updating_frame, f); + } + Vmenu_updating_frame = Qnil; +#endif /* HAVE_MENUS */ + + title = Qnil; + GCPRO1 (title); + + /* Decode the menu items from what was specified. */ + + keymap = get_keymap (menu, 0, 0); + if (CONSP (keymap)) + { + /* We were given a keymap. Extract menu info from the keymap. */ + Lisp_Object prompt; + + /* Extract the detailed info to make one pane. */ + keymap_panes (&menu, 1, NILP (position)); + + /* Search for a string appearing directly as an element of the keymap. + That string is the title of the menu. */ + prompt = Fkeymap_prompt (keymap); + if (NILP (title) && !NILP (prompt)) + title = prompt; + + /* Make that be the pane title of the first pane. */ + if (!NILP (prompt) && menu_items_n_panes >= 0) + XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = prompt; + + keymaps = 1; + } + else if (CONSP (menu) && KEYMAPP (XCAR (menu))) + { + /* We were given a list of keymaps. */ + int nmaps = XFASTINT (Flength (menu)); + Lisp_Object *maps + = (Lisp_Object *) alloca (nmaps * sizeof (Lisp_Object)); + int i; + + title = Qnil; + + /* The first keymap that has a prompt string + supplies the menu title. */ + for (tem = menu, i = 0; CONSP (tem); tem = Fcdr (tem)) + { + Lisp_Object prompt; + + maps[i++] = keymap = get_keymap (Fcar (tem), 1, 0); + + prompt = Fkeymap_prompt (keymap); + if (NILP (title) && !NILP (prompt)) + title = prompt; + } + + /* Extract the detailed info to make one pane. */ + keymap_panes (maps, nmaps, NILP (position)); + + /* Make the title be the pane title of the first pane. */ + if (!NILP (title) && menu_items_n_panes >= 0) + XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME] = title; + + keymaps = 1; + } + else + { + /* We were given an old-fashioned menu. */ + title = Fcar (menu); + CHECK_STRING (title); + + list_of_panes (Fcdr (menu)); + + keymaps = 0; + } + + if (NILP (position)) + { + discard_menu_items (); + UNGCPRO; + return Qnil; + } + +#ifdef HAVE_MENUS + /* Display them in a menu. */ + BLOCK_INPUT; + + selection = mac_menu_show (f, xpos, ypos, for_click, + keymaps, title, &error_name); + UNBLOCK_INPUT; + + discard_menu_items (); + + UNGCPRO; +#endif /* HAVE_MENUS */ + + if (error_name) error (error_name); + return selection; +} + +#ifdef HAVE_MENUS + +DEFUN ("x-popup-dialog", Fx_popup_dialog, Sx_popup_dialog, 2, 2, 0, + doc: /* Pop up a dialog box and return user's selection. +POSITION specifies which frame to use. +This is normally a mouse button event or a window or frame. +If POSITION is t, it means to use the frame the mouse is on. +The dialog box appears in the middle of the specified frame. + +CONTENTS specifies the alternatives to display in the dialog box. +It is a list of the form (TITLE ITEM1 ITEM2...). +Each ITEM is a cons cell (STRING . VALUE). +The return value is VALUE from the chosen item. + +An ITEM may also be just a string--that makes a nonselectable item. +An ITEM may also be nil--that means to put all preceding items +on the left of the dialog box and all following items on the right. +\(By default, approximately half appear on each side.) */) + (position, contents) + Lisp_Object position, contents; +{ + FRAME_PTR f = NULL; + Lisp_Object window; + + check_mac (); + + /* Decode the first argument: find the window or frame to use. */ + if (EQ (position, Qt) + || (CONSP (position) && (EQ (XCAR (position), Qmenu_bar) + || EQ (XCAR (position), Qtool_bar)))) + { +#if 0 /* Using the frame the mouse is on may not be right. */ + /* Use the mouse's current position. */ + FRAME_PTR new_f = SELECTED_FRAME (); + Lisp_Object bar_window; + enum scroll_bar_part part; + unsigned long time; + Lisp_Object x, y; + + (*mouse_position_hook) (&new_f, 1, &bar_window, &part, &x, &y, &time); + + if (new_f != 0) + XSETFRAME (window, new_f); + else + window = selected_window; +#endif + window = selected_window; + } + else if (CONSP (position)) + { + Lisp_Object tem; + tem = Fcar (position); + if (CONSP (tem)) + window = Fcar (Fcdr (position)); + else + { + tem = Fcar (Fcdr (position)); /* EVENT_START (position) */ + window = Fcar (tem); /* POSN_WINDOW (tem) */ + } + } + else if (WINDOWP (position) || FRAMEP (position)) + window = position; + else + window = Qnil; + + /* Decode where to put the menu. */ + + if (FRAMEP (window)) + f = XFRAME (window); + else if (WINDOWP (window)) + { + CHECK_LIVE_WINDOW (window); + f = XFRAME (WINDOW_FRAME (XWINDOW (window))); + } + else + /* ??? Not really clean; should be CHECK_WINDOW_OR_FRAME, + but I don't want to make one now. */ + CHECK_WINDOW (window); + +#ifndef HAVE_DIALOGS + /* Display a menu with these alternatives + in the middle of frame F. */ + { + Lisp_Object x, y, frame, newpos; + XSETFRAME (frame, f); + XSETINT (x, x_pixel_width (f) / 2); + XSETINT (y, x_pixel_height (f) / 2); + newpos = Fcons (Fcons (x, Fcons (y, Qnil)), Fcons (frame, Qnil)); + + return Fx_popup_menu (newpos, + Fcons (Fcar (contents), Fcons (contents, Qnil))); + } +#else /* HAVE_DIALOGS */ + { + Lisp_Object title; + char *error_name; + Lisp_Object selection; + + /* Decode the dialog items from what was specified. */ + title = Fcar (contents); + CHECK_STRING (title); + + list_of_panes (Fcons (contents, Qnil)); + + /* Display them in a dialog box. */ + BLOCK_INPUT; + selection = mac_dialog_show (f, 0, title, &error_name); + UNBLOCK_INPUT; + + discard_menu_items (); + + if (error_name) error (error_name); + return selection; + } +#endif /* HAVE_DIALOGS */ +} + +/* Activate the menu bar of frame F. + This is called from keyboard.c when it gets the + menu_bar_activate_event out of the Emacs event queue. + + To activate the menu bar, we signal to the input thread that it can + return from the WM_INITMENU message, allowing the normal Windows + processing of the menus. + + But first we recompute the menu bar contents (the whole tree). + + This way we can safely execute Lisp code. */ + +void +x_activate_menubar (f) + FRAME_PTR f; +{ + SInt32 menu_choice; + extern Point saved_menu_event_location; + + set_frame_menubar (f, 0, 1); + BLOCK_INPUT; + + menu_choice = MenuSelect (saved_menu_event_location); + do_menu_choice (menu_choice); + + UNBLOCK_INPUT; +} + +/* This callback is called from the menu bar pulldown menu + when the user makes a selection. + Figure out what the user chose + and put the appropriate events into the keyboard buffer. */ + +void +menubar_selection_callback (FRAME_PTR f, int client_data) +{ + Lisp_Object prefix, entry; + Lisp_Object vector; + Lisp_Object *subprefix_stack; + int submenu_depth = 0; + int i; + + if (!f) + return; + entry = Qnil; + subprefix_stack = (Lisp_Object *) alloca (f->menu_bar_items_used * sizeof (Lisp_Object)); + vector = f->menu_bar_vector; + prefix = Qnil; + i = 0; + while (i < f->menu_bar_items_used) + { + if (EQ (XVECTOR (vector)->contents[i], Qnil)) + { + subprefix_stack[submenu_depth++] = prefix; + prefix = entry; + i++; + } + else if (EQ (XVECTOR (vector)->contents[i], Qlambda)) + { + prefix = subprefix_stack[--submenu_depth]; + i++; + } + else if (EQ (XVECTOR (vector)->contents[i], Qt)) + { + prefix = XVECTOR (vector)->contents[i + MENU_ITEMS_PANE_PREFIX]; + i += MENU_ITEMS_PANE_LENGTH; + } + else + { + entry = XVECTOR (vector)->contents[i + MENU_ITEMS_ITEM_VALUE]; + /* The EMACS_INT cast avoids a warning. There's no problem + as long as pointers have enough bits to hold small integers. */ + if ((int) (EMACS_INT) client_data == i) + { + int j; + struct input_event buf; + Lisp_Object frame; + + XSETFRAME (frame, f); + buf.kind = MENU_BAR_EVENT; + buf.frame_or_window = frame; + buf.arg = frame; + kbd_buffer_store_event (&buf); + + for (j = 0; j < submenu_depth; j++) + if (!NILP (subprefix_stack[j])) + { + buf.kind = MENU_BAR_EVENT; + buf.frame_or_window = frame; + buf.arg = subprefix_stack[j]; + kbd_buffer_store_event (&buf); + } + + if (!NILP (prefix)) + { + buf.kind = MENU_BAR_EVENT; + buf.frame_or_window = frame; + buf.arg = prefix; + kbd_buffer_store_event (&buf); + } + + buf.kind = MENU_BAR_EVENT; + buf.frame_or_window = frame; + buf.arg = entry; + kbd_buffer_store_event (&buf); + + f->output_data.mac->menu_command_in_progress = 0; + f->output_data.mac->menubar_active = 0; + return; + } + i += MENU_ITEMS_ITEM_LENGTH; + } + } + f->output_data.mac->menu_command_in_progress = 0; + f->output_data.mac->menubar_active = 0; +} + +/* Allocate a widget_value, blocking input. */ + +widget_value * +xmalloc_widget_value () +{ + widget_value *value; + + BLOCK_INPUT; + value = malloc_widget_value (); + UNBLOCK_INPUT; + + return value; +} + +/* This recursively calls free_widget_value on the tree of widgets. + It must free all data that was malloc'ed for these widget_values. + In Emacs, many slots are pointers into the data of Lisp_Strings, and + must be left alone. */ + +void +free_menubar_widget_value_tree (wv) + widget_value *wv; +{ + if (! wv) return; + + wv->name = wv->value = wv->key = (char *) 0xDEADBEEF; + + if (wv->contents && (wv->contents != (widget_value*)1)) + { + free_menubar_widget_value_tree (wv->contents); + wv->contents = (widget_value *) 0xDEADBEEF; + } + if (wv->next) + { + free_menubar_widget_value_tree (wv->next); + wv->next = (widget_value *) 0xDEADBEEF; + } + BLOCK_INPUT; + free_widget_value (wv); + UNBLOCK_INPUT; +} + +/* Return a tree of widget_value structures for a menu bar item + whose event type is ITEM_KEY (with string ITEM_NAME) + and whose contents come from the list of keymaps MAPS. */ + +static widget_value * +single_submenu (item_key, item_name, maps) + Lisp_Object item_key, item_name, maps; +{ + widget_value *wv, *prev_wv, *save_wv, *first_wv; + int i; + int submenu_depth = 0; + Lisp_Object length; + int len; + Lisp_Object *mapvec; + widget_value **submenu_stack; + int previous_items = menu_items_used; + int top_level_items = 0; + + length = Flength (maps); + len = XINT (length); + + /* Convert the list MAPS into a vector MAPVEC. */ + mapvec = (Lisp_Object *) alloca (len * sizeof (Lisp_Object)); + for (i = 0; i < len; i++) + { + mapvec[i] = Fcar (maps); + maps = Fcdr (maps); + } + + menu_items_n_panes = 0; + + /* Loop over the given keymaps, making a pane for each map. + But don't make a pane that is empty--ignore that map instead. */ + for (i = 0; i < len; i++) + { + if (SYMBOLP (mapvec[i]) + || (CONSP (mapvec[i]) && !KEYMAPP (mapvec[i]))) + { + /* Here we have a command at top level in the menu bar + as opposed to a submenu. */ + top_level_items = 1; + push_menu_pane (Qnil, Qnil); + push_menu_item (item_name, Qt, item_key, mapvec[i], + Qnil, Qnil, Qnil, Qnil); + } + else + single_keymap_panes (mapvec[i], item_name, item_key, 0, 10); + } + + /* Create a tree of widget_value objects + representing the panes and their items. */ + + submenu_stack + = (widget_value **) alloca (menu_items_used * sizeof (widget_value *)); + wv = xmalloc_widget_value (); + wv->name = "menu"; + wv->value = 0; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + first_wv = wv; + save_wv = 0; + prev_wv = 0; + + /* Loop over all panes and items made during this call + and construct a tree of widget_value objects. + Ignore the panes and items made by previous calls to + single_submenu, even though those are also in menu_items. */ + i = previous_items; + while (i < menu_items_used) + { + if (EQ (XVECTOR (menu_items)->contents[i], Qnil)) + { + submenu_stack[submenu_depth++] = save_wv; + save_wv = prev_wv; + prev_wv = 0; + i++; + } + else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda)) + { + prev_wv = save_wv; + save_wv = submenu_stack[--submenu_depth]; + i++; + } + else if (EQ (XVECTOR (menu_items)->contents[i], Qt) + && submenu_depth != 0) + i += MENU_ITEMS_PANE_LENGTH; + /* Ignore a nil in the item list. + It's meaningful only for dialog boxes. */ + else if (EQ (XVECTOR (menu_items)->contents[i], Qquote)) + i += 1; + else if (EQ (XVECTOR (menu_items)->contents[i], Qt)) + { + /* Create a new pane. */ + Lisp_Object pane_name, prefix; + char *pane_string; + + pane_name = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_NAME]; + prefix = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_PREFIX]; + +#ifndef HAVE_MULTILINGUAL_MENU + if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name)) + { + pane_name = ENCODE_SYSTEM (pane_name); + AREF (menu_items, i + MENU_ITEMS_PANE_NAME) = pane_name; + } +#endif + pane_string = (NILP (pane_name) + ? "" : (char *) XSTRING (pane_name)->data); + /* If there is just one top-level pane, put all its items directly + under the top-level menu. */ + if (menu_items_n_panes == 1) + pane_string = ""; + + /* If the pane has a meaningful name, + make the pane a top-level menu item + with its items as a submenu beneath it. */ + if (strcmp (pane_string, "")) + { + wv = xmalloc_widget_value (); + if (save_wv) + save_wv->next = wv; + else + first_wv->contents = wv; + wv->name = pane_string; + /* Ignore the @ that means "separate pane". + This is a kludge, but this isn't worth more time. */ + if (!NILP (prefix) && wv->name[0] == '@') + wv->name++; + wv->value = 0; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + } + save_wv = wv; + prev_wv = 0; + i += MENU_ITEMS_PANE_LENGTH; + } + else + { + /* Create a new item within current pane. */ + Lisp_Object item_name, enable, descrip, def, type, selected; + Lisp_Object help; + + item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME); + enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE); + descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY); + def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION); + type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE); + selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED); + help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP); + +#ifndef HAVE_MULTILINGUAL_MENU + if (STRING_MULTIBYTE (item_name)) + { + item_name = ENCODE_SYSTEM (item_name); + AREF (menu_items, i + MENU_ITEMS_ITEM_NAME) = item_name; + } + + if (STRINGP (descrip) && STRING_MULTIBYTE (descrip)) + { + descrip = ENCODE_SYSTEM (descrip); + AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY) = descrip; + } +#endif /* not HAVE_MULTILINGUAL_MENU */ + + wv = xmalloc_widget_value (); + if (prev_wv) + prev_wv->next = wv; + else + save_wv->contents = wv; + + wv->name = (char *) XSTRING (item_name)->data; + if (!NILP (descrip)) + wv->key = (char *) XSTRING (descrip)->data; + wv->value = 0; + /* The EMACS_INT cast avoids a warning. There's no problem + as long as pointers have enough bits to hold small integers. */ + wv->call_data = (!NILP (def) ? (void *) (EMACS_INT) i : 0); + wv->enabled = !NILP (enable); + + if (NILP (type)) + wv->button_type = BUTTON_TYPE_NONE; + else if (EQ (type, QCradio)) + wv->button_type = BUTTON_TYPE_RADIO; + else if (EQ (type, QCtoggle)) + wv->button_type = BUTTON_TYPE_TOGGLE; + else + abort (); + + wv->selected = !NILP (selected); + if (!STRINGP (help)) + help = Qnil; + + wv->help = help; + + prev_wv = wv; + + i += MENU_ITEMS_ITEM_LENGTH; + } + } + + /* If we have just one "menu item" + that was originally a button, return it by itself. */ + if (top_level_items && first_wv->contents && first_wv->contents->next == 0) + { + wv = first_wv->contents; + free_widget_value (first_wv); + return wv; + } + + return first_wv; +} + +/* Set the contents of the menubar widgets of frame F. + The argument FIRST_TIME is currently ignored; + it is set the first time this is called, from initialize_frame_menubar. */ + +void +set_frame_menubar (f, first_time, deep_p) + FRAME_PTR f; + int first_time; + int deep_p; +{ + int menubar_widget = f->output_data.mac->menubar_widget; + Lisp_Object items; + widget_value *wv, *first_wv, *prev_wv = 0; + int i; + + /* We must not change the menubar when actually in use. */ + if (f->output_data.mac->menubar_active) + return; + + XSETFRAME (Vmenu_updating_frame, f); + + if (! menubar_widget) + deep_p = 1; + else if (pending_menu_activation && !deep_p) + deep_p = 1; + + wv = xmalloc_widget_value (); + wv->name = "menubar"; + wv->value = 0; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + first_wv = wv; + + if (deep_p) + { + /* Make a widget-value tree representing the entire menu trees. */ + + struct buffer *prev = current_buffer; + Lisp_Object buffer; + int specpdl_count = specpdl_ptr - specpdl; + int previous_menu_items_used = f->menu_bar_items_used; + Lisp_Object *previous_items + = (Lisp_Object *) alloca (previous_menu_items_used + * sizeof (Lisp_Object)); + + /* If we are making a new widget, its contents are empty, + do always reinitialize them. */ + if (! menubar_widget) + previous_menu_items_used = 0; + + buffer = XWINDOW (FRAME_SELECTED_WINDOW (f))->buffer; + specbind (Qinhibit_quit, Qt); + /* Don't let the debugger step into this code + because it is not reentrant. */ + specbind (Qdebug_on_next_call, Qnil); + + record_unwind_protect (Fset_match_data, Fmatch_data (Qnil, Qnil)); + if (NILP (Voverriding_local_map_menu_flag)) + { + specbind (Qoverriding_terminal_local_map, Qnil); + specbind (Qoverriding_local_map, Qnil); + } + + set_buffer_internal_1 (XBUFFER (buffer)); + + /* Run the Lucid hook. */ + safe_run_hooks (Qactivate_menubar_hook); + /* If it has changed current-menubar from previous value, + really recompute the menubar from the value. */ + if (! NILP (Vlucid_menu_bar_dirty_flag)) + call0 (Qrecompute_lucid_menubar); + safe_run_hooks (Qmenu_bar_update_hook); + FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f)); + + items = FRAME_MENU_BAR_ITEMS (f); + + inhibit_garbage_collection (); + + /* Save the frame's previous menu bar contents data. */ + if (previous_menu_items_used) + bcopy (XVECTOR (f->menu_bar_vector)->contents, previous_items, + previous_menu_items_used * sizeof (Lisp_Object)); + + /* Fill in the current menu bar contents. */ + menu_items = f->menu_bar_vector; + menu_items_allocated = VECTORP (menu_items) ? ASIZE (menu_items) : 0; + init_menu_items (); + for (i = 0; i < XVECTOR (items)->size; i += 4) + { + Lisp_Object key, string, maps; + + key = XVECTOR (items)->contents[i]; + string = XVECTOR (items)->contents[i + 1]; + maps = XVECTOR (items)->contents[i + 2]; + if (NILP (string)) + break; + + wv = single_submenu (key, string, maps); + if (prev_wv) + prev_wv->next = wv; + else + first_wv->contents = wv; + /* Don't set wv->name here; GC during the loop might relocate it. */ + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + prev_wv = wv; + } + + finish_menu_items (); + + set_buffer_internal_1 (prev); + unbind_to (specpdl_count, Qnil); + + /* If there has been no change in the Lisp-level contents + of the menu bar, skip redisplaying it. Just exit. */ + + for (i = 0; i < previous_menu_items_used; i++) + if (menu_items_used == i + || (!Fequal (previous_items[i], XVECTOR (menu_items)->contents[i]))) + break; + if (i == menu_items_used && i == previous_menu_items_used && i != 0) + { + free_menubar_widget_value_tree (first_wv); + menu_items = Qnil; + + return; + } + + /* Now GC cannot happen during the lifetime of the widget_value, + so it's safe to store data from a Lisp_String, as long as + local copies are made when the actual menu is created. + Windows takes care of this for normal string items, but + not for owner-drawn items or additional item-info. */ + wv = first_wv->contents; + for (i = 0; i < XVECTOR (items)->size; i += 4) + { + Lisp_Object string; + string = XVECTOR (items)->contents[i + 1]; + if (NILP (string)) + break; + wv->name = (char *) XSTRING (string)->data; + wv = wv->next; + } + + f->menu_bar_vector = menu_items; + f->menu_bar_items_used = menu_items_used; + menu_items = Qnil; + } + else + { + /* Make a widget-value tree containing + just the top level menu bar strings. */ + + items = FRAME_MENU_BAR_ITEMS (f); + for (i = 0; i < XVECTOR (items)->size; i += 4) + { + Lisp_Object string; + + string = XVECTOR (items)->contents[i + 1]; + if (NILP (string)) + break; + + wv = xmalloc_widget_value (); + wv->name = (char *) XSTRING (string)->data; + wv->value = 0; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + /* This prevents lwlib from assuming this + menu item is really supposed to be empty. */ + /* The EMACS_INT cast avoids a warning. + This value just has to be different from small integers. */ + wv->call_data = (void *) (EMACS_INT) (-1); + + if (prev_wv) + prev_wv->next = wv; + else + first_wv->contents = wv; + prev_wv = wv; + } + + /* Forget what we thought we knew about what is in the + detailed contents of the menu bar menus. + Changing the top level always destroys the contents. */ + f->menu_bar_items_used = 0; + } + + /* Create or update the menu bar widget. */ + + BLOCK_INPUT; + + /* Non-null value to indicate menubar has already been "created". */ + f->output_data.mac->menubar_widget = 1; + + { + int i = MIN_MENU_ID; + MenuHandle menu = GetMenuHandle (i); + while (menu != NULL) + { + DeleteMenu (i); + DisposeMenu (menu); + menu = GetMenuHandle (++i); + } + + i = MIN_SUBMENU_ID; + menu = GetMenuHandle (i); + while (menu != NULL) + { + DeleteMenu (i); + DisposeMenu (menu); + menu = GetMenuHandle (++i); + } + } + + fill_menubar (first_wv->contents); + + DrawMenuBar (); + + free_menubar_widget_value_tree (first_wv); + + UNBLOCK_INPUT; +} + +/* Called from Fx_create_frame to create the initial menubar of a frame + before it is mapped, so that the window is mapped with the menubar already + there instead of us tacking it on later and thrashing the window after it + is visible. */ + +void +initialize_frame_menubar (f) + FRAME_PTR f; +{ + /* This function is called before the first chance to redisplay + the frame. It has to be, so the frame will have the right size. */ + FRAME_MENU_BAR_ITEMS (f) = menu_bar_items (FRAME_MENU_BAR_ITEMS (f)); + set_frame_menubar (f, 1, 1); +} + +/* Get rid of the menu bar of frame F, and free its storage. + This is used when deleting a frame, and when turning off the menu bar. */ + +void +free_frame_menubar (f) + FRAME_PTR f; +{ + f->output_data.mac->menubar_widget = NULL; +} + + +/* mac_menu_show actually displays a menu using the panes and items in + menu_items and returns the value selected from it; we assume input + is blocked by the caller. */ + +/* F is the frame the menu is for. + X and Y are the frame-relative specified position, + relative to the inside upper left corner of the frame F. + FOR_CLICK is nonzero if this menu was invoked for a mouse click. + KEYMAPS is 1 if this menu was specified with keymaps; + in that case, we return a list containing the chosen item's value + and perhaps also the pane's prefix. + TITLE is the specified menu title. + ERROR is a place to store an error message string in case of failure. + (We return nil on failure, but the value doesn't actually matter.) */ + +static Lisp_Object +mac_menu_show (f, x, y, for_click, keymaps, title, error) + FRAME_PTR f; + int x; + int y; + int for_click; + int keymaps; + Lisp_Object title; + char **error; +{ + int i; + int menu_item_selection; + MenuHandle menu; + Point pos; + widget_value *wv, *save_wv = 0, *first_wv = 0, *prev_wv = 0; + widget_value **submenu_stack + = (widget_value **) alloca (menu_items_used * sizeof (widget_value *)); + Lisp_Object *subprefix_stack + = (Lisp_Object *) alloca (menu_items_used * sizeof (Lisp_Object)); + int submenu_depth = 0; + int first_pane; + + *error = NULL; + + if (menu_items_used <= MENU_ITEMS_PANE_LENGTH) + { + *error = "Empty menu"; + return Qnil; + } + + /* Create a tree of widget_value objects + representing the panes and their items. */ + wv = xmalloc_widget_value (); + wv->name = "menu"; + wv->value = 0; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + first_wv = wv; + first_pane = 1; + + /* Loop over all panes and items, filling in the tree. */ + i = 0; + while (i < menu_items_used) + { + if (EQ (XVECTOR (menu_items)->contents[i], Qnil)) + { + submenu_stack[submenu_depth++] = save_wv; + save_wv = prev_wv; + prev_wv = 0; + first_pane = 1; + i++; + } + else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda)) + { + prev_wv = save_wv; + save_wv = submenu_stack[--submenu_depth]; + first_pane = 0; + i++; + } + else if (EQ (XVECTOR (menu_items)->contents[i], Qt) + && submenu_depth != 0) + i += MENU_ITEMS_PANE_LENGTH; + /* Ignore a nil in the item list. + It's meaningful only for dialog boxes. */ + else if (EQ (XVECTOR (menu_items)->contents[i], Qquote)) + i += 1; + else if (EQ (XVECTOR (menu_items)->contents[i], Qt)) + { + /* Create a new pane. */ + Lisp_Object pane_name, prefix; + char *pane_string; + pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME); + prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX); +#ifndef HAVE_MULTILINGUAL_MENU + if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name)) + { + pane_name = ENCODE_SYSTEM (pane_name); + AREF (menu_items, i + MENU_ITEMS_PANE_NAME) = pane_name; + } +#endif + pane_string = (NILP (pane_name) + ? "" : (char *) XSTRING (pane_name)->data); + /* If there is just one top-level pane, put all its items directly + under the top-level menu. */ + if (menu_items_n_panes == 1) + pane_string = ""; + + /* If the pane has a meaningful name, + make the pane a top-level menu item + with its items as a submenu beneath it. */ + if (!keymaps && strcmp (pane_string, "")) + { + wv = xmalloc_widget_value (); + if (save_wv) + save_wv->next = wv; + else + first_wv->contents = wv; + wv->name = pane_string; + if (keymaps && !NILP (prefix)) + wv->name++; + wv->value = 0; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + save_wv = wv; + prev_wv = 0; + } + else if (first_pane) + { + save_wv = wv; + prev_wv = 0; + } + first_pane = 0; + i += MENU_ITEMS_PANE_LENGTH; + } + else + { + /* Create a new item within current pane. */ + Lisp_Object item_name, enable, descrip, def, type, selected, help; + + item_name = AREF (menu_items, i + MENU_ITEMS_ITEM_NAME); + enable = AREF (menu_items, i + MENU_ITEMS_ITEM_ENABLE); + descrip = AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY); + def = AREF (menu_items, i + MENU_ITEMS_ITEM_DEFINITION); + type = AREF (menu_items, i + MENU_ITEMS_ITEM_TYPE); + selected = AREF (menu_items, i + MENU_ITEMS_ITEM_SELECTED); + help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP); + +#ifndef HAVE_MULTILINGUAL_MENU + if (STRINGP (item_name) && STRING_MULTIBYTE (item_name)) + { + item_name = ENCODE_SYSTEM (item_name); + AREF (menu_items, i + MENU_ITEMS_ITEM_NAME) = item_name; + } + if (STRINGP (descrip) && STRING_MULTIBYTE (descrip)) + { + descrip = ENCODE_SYSTEM (descrip); + AREF (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY) = descrip; + } +#endif /* not HAVE_MULTILINGUAL_MENU */ + + wv = xmalloc_widget_value (); + if (prev_wv) + prev_wv->next = wv; + else + save_wv->contents = wv; + wv->name = (char *) XSTRING (item_name)->data; + if (!NILP (descrip)) + wv->key = (char *) XSTRING (descrip)->data; + wv->value = 0; + /* Use the contents index as call_data, since we are + restricted to 16-bits. */ + wv->call_data = !NILP (def) ? (void *) (EMACS_INT) i : 0; + wv->enabled = !NILP (enable); + + if (NILP (type)) + wv->button_type = BUTTON_TYPE_NONE; + else if (EQ (type, QCtoggle)) + wv->button_type = BUTTON_TYPE_TOGGLE; + else if (EQ (type, QCradio)) + wv->button_type = BUTTON_TYPE_RADIO; + else + abort (); + + wv->selected = !NILP (selected); + if (!STRINGP (help)) + help = Qnil; + + wv->help = help; + + prev_wv = wv; + + i += MENU_ITEMS_ITEM_LENGTH; + } + } + + /* Deal with the title, if it is non-nil. */ + if (!NILP (title)) + { + widget_value *wv_title = xmalloc_widget_value (); + widget_value *wv_sep = xmalloc_widget_value (); + + /* Maybe replace this separator with a bitmap or owner-draw item + so that it looks better. Having two separators looks odd. */ + wv_sep->name = "--"; + wv_sep->next = first_wv->contents; + wv_sep->help = Qnil; + +#ifndef HAVE_MULTILINGUAL_MENU + if (STRING_MULTIBYTE (title)) + title = ENCODE_SYSTEM (title); +#endif + wv_title->name = (char *) XSTRING (title)->data; + wv_title->enabled = TRUE; + wv_title->title = TRUE; + wv_title->button_type = BUTTON_TYPE_NONE; + wv_title->help = Qnil; + wv_title->next = wv_sep; + first_wv->contents = wv_title; + } + + /* Actually create the menu. */ + menu = NewMenu (POPUP_SUBMENU_ID, "\p"); + fill_submenu (menu, first_wv->contents, 0); + + /* Adjust coordinates to be root-window-relative. */ + pos.h = x; + pos.v = y; + +#if TARGET_API_MAC_CARBON + SetPort (GetWindowPort (FRAME_MAC_WINDOW (f))); +#else + SetPort (FRAME_MAC_WINDOW (f)); +#endif + + LocalToGlobal (&pos); + + /* No selection has been chosen yet. */ + menu_item_selection = 0; + + InsertMenu (menu, -1); + + /* Display the menu. */ + menu_item_selection = LoWord (PopUpMenuSelect (menu, pos.v, pos.h, 0)); + + DeleteMenu (POPUP_SUBMENU_ID); + +#if 0 + /* Clean up extraneous mouse events which might have been generated + during the call. */ + discard_mouse_events (); +#endif + + /* Free the widget_value objects we used to specify the + contents. */ + free_menubar_widget_value_tree (first_wv); + + DisposeMenu (menu); + + /* Find the selected item, and its pane, to return + the proper value. */ + if (menu_item_selection != 0) + { + Lisp_Object prefix, entry; + + prefix = entry = Qnil; + i = 0; + while (i < menu_items_used) + { + if (EQ (XVECTOR (menu_items)->contents[i], Qnil)) + { + subprefix_stack[submenu_depth++] = prefix; + prefix = entry; + i++; + } + else if (EQ (XVECTOR (menu_items)->contents[i], Qlambda)) + { + prefix = subprefix_stack[--submenu_depth]; + i++; + } + else if (EQ (XVECTOR (menu_items)->contents[i], Qt)) + { + prefix + = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_PREFIX]; + i += MENU_ITEMS_PANE_LENGTH; + } + /* Ignore a nil in the item list. + It's meaningful only for dialog boxes. */ + else if (EQ (XVECTOR (menu_items)->contents[i], Qquote)) + i += 1; + else + { + entry + = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_VALUE]; + if (menu_item_selection == i) + { + if (keymaps != 0) + { + int j; + + entry = Fcons (entry, Qnil); + if (!NILP (prefix)) + entry = Fcons (prefix, entry); + for (j = submenu_depth - 1; j >= 0; j--) + if (!NILP (subprefix_stack[j])) + entry = Fcons (subprefix_stack[j], entry); + } + return entry; + } + i += MENU_ITEMS_ITEM_LENGTH; + } + } + } + + return Qnil; +} + + +#ifdef HAVE_DIALOGS +/* Construct native Mac OS menubar based on widget_value tree. */ + +static int +mac_dialog (widget_value *wv) +{ + char *dialog_name; + char *prompt; + char **button_labels; + UInt32 *ref_cons; + int nb_buttons; + int left_count; + int i; + int dialog_width; + Rect rect; + WindowPtr window_ptr; + ControlHandle ch; + int left; + EventRecord event_record; + SInt16 part_code; + int control_part_code; + Point mouse; + + dialog_name = wv->name; + nb_buttons = dialog_name[1] - '0'; + left_count = nb_buttons - (dialog_name[4] - '0'); + button_labels = (char **) alloca (sizeof (char *) * nb_buttons); + ref_cons = (UInt32 *) alloca (sizeof (UInt32) * nb_buttons); + + wv = wv->contents; + prompt = (char *) alloca (strlen (wv->value) + 1); + strcpy (prompt, wv->value); + c2pstr (prompt); + + wv = wv->next; + for (i = 0; i < nb_buttons; i++) + { + button_labels[i] = wv->value; + button_labels[i] = (char *) alloca (strlen (wv->value) + 1); + strcpy (button_labels[i], wv->value); + c2pstr (button_labels[i]); + ref_cons[i] = (UInt32) wv->call_data; + wv = wv->next; + } + + window_ptr = GetNewCWindow (DIALOG_WINDOW_RESOURCE, NULL, (WindowPtr) -1); + +#if TARGET_API_MAC_CARBON + SetPort (GetWindowPort (window_ptr)); +#else + SetPort (window_ptr); +#endif + + TextFont (0); + /* Left and right margins in the dialog are 13 pixels each.*/ + dialog_width = 14; + /* Calculate width of dialog box: 8 pixels on each side of the text + label in each button, 12 pixels between buttons. */ + for (i = 0; i < nb_buttons; i++) + dialog_width += StringWidth (button_labels[i]) + 16 + 12; + + if (left_count != 0 && nb_buttons - left_count != 0) + dialog_width += 12; + + dialog_width = max (dialog_width, StringWidth (prompt) + 26); + + SizeWindow (window_ptr, dialog_width, 78, 0); + ShowWindow (window_ptr); + +#if TARGET_API_MAC_CARBON + SetPort (GetWindowPort (window_ptr)); +#else + SetPort (window_ptr); +#endif + + TextFont (0); + + MoveTo (13, 29); + DrawString (prompt); + + left = 13; + for (i = 0; i < nb_buttons; i++) + { + int button_width = StringWidth (button_labels[i]) + 16; + SetRect (&rect, left, 45, left + button_width, 65); + ch = NewControl (window_ptr, &rect, button_labels[i], 1, 0, 0, 0, + kControlPushButtonProc, ref_cons[i]); + left += button_width + 12; + if (i == left_count - 1) + left += 12; + } + + i = 0; + while (!i) + { + if (WaitNextEvent (mDownMask, &event_record, 10, NULL)) + if (event_record.what == mouseDown) + { + part_code = FindWindow (event_record.where, &window_ptr); + if (part_code == inContent) + { + mouse = event_record.where; + GlobalToLocal (&mouse); + control_part_code = FindControl (mouse, window_ptr, &ch); + if (control_part_code == kControlButtonPart) + if (TrackControl (ch, mouse, NULL)) + i = GetControlReference (ch); + } + } + } + + DisposeWindow (window_ptr); + + return i; +} + +static char * button_names [] = { + "button1", "button2", "button3", "button4", "button5", + "button6", "button7", "button8", "button9", "button10" }; + +static Lisp_Object +mac_dialog_show (f, keymaps, title, error) + FRAME_PTR f; + int keymaps; + Lisp_Object title; + char **error; +{ + int i, nb_buttons=0; + char dialog_name[6]; + int menu_item_selection; + + widget_value *wv, *first_wv = 0, *prev_wv = 0; + + /* Number of elements seen so far, before boundary. */ + int left_count = 0; + /* 1 means we've seen the boundary between left-hand elts and right-hand. */ + int boundary_seen = 0; + + *error = NULL; + + if (menu_items_n_panes > 1) + { + *error = "Multiple panes in dialog box"; + return Qnil; + } + + /* Create a tree of widget_value objects + representing the text label and buttons. */ + { + Lisp_Object pane_name, prefix; + char *pane_string; + pane_name = XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_NAME]; + prefix = XVECTOR (menu_items)->contents[MENU_ITEMS_PANE_PREFIX]; + pane_string = (NILP (pane_name) + ? "" : (char *) XSTRING (pane_name)->data); + prev_wv = xmalloc_widget_value (); + prev_wv->value = pane_string; + if (keymaps && !NILP (prefix)) + prev_wv->name++; + prev_wv->enabled = 1; + prev_wv->name = "message"; + prev_wv->help = Qnil; + first_wv = prev_wv; + + /* Loop over all panes and items, filling in the tree. */ + i = MENU_ITEMS_PANE_LENGTH; + while (i < menu_items_used) + { + + /* Create a new item within current pane. */ + Lisp_Object item_name, enable, descrip, help; + + item_name = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_NAME]; + enable = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_ENABLE]; + descrip + = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_EQUIV_KEY]; + help = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_HELP]; + + if (NILP (item_name)) + { + free_menubar_widget_value_tree (first_wv); + *error = "Submenu in dialog items"; + return Qnil; + } + if (EQ (item_name, Qquote)) + { + /* This is the boundary between left-side elts + and right-side elts. Stop incrementing right_count. */ + boundary_seen = 1; + i++; + continue; + } + if (nb_buttons >= 9) + { + free_menubar_widget_value_tree (first_wv); + *error = "Too many dialog items"; + return Qnil; + } + + wv = xmalloc_widget_value (); + prev_wv->next = wv; + wv->name = (char *) button_names[nb_buttons]; + if (!NILP (descrip)) + wv->key = (char *) XSTRING (descrip)->data; + wv->value = (char *) XSTRING (item_name)->data; + wv->call_data = (void *) i; + /* menu item is identified by its index in menu_items table */ + wv->enabled = !NILP (enable); + wv->help = Qnil; + prev_wv = wv; + + if (! boundary_seen) + left_count++; + + nb_buttons++; + i += MENU_ITEMS_ITEM_LENGTH; + } + + /* If the boundary was not specified, + by default put half on the left and half on the right. */ + if (! boundary_seen) + left_count = nb_buttons - nb_buttons / 2; + + wv = xmalloc_widget_value (); + wv->name = dialog_name; + wv->help = Qnil; + + /* Dialog boxes use a really stupid name encoding + which specifies how many buttons to use + and how many buttons are on the right. + The Q means something also. */ + dialog_name[0] = 'Q'; + dialog_name[1] = '0' + nb_buttons; + dialog_name[2] = 'B'; + dialog_name[3] = 'R'; + /* Number of buttons to put on the right. */ + dialog_name[4] = '0' + nb_buttons - left_count; + dialog_name[5] = 0; + wv->contents = first_wv; + first_wv = wv; + } + + /* Actually create the dialog. */ +#ifdef HAVE_DIALOGS + menu_item_selection = mac_dialog (first_wv); +#else + menu_item_selection = 0; +#endif + + /* Free the widget_value objects we used to specify the contents. */ + free_menubar_widget_value_tree (first_wv); + + /* Find the selected item, and its pane, to return the proper + value. */ + if (menu_item_selection != 0) + { + Lisp_Object prefix; + + prefix = Qnil; + i = 0; + while (i < menu_items_used) + { + Lisp_Object entry; + + if (EQ (XVECTOR (menu_items)->contents[i], Qt)) + { + prefix + = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_PREFIX]; + i += MENU_ITEMS_PANE_LENGTH; + } + else + { + entry + = XVECTOR (menu_items)->contents[i + MENU_ITEMS_ITEM_VALUE]; + if (menu_item_selection == i) + { + if (keymaps != 0) + { + entry = Fcons (entry, Qnil); + if (!NILP (prefix)) + entry = Fcons (prefix, entry); + } + return entry; + } + i += MENU_ITEMS_ITEM_LENGTH; + } + } + } + + return Qnil; +} +#endif /* HAVE_DIALOGS */ + + +/* Is this item a separator? */ +static int +name_is_separator (name) + char *name; +{ + char *start = name; + + /* Check if name string consists of only dashes ('-'). */ + while (*name == '-') name++; + /* Separators can also be of the form "--:TripleSuperMegaEtched" + or "--deep-shadow". We don't implement them yet, se we just treat + them like normal separators. */ + return (*name == '\0' || start + 2 == name); +} + +static void +add_menu_item (MenuHandle menu, widget_value *wv, int submenu, int indent, + int force_disable) +{ + Str255 item_name; + int pos, i; + + if (name_is_separator (wv->name)) + AppendMenu (menu, "\p-"); + else + { + AppendMenu (menu, "\pX"); + +#if TARGET_API_MAC_CARBON + pos = CountMenuItems (menu); +#else + pos = CountMItems (menu); +#endif + + strcpy (item_name, ""); + for (i = 0; i < indent; i++) + strcat (item_name, " "); + strcat (item_name, wv->name); + if (wv->key != NULL) + { + strcat (item_name, " "); + strcat (item_name, wv->key); + } + c2pstr (item_name); + SetMenuItemText (menu, pos, item_name); + + if (wv->enabled && !force_disable) +#if TARGET_API_MAC_CARBON + EnableMenuItem (menu, pos); +#else + EnableItem (menu, pos); +#endif + else +#if TARGET_API_MAC_CARBON + DisableMenuItem (menu, pos); +#else + DisableItem (menu, pos); +#endif + + /* Draw radio buttons and tickboxes. */ + { + if (wv->selected && (wv->button_type == BUTTON_TYPE_TOGGLE || + wv->button_type == BUTTON_TYPE_RADIO)) + SetItemMark (menu, pos, checkMark); + else + SetItemMark (menu, pos, noMark); + } + } + + SetMenuItemRefCon (menu, pos, (UInt32) wv->call_data); + + if (submenu != NULL) + SetMenuItemHierarchicalID (menu, pos, submenu); +} + +static int submenu_id; + +/* Construct native Mac OS menubar based on widget_value tree. */ + +static void +fill_submenu (MenuHandle menu, widget_value *wv, int indent) +{ + for ( ; wv != NULL; wv = wv->next) + if (wv->contents) + { + add_menu_item (menu, wv, NULL, indent, 1); + + fill_submenu (menu, wv->contents, indent + 1); + } + else + add_menu_item (menu, wv, NULL, indent, 0); +} + + +/* Construct native Mac OS menu based on widget_value tree. */ + +static void +fill_menu (MenuHandle menu, widget_value *wv) +{ + for ( ; wv != NULL; wv = wv->next) + if (wv->contents) + { + MenuHandle submenu = NewMenu (submenu_id, "\pX"); + fill_submenu (submenu, wv->contents, 0); + InsertMenu (submenu, -1); + add_menu_item (menu, wv, submenu_id, 0, 0); + submenu_id++; + } + else + add_menu_item (menu, wv, NULL, 0, 0); +} + +/* Construct native Mac OS menubar based on widget_value tree. */ + +static void +fill_menubar (widget_value *wv) +{ + int id; + + submenu_id = MIN_SUBMENU_ID; + + for (id = MIN_MENU_ID; wv != NULL; wv = wv->next, id++) + { + MenuHandle menu; + Str255 title; + + strcpy (title, wv->name); + c2pstr (title); + menu = NewMenu (id, title); + + if (wv->contents) + fill_menu (menu, wv->contents); + + InsertMenu (menu, 0); + } +} + +#endif /* HAVE_MENUS */ + + +void +syms_of_macmenu () +{ + staticpro (&menu_items); + menu_items = Qnil; + + Qdebug_on_next_call = intern ("debug-on-next-call"); + staticpro (&Qdebug_on_next_call); + + DEFVAR_LISP ("menu-updating-frame", &Vmenu_updating_frame, + doc: /* Frame for which we are updating a menu. +The enable predicate for a menu command should check this variable. */); + Vmenu_updating_frame = Qnil; + + defsubr (&Sx_popup_menu); +#ifdef HAVE_MENUS + defsubr (&Sx_popup_dialog); +#endif +} |