Mercurial > emacs
changeset 95646:816ec0114c3c
* menu.c: New file. Relocate platform-independent menu definitions
from xmenu.c.
author | Chong Yidong <cyd@stupidchicken.com> |
---|---|
date | Sun, 08 Jun 2008 04:36:08 +0000 |
parents | 454e3c065a98 |
children | 0f6a0e86ee24 |
files | src/menu.c |
diffstat | 1 files changed, 996 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/menu.c Sun Jun 08 04:36:08 2008 +0000 @@ -0,0 +1,996 @@ +/* Platform-independent code for terminal communications. + Copyright (C) 1986, 1988, 1993, 1994, 1996, 1999, 2000, 2001, 2002, 2003, + 2004, 2005, 2006, 2007, 2008 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 3 of the License, 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. If not, see <http://www.gnu.org/licenses/>. */ + +#include <config.h> +#include <stdio.h> + +#include "lisp.h" +#include "keyboard.h" +#include "keymap.h" +#include "frame.h" +#include "termhooks.h" +#include "blockinput.h" +#include "dispextern.h" + +#ifdef USE_X_TOOLKIT +#include "../lwlib/lwlib.h" +#endif + +#ifdef USE_GTK +#include "gtkutil.h" +#endif + +#ifdef HAVE_NTGUI +/* Definitions copied from lwlib.h */ +typedef void * XtPointer; +typedef char Boolean; +enum button_type +{ + BUTTON_TYPE_NONE, + BUTTON_TYPE_TOGGLE, + BUTTON_TYPE_RADIO +}; + +/* This structure is based on the one in ../lwlib/lwlib.h */ +typedef struct _widget_value +{ + Lisp_Object lname; + char* name; + char* value; + Lisp_Object lkey; + char* key; + Lisp_Object help; + Boolean enabled; + Boolean selected; + enum button_type button_type; + Boolean title; + struct _widget_value* contents; + XtPointer call_data; + struct _widget_value* next; +} widget_value; + +/* Local memory management */ +#define local_heap (GetProcessHeap ()) +#define local_alloc(n) (HeapAlloc (local_heap, HEAP_ZERO_MEMORY, (n))) +#define local_free(p) (HeapFree (local_heap, 0, ((LPVOID) (p)))) + +#define malloc_widget_value() ((widget_value *) local_alloc (sizeof (widget_value))) +#define free_widget_value(wv) (local_free ((wv))) + +extern AppendMenuW_Proc unicode_append_menu; + +#endif /* HAVE_NTGUI */ + + +/* Define HAVE_BOXES if menus can handle radio and toggle buttons. */ +#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NTGUI) +#define HAVE_BOXES 1 +#endif + +extern Lisp_Object QCtoggle, QCradio; + +Lisp_Object menu_items; + +/* If non-nil, means that the global vars defined here are already in use. + Used to detect cases where we try to re-enter this non-reentrant code. */ +Lisp_Object menu_items_inuse; + +/* Number of slots currently allocated in menu_items. */ +int menu_items_allocated; + +/* This is the index in menu_items of the first empty slot. */ +int menu_items_used; + +/* The number of panes currently recorded in menu_items, + excluding those within submenus. */ +int menu_items_n_panes; + +/* Current depth within submenus. */ +static int menu_items_submenu_depth; + +void +init_menu_items () +{ + if (!NILP (menu_items_inuse)) + error ("Trying to use a menu from within a menu-entry"); + + if (NILP (menu_items)) + { + menu_items_allocated = 60; + menu_items = Fmake_vector (make_number (menu_items_allocated), Qnil); + } + + menu_items_inuse = Qt; + 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. */ + +void +finish_menu_items () +{ +} + +Lisp_Object +unuse_menu_items (dummy) + Lisp_Object dummy; +{ + return menu_items_inuse = Qnil; +} + +/* Call when finished using the data for the current menu + in menu_items. */ + +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; + } + xassert (NILP (menu_items_inuse)); +} + +/* This undoes save_menu_items, and it is called by the specpdl unwind + mechanism. */ + +static Lisp_Object +restore_menu_items (saved) + Lisp_Object saved; +{ + menu_items = XCAR (saved); + menu_items_inuse = (! NILP (menu_items) ? Qt : Qnil); + menu_items_allocated = (VECTORP (menu_items) ? ASIZE (menu_items) : 0); + saved = XCDR (saved); + menu_items_used = XINT (XCAR (saved)); + saved = XCDR (saved); + menu_items_n_panes = XINT (XCAR (saved)); + saved = XCDR (saved); + menu_items_submenu_depth = XINT (XCAR (saved)); + return Qnil; +} + +/* Push the whole state of menu_items processing onto the specpdl. + It will be restored when the specpdl is unwound. */ + +void +save_menu_items () +{ + Lisp_Object saved = list4 (!NILP (menu_items_inuse) ? menu_items : Qnil, + make_number (menu_items_used), + make_number (menu_items_n_panes), + make_number (menu_items_submenu_depth)); + record_unwind_protect (restore_menu_items, saved); + menu_items_inuse = Qnil; + menu_items = Qnil; +} + + +/* Make the menu_items vector twice as large. */ + +static void +grow_menu_items () +{ + menu_items_allocated *= 2; + menu_items = larger_vector (menu_items, menu_items_allocated, Qnil); +} + +/* 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; +} + +/* Args passed between single_keymap_panes and single_menu_item. */ +struct skp + { + Lisp_Object pending_maps; + int maxdepth, notreal; + int notbuttons; + }; + +static void single_menu_item P_ ((Lisp_Object, Lisp_Object, Lisp_Object, + void *)); + +/* 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; +{ + struct skp skp; + struct gcpro gcpro1; + + skp.pending_maps = Qnil; + skp.maxdepth = maxdepth; + skp.notreal = notreal; + skp.notbuttons = 0; + + if (maxdepth <= 0) + return; + + push_menu_pane (pane_name, prefix); + +#ifndef HAVE_BOXES + /* Remember index for first item in this pane so we can go back and + add a prefix when (if) we see the first button. After that, notbuttons + is set to 0, to mark that we have seen a button and all non button + items need a prefix. */ + skp.notbuttons = menu_items_used; +#endif + + GCPRO1 (skp.pending_maps); + map_keymap_canonical (keymap, single_menu_item, Qnil, &skp); + UNGCPRO; + + /* Process now any submenus which want to be panes at this level. */ + while (CONSP (skp.pending_maps)) + { + Lisp_Object elt, eltcdr, string; + elt = XCAR (skp.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 *menu_show. */ + single_keymap_panes (Fcar (elt), string, + XCDR (eltcdr), notreal, maxdepth - 1); + skp.pending_maps = XCDR (skp.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. + SKP->PENDING_MAPS_PTR is a list of keymaps waiting to be made into + separate panes. + If SKP->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 SKP->MAXDEPTH levels, ignore them. */ + +static void +single_menu_item (key, item, dummy, skp_v) + Lisp_Object key, item, dummy; + void *skp_v; +{ + Lisp_Object map, item_string, enabled; + struct gcpro gcpro1, gcpro2; + int res; + struct skp *skp = skp_v; + + /* Parse the menu item and leave the result in item_properties. */ + GCPRO2 (key, item); + res = parse_menu_item (item, skp->notreal, 0); + UNGCPRO; + if (!res) + return; /* Not a menu item. */ + + map = XVECTOR (item_properties)->contents[ITEM_PROPERTY_MAP]; + + if (skp->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, skp->maxdepth - 1); + return; + } + + enabled = XVECTOR (item_properties)->contents[ITEM_PROPERTY_ENABLE]; + item_string = XVECTOR (item_properties)->contents[ITEM_PROPERTY_NAME]; + + if (!NILP (map) && SREF (item_string, 0) == '@') + { + if (!NILP (enabled)) + /* An enabled separate pane. Remember this to handle it later. */ + skp->pending_maps = Fcons (Fcons (map, Fcons (item_string, key)), + skp->pending_maps); + return; + } + +#ifdef HAVE_X_WINDOWS +#ifndef HAVE_BOXES + /* Simulate radio buttons and toggle boxes by putting a prefix in + front of them. */ + { + Lisp_Object prefix = Qnil; + Lisp_Object type = XVECTOR (item_properties)->contents[ITEM_PROPERTY_TYPE]; + if (!NILP (type)) + { + Lisp_Object selected + = XVECTOR (item_properties)->contents[ITEM_PROPERTY_SELECTED]; + + if (skp->notbuttons) + /* The first button. Line up previous items in this menu. */ + { + int index = skp->notbuttons; /* Index for first item this menu. */ + int submenu = 0; + Lisp_Object tem; + while (index < menu_items_used) + { + tem + = XVECTOR (menu_items)->contents[index + MENU_ITEMS_ITEM_NAME]; + if (NILP (tem)) + { + index++; + submenu++; /* Skip sub menu. */ + } + else if (EQ (tem, Qlambda)) + { + index++; + submenu--; /* End sub menu. */ + } + else if (EQ (tem, Qt)) + index += 3; /* Skip new pane marker. */ + else if (EQ (tem, Qquote)) + index++; /* Skip a left, right divider. */ + else + { + if (!submenu && SREF (tem, 0) != '\0' + && SREF (tem, 0) != '-') + XVECTOR (menu_items)->contents[index + MENU_ITEMS_ITEM_NAME] + = concat2 (build_string (" "), tem); + index += MENU_ITEMS_ITEM_LENGTH; + } + } + skp->notbuttons = 0; + } + + /* Calculate prefix, if any, for this item. */ + if (EQ (type, QCtoggle)) + prefix = build_string (NILP (selected) ? "[ ] " : "[X] "); + else if (EQ (type, QCradio)) + prefix = build_string (NILP (selected) ? "( ) " : "(*) "); + } + /* Not a button. If we have earlier buttons, then we need a prefix. */ + else if (!skp->notbuttons && SREF (item_string, 0) != '\0' + && SREF (item_string, 0) != '-') + prefix = build_string (" "); + + if (!NILP (prefix)) + item_string = concat2 (prefix, item_string); + } +#endif /* not HAVE_BOXES */ + +#if ! defined (USE_X_TOOLKIT) && ! defined (USE_GTK) + if (!NILP (map)) + /* Indicate visually that this is a submenu. */ + item_string = concat2 (item_string, build_string (" >")); +#endif + +#endif /* HAVE_X_WINDOWS */ + + 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]); + +#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NTGUI) + /* Display a submenu using the toolkit. */ + if (! (NILP (map) || NILP (enabled))) + { + push_submenu_start (); + single_keymap_panes (map, Qnil, key, 0, skp->maxdepth - 1); + push_submenu_end (); + } +#endif +} + +/* 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. */ + +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 (); +} + + +/* 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; CONSP (tail); tail = XCDR (tail)) + { + item = XCAR (tail); + if (STRINGP (item)) + push_menu_item (ENCODE_MENU_STRING (item), Qnil, Qnil, Qt, + Qnil, Qnil, Qnil, Qnil); + else if (CONSP (item)) + { + item1 = XCAR (item); + CHECK_STRING (item1); + push_menu_item (ENCODE_MENU_STRING (item1), Qt, XCDR (item), + Qt, Qnil, Qnil, Qnil, Qnil); + } + else + push_left_right_boundary (); + + } +} + +/* 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. */ +void +list_of_panes (menu) + Lisp_Object menu; +{ + Lisp_Object tail; + + init_menu_items (); + + for (tail = menu; CONSP (tail); tail = XCDR (tail)) + { + Lisp_Object elt, pane_name, pane_data; + elt = XCAR (tail); + pane_name = Fcar (elt); + CHECK_STRING (pane_name); + push_menu_pane (ENCODE_MENU_STRING (pane_name), Qnil); + pane_data = Fcdr (elt); + CHECK_CONS (pane_data); + list_of_items (pane_data); + } + + finish_menu_items (); +} + +/* Set up data in menu_items 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. */ +int +parse_single_submenu (item_key, item_name, maps) + Lisp_Object item_key, item_name, maps; +{ + Lisp_Object length; + int len; + Lisp_Object *mapvec; + int i; + 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); + } + + /* 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 (!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 + { + Lisp_Object prompt; + prompt = Fkeymap_prompt (mapvec[i]); + single_keymap_panes (mapvec[i], + !NILP (prompt) ? prompt : item_name, + item_key, 0, 10); + } + } + + return top_level_items; +} + + +#if defined (USE_X_TOOLKIT) || defined (USE_GTK) || defined (HAVE_NTGUI) + +/* 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; +} + +/* Create a tree of widget_value objects + representing the panes and items + in menu_items starting at index START, up to index END. */ + +widget_value * +digest_single_submenu (start, end, top_level_items) + int start, end, top_level_items; +{ + widget_value *wv, *prev_wv, *save_wv, *first_wv; + int i; + int submenu_depth = 0; + widget_value **submenu_stack; + int panes_seen = 0; + + 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 by the preceding call + to parse_single_submenu and construct a tree of widget_value objects. + Ignore the panes and items used by previous calls to + digest_single_submenu, even though those are also in menu_items. */ + i = start; + while (i < end) + { + 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; + + panes_seen++; + + pane_name = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_NAME]; + prefix = XVECTOR (menu_items)->contents[i + MENU_ITEMS_PANE_PREFIX]; + +#ifdef HAVE_NTGUI + if (STRINGP (pane_name)) + { + if (unicode_append_menu) + /* Encode as UTF-8 for now. */ + pane_name = ENCODE_UTF_8 (pane_name); + else if (STRING_MULTIBYTE (pane_name)) + pane_name = ENCODE_SYSTEM (pane_name); + + ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name); + } +#elif !defined (HAVE_MULTILINGUAL_MENU) + if (STRINGP (pane_name) && STRING_MULTIBYTE (pane_name)) + { + pane_name = ENCODE_MENU_STRING (pane_name); + ASET (menu_items, i + MENU_ITEMS_PANE_NAME, pane_name); + } +#endif + + pane_string = (NILP (pane_name) + ? "" : (char *) SDATA (pane_name)); + /* 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->lname = pane_name; + /* Set value to 1 so update_submenu_strings can handle '@' */ + wv->value = (char *)1; + wv->enabled = 1; + wv->button_type = BUTTON_TYPE_NONE; + wv->help = Qnil; + save_wv = wv; + } + else + save_wv = first_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; + + /* All items should be contained in panes. */ + if (panes_seen == 0) + abort (); + + 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); + +#ifdef HAVE_NTGUI + if (STRINGP (item_name)) + { + if (unicode_append_menu) + item_name = ENCODE_UTF_8 (item_name); + else if (STRING_MULTIBYTE (item_name)) + item_name = ENCODE_SYSTEM (item_name); + + ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name); + } + + if (STRINGP (descrip) && STRING_MULTIBYTE (descrip)) + { + descrip = ENCODE_SYSTEM (descrip); + ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip); + } +#elif !defined (HAVE_MULTILINGUAL_MENU) + if (STRING_MULTIBYTE (item_name)) + { + item_name = ENCODE_MENU_STRING (item_name); + ASET (menu_items, i + MENU_ITEMS_ITEM_NAME, item_name); + } + + if (STRINGP (descrip) && STRING_MULTIBYTE (descrip)) + { + descrip = ENCODE_MENU_STRING (descrip); + ASET (menu_items, i + MENU_ITEMS_ITEM_EQUIV_KEY, descrip); + } +#endif + + wv = xmalloc_widget_value (); + if (prev_wv) + prev_wv->next = wv; + else + save_wv->contents = wv; + + wv->lname = item_name; + if (!NILP (descrip)) + wv->lkey = descrip; + 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; +} + +/* Walk through the widget_value tree starting at FIRST_WV and update + the char * pointers from the corresponding lisp values. + We do this after building the whole tree, since GC may happen while the + tree is constructed, and small strings are relocated. So we must wait + until no GC can happen before storing pointers into lisp values. */ +void +update_submenu_strings (first_wv) + widget_value *first_wv; +{ + widget_value *wv; + + for (wv = first_wv; wv; wv = wv->next) + { + if (STRINGP (wv->lname)) + { + wv->name = (char *) SDATA (wv->lname); + + /* Ignore the @ that means "separate pane". + This is a kludge, but this isn't worth more time. */ + if (wv->value == (char *)1) + { + if (wv->name[0] == '@') + wv->name++; + wv->value = 0; + } + } + + if (STRINGP (wv->lkey)) + wv->key = (char *) SDATA (wv->lkey); + + if (wv->contents) + update_submenu_strings (wv->contents); + } +} + +/* Find the menu selection and store it in the keyboard buffer. + F is the frame the menu is on. + MENU_BAR_ITEMS_USED is the length of VECTOR. + VECTOR is an array of menu events for the whole menu. */ + +void +find_and_call_menu_selection (f, menu_bar_items_used, vector, client_data) + FRAME_PTR f; + EMACS_INT menu_bar_items_used; + Lisp_Object vector; + void *client_data; +{ + Lisp_Object prefix, entry; + Lisp_Object *subprefix_stack; + int submenu_depth = 0; + int i; + + entry = Qnil; + subprefix_stack = (Lisp_Object *) alloca (menu_bar_items_used * sizeof (Lisp_Object)); + prefix = Qnil; + i = 0; + + while (i < 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; + EVENT_INIT (buf); + + 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); + + return; + } + i += MENU_ITEMS_ITEM_LENGTH; + } + } +} + +#endif /* USE_X_TOOLKIT || USE_GTK || HAVE_NTGUI */ + +void +syms_of_menu () +{ + staticpro (&menu_items); + menu_items = Qnil; + menu_items_inuse = Qnil; +}