+\f
+/***********************************************************************
+ Menus
+ ***********************************************************************/
+
+#if !defined (MSDOS)
+
+/* TTY menu implementation and main ideas are borrowed from msdos.c.
+
+ However, unlike on MSDOS, where the menu text is drawn directly to
+ the display video memory, on a TTY we use display_string (see
+ display_tty_menu_item in xdisp.c) to put the glyphs produced from
+ the menu items into the frame's 'desired_matrix' glyph matrix, and
+ then call update_frame_with_menu to deliver the results to the
+ glass. The previous contents of the screen, in the form of the
+ current_matrix, is stashed away, and used to restore screen
+ contents when the menu selection changes or when the final
+ selection is made and the menu should be popped down.
+
+ The idea of this implementation was suggested by Gerd Moellmann. */
+
+#define TTYM_FAILURE -1
+#define TTYM_SUCCESS 1
+#define TTYM_NO_SELECT 2
+#define TTYM_IA_SELECT 3
+#define TTYM_NEXT 4
+#define TTYM_PREV 5
+
+/* These hold text of the current and the previous menu help messages. */
+static const char *menu_help_message, *prev_menu_help_message;
+/* Pane number and item number of the menu item which generated the
+ last menu help message. */
+static int menu_help_paneno, menu_help_itemno;
+
+static Lisp_Object Qtty_menu_navigation_map, Qtty_menu_exit;
+static Lisp_Object Qtty_menu_prev_item, Qtty_menu_next_item;
+static Lisp_Object Qtty_menu_next_menu, Qtty_menu_prev_menu;
+static Lisp_Object Qtty_menu_select, Qtty_menu_ignore;
+static Lisp_Object Qtty_menu_mouse_movement;
+
+typedef struct tty_menu_struct
+{
+ int count;
+ char **text;
+ struct tty_menu_struct **submenu;
+ int *panenumber; /* Also used as enabled flag. */
+ ptrdiff_t allocated;
+ int panecount;
+ int width;
+ const char **help_text;
+} tty_menu;
+
+/* Create a brand new menu structure. */
+
+static tty_menu *
+tty_menu_create (void)
+{
+ return xzalloc (sizeof *tty_menu_create ());
+}
+
+/* Allocate some (more) memory for MENU ensuring that there is room for one
+ more item. */
+
+static void
+tty_menu_make_room (tty_menu *menu)
+{
+ if (menu->allocated == menu->count)
+ {
+ ptrdiff_t allocated = menu->allocated;
+ menu->text = xpalloc (menu->text, &allocated, 1, -1, sizeof *menu->text);
+ menu->text = xrealloc (menu->text, allocated * sizeof *menu->text);
+ menu->submenu = xrealloc (menu->submenu,
+ allocated * sizeof *menu->submenu);
+ menu->panenumber = xrealloc (menu->panenumber,
+ allocated * sizeof *menu->panenumber);
+ menu->help_text = xrealloc (menu->help_text,
+ allocated * sizeof *menu->help_text);
+ menu->allocated = allocated;
+ }
+}
+
+/* Search the given menu structure for a given pane number. */
+
+static tty_menu *
+tty_menu_search_pane (tty_menu *menu, int pane)
+{
+ int i;
+ tty_menu *try;
+
+ for (i = 0; i < menu->count; i++)
+ if (menu->submenu[i])
+ {
+ if (pane == menu->panenumber[i])
+ return menu->submenu[i];
+ try = tty_menu_search_pane (menu->submenu[i], pane);
+ if (try)
+ return try;
+ }
+ return (tty_menu *) 0;
+}
+
+/* Determine how much screen space a given menu needs. */
+
+static void
+tty_menu_calc_size (tty_menu *menu, int *width, int *height)
+{
+ int i, h2, w2, maxsubwidth, maxheight;
+
+ maxsubwidth = menu->width;
+ maxheight = menu->count;
+ for (i = 0; i < menu->count; i++)
+ {
+ if (menu->submenu[i])
+ {
+ tty_menu_calc_size (menu->submenu[i], &w2, &h2);
+ if (w2 > maxsubwidth) maxsubwidth = w2;
+ if (i + h2 > maxheight) maxheight = i + h2;
+ }
+ }
+ *width = maxsubwidth;
+ *height = maxheight;
+}
+
+static void
+mouse_get_xy (int *x, int *y)
+{
+ struct frame *sf = SELECTED_FRAME ();
+ Lisp_Object lmx = Qnil, lmy = Qnil, lisp_dummy;
+ enum scroll_bar_part part_dummy;
+ Time time_dummy;
+
+ if (FRAME_TERMINAL (sf)->mouse_position_hook)
+ (*FRAME_TERMINAL (sf)->mouse_position_hook) (&sf, -1,
+ &lisp_dummy, &part_dummy,
+ &lmx, &lmy,
+ &time_dummy);
+ if (!NILP (lmx))
+ {
+ *x = XINT (lmx);
+ *y = XINT (lmy);
+ }
+}
+
+/* Display MENU at (X,Y) using FACES, starting with FIRST_ITEM
+ (zero-based). */
+
+static void
+tty_menu_display (tty_menu *menu, int x, int y, int pn, int *faces,
+ int mx, int my, int first_item, bool disp_help)
+{
+ int i, face, width, enabled, mousehere, row, col;
+ struct frame *sf = SELECTED_FRAME ();
+ struct tty_display_info *tty = FRAME_TTY (sf);
+ /* Don't try to display more menu items than the console can display
+ using the available screen lines. Exclude the echo area line, as
+ it will be overwritten by the help-echo anyway. */
+ int max_items = min (menu->count - first_item, FRAME_LINES (sf) - 1 - y);
+
+ menu_help_message = NULL;
+
+ width = menu->width;
+ col = cursorX (tty);
+ row = cursorY (tty);
+ for (i = 0; i < max_items; i++)
+ {
+ int max_width = width + 2; /* +2 for padding blanks on each side */
+ int j = i + first_item;
+
+ if (menu->submenu[j])
+ max_width += 2; /* for displaying " >" after the item */
+ enabled
+ = (!menu->submenu[j] && menu->panenumber[j]) || (menu->submenu[j]);
+ mousehere = (y + i == my && x <= mx && mx < x + max_width);
+ face = faces[enabled + mousehere * 2];
+ /* Display the menu help string for the i-th menu item even if
+ the menu item is currently disabled. That's what the GUI
+ code does. */
+ if (disp_help && enabled + mousehere * 2 >= 2)
+ {
+ menu_help_message = menu->help_text[j];
+ menu_help_paneno = pn - 1;
+ menu_help_itemno = j;
+ }
+ display_tty_menu_item (menu->text[j], max_width, face, x, y + i,
+ menu->submenu[j] != NULL);
+ }
+ update_frame_with_menu (sf);
+ cursor_to (sf, row, col);
+}
+
+/* --------------------------- X Menu emulation ---------------------- */
+
+/* Create a new pane and place it on the outer-most level. */
+
+static int
+tty_menu_add_pane (tty_menu *menu, const char *txt)
+{
+ int len;
+ const unsigned char *p;
+
+ tty_menu_make_room (menu);
+ menu->submenu[menu->count] = tty_menu_create ();
+ menu->text[menu->count] = (char *)txt;
+ menu->panenumber[menu->count] = ++menu->panecount;
+ menu->help_text[menu->count] = NULL;
+ menu->count++;
+
+ /* Update the menu width, if necessary. */
+ for (len = 0, p = (unsigned char *) txt; *p; )
+ {
+ int ch_len;
+ int ch = STRING_CHAR_AND_LENGTH (p, ch_len);
+
+ len += CHAR_WIDTH (ch);
+ p += ch_len;
+ }
+
+ if (len > menu->width)
+ menu->width = len;
+
+ return menu->panecount;
+}
+
+/* Create a new item in a menu pane. */
+
+static bool
+tty_menu_add_selection (tty_menu *menu, int pane,
+ char *txt, bool enable, char const *help_text)
+{
+ int len;
+ unsigned char *p;
+
+ if (pane)
+ {
+ menu = tty_menu_search_pane (menu, pane);
+ if (! menu)
+ return 0;
+ }
+ tty_menu_make_room (menu);
+ menu->submenu[menu->count] = (tty_menu *) 0;
+ menu->text[menu->count] = txt;
+ menu->panenumber[menu->count] = enable;
+ menu->help_text[menu->count] = help_text;
+ menu->count++;
+
+ /* Update the menu width, if necessary. */
+ for (len = 0, p = (unsigned char *) txt; *p; )
+ {
+ int ch_len;
+ int ch = STRING_CHAR_AND_LENGTH (p, ch_len);
+
+ len += CHAR_WIDTH (ch);
+ p += ch_len;
+ }
+
+ if (len > menu->width)
+ menu->width = len;
+
+ return 1;
+}
+
+/* Decide where the menu would be placed if requested at (X,Y). */
+
+static void
+tty_menu_locate (tty_menu *menu, int x, int y,
+ int *ulx, int *uly, int *width, int *height)
+{
+ tty_menu_calc_size (menu, width, height);
+ *ulx = x + 1;
+ *uly = y;
+ *width += 2;
+}
+
+struct tty_menu_state
+{
+ struct glyph_matrix *screen_behind;
+ tty_menu *menu;
+ int pane;
+ int x, y;
+};
+
+/* Save away the contents of frame F's current frame matrix, and
+ enable all its rows. Value is a glyph matrix holding the contents
+ of F's current frame matrix with all its glyph rows enabled. */
+
+static struct glyph_matrix *
+save_and_enable_current_matrix (struct frame *f)
+{
+ int i;
+ struct glyph_matrix *saved = xzalloc (sizeof *saved);
+ saved->nrows = f->current_matrix->nrows;
+ saved->rows = xzalloc (saved->nrows * sizeof *saved->rows);
+
+ for (i = 0; i < saved->nrows; ++i)
+ {
+ struct glyph_row *from = f->current_matrix->rows + i;
+ struct glyph_row *to = saved->rows + i;
+ ptrdiff_t nbytes = from->used[TEXT_AREA] * sizeof (struct glyph);
+
+ to->glyphs[TEXT_AREA] = xmalloc (nbytes);
+ memcpy (to->glyphs[TEXT_AREA], from->glyphs[TEXT_AREA], nbytes);
+ to->used[TEXT_AREA] = from->used[TEXT_AREA];
+ /* Make sure every row is enabled, or else update_frame will not
+ redraw them. (Rows that are identical to what is already on
+ screen will not be redrawn anyway.) */
+ to->enabled_p = 1;
+ to->hash = from->hash;
+ }
+
+ return saved;
+}
+
+/* Restore the contents of frame F's desired frame matrix from SAVED,
+ and free memory associated with SAVED. */
+
+static void
+restore_desired_matrix (struct frame *f, struct glyph_matrix *saved)
+{
+ int i;
+
+ for (i = 0; i < saved->nrows; ++i)
+ {
+ struct glyph_row *from = saved->rows + i;
+ struct glyph_row *to = f->desired_matrix->rows + i;
+ ptrdiff_t nbytes = from->used[TEXT_AREA] * sizeof (struct glyph);
+
+ eassert (to->glyphs[TEXT_AREA] != from->glyphs[TEXT_AREA]);
+ memcpy (to->glyphs[TEXT_AREA], from->glyphs[TEXT_AREA], nbytes);
+ to->used[TEXT_AREA] = from->used[TEXT_AREA];
+ to->enabled_p = from->enabled_p;
+ to->hash = from->hash;
+ }
+}
+
+static void
+free_saved_screen (struct glyph_matrix *saved)
+{
+ int i;
+
+ if (!saved)
+ return; /* already freed */
+
+ for (i = 0; i < saved->nrows; ++i)
+ {
+ struct glyph_row *from = saved->rows + i;
+
+ xfree (from->glyphs[TEXT_AREA]);
+ }
+
+ xfree (saved->rows);
+ xfree (saved);
+}
+
+/* Update the display of frame F from its saved contents. */
+static void
+screen_update (struct frame *f, struct glyph_matrix *mtx)
+{
+ restore_desired_matrix (f, mtx);
+ update_frame_with_menu (f);
+}
+
+typedef enum {
+ MI_QUIT_MENU = -1,
+ MI_CONTINUE = 0,
+ MI_ITEM_SELECTED = 1,
+ MI_NEXT_ITEM = 2,
+ MI_PREV_ITEM = 3,
+ MI_SCROLL_FORWARD = 4,
+ MI_SCROLL_BACK = 5
+} mi_result;
+
+/* Read user input and return X and Y coordinates where that input
+ puts us. We only consider mouse movement and click events, and
+ keyboard movement commands; the rest are ignored. */
+static mi_result
+read_menu_input (struct frame *sf, int *x, int *y, int min_y, int max_y,
+ bool *first_time)
+{
+ if (*first_time)
+ {
+ *first_time = false;
+ sf->mouse_moved = 1;
+ }
+ else
+ {
+ Lisp_Object cmd;
+ bool usable_input = 1;
+ mi_result st = MI_CONTINUE;
+ struct tty_display_info *tty = FRAME_TTY (sf);
+ Lisp_Object saved_mouse_tracking = do_mouse_tracking;
+
+ /* Signal the keyboard reading routines we are displaying a menu
+ on this terminal. */
+ tty->showing_menu = 1;
+ /* We want mouse movements be reported by read_menu_command. */
+ do_mouse_tracking = Qt;
+ do {
+ cmd = read_menu_command ();
+ } while (NILP (cmd));
+ tty->showing_menu = 0;
+ do_mouse_tracking = saved_mouse_tracking;
+
+ if (EQ (cmd, Qt) || EQ (cmd, Qtty_menu_exit))
+ return MI_QUIT_MENU;
+ if (EQ (cmd, Qtty_menu_mouse_movement))
+ mouse_get_xy (x, y);
+ else if (EQ (cmd, Qtty_menu_next_menu))
+ {
+ usable_input = 0;
+ st = MI_NEXT_ITEM;
+ }
+ else if (EQ (cmd, Qtty_menu_prev_menu))
+ {
+ usable_input = 0;
+ st = MI_PREV_ITEM;
+ }
+ else if (EQ (cmd, Qtty_menu_next_item))
+ {
+ if (*y < max_y)
+ *y += 1;
+ else
+ st = MI_SCROLL_FORWARD;
+ }
+ else if (EQ (cmd, Qtty_menu_prev_item))
+ {
+ if (*y > min_y)
+ *y -= 1;
+ else
+ st = MI_SCROLL_BACK;
+ }
+ else if (EQ (cmd, Qtty_menu_select))
+ st = MI_ITEM_SELECTED;
+ else if (!EQ (cmd, Qtty_menu_ignore))
+ usable_input = 0;
+ if (usable_input)
+ sf->mouse_moved = 1;
+ return st;
+ }
+ return MI_CONTINUE;
+}
+
+/* Display menu, wait for user's response, and return that response. */
+static int
+tty_menu_activate (tty_menu *menu, int *pane, int *selidx,
+ int x0, int y0, char **txt,
+ void (*help_callback)(char const *, int, int),
+ bool kbd_navigation)
+{
+ struct tty_menu_state *state;
+ int statecount, x, y, i;
+ bool leave, onepane;
+ int result IF_LINT (= 0);
+ int title_faces[4]; /* face to display the menu title */
+ int faces[4], buffers_num_deleted = 0;
+ struct frame *sf = SELECTED_FRAME ();
+ struct tty_display_info *tty = FRAME_TTY (sf);
+ bool first_time;
+ Lisp_Object selectface;
+ int first_item = 0;
+
+ /* Don't allow non-positive x0 and y0, lest the menu will wrap
+ around the display. */
+ if (x0 <= 0)
+ x0 = 1;
+ if (y0 <= 0)
+ y0 = 1;
+
+ state = alloca (menu->panecount * sizeof (struct tty_menu_state));
+ memset (state, 0, sizeof (*state));
+ faces[0]
+ = lookup_derived_face (sf, intern ("tty-menu-disabled-face"),
+ DEFAULT_FACE_ID, 1);
+ faces[1]
+ = lookup_derived_face (sf, intern ("tty-menu-enabled-face"),
+ DEFAULT_FACE_ID, 1);
+ selectface = intern ("tty-menu-selected-face");
+ faces[2] = lookup_derived_face (sf, selectface,
+ faces[0], 1);
+ faces[3] = lookup_derived_face (sf, selectface,
+ faces[1], 1);
+
+ /* Make sure the menu title is always displayed with
+ `tty-menu-selected-face', no matter where the mouse pointer is. */
+ for (i = 0; i < 4; i++)
+ title_faces[i] = faces[3];
+
+ statecount = 1;
+
+ /* Don't let the title for the "Buffers" popup menu include a
+ digit (which is ugly).
+
+ This is a terrible kludge, but I think the "Buffers" case is
+ the only one where the title includes a number, so it doesn't
+ seem to be necessary to make this more general. */
+ if (strncmp (menu->text[0], "Buffers 1", 9) == 0)
+ {
+ menu->text[0][7] = '\0';
+ buffers_num_deleted = 1;
+ }
+
+ /* Force update of the current frame, so that the desired and the
+ current matrices are identical. */
+ update_frame_with_menu (sf);
+ state[0].menu = menu;
+ state[0].screen_behind = save_and_enable_current_matrix (sf);
+
+ /* Display the menu title. We subtract 1 from x0 and y0 because we
+ want to interpret them as zero-based column and row coordinates,
+ and also because we want the first item of the menu, not its
+ title, to appear at x0,y0. */
+ tty_menu_display (menu, x0 - 1, y0 - 1, 1, title_faces, x0 - 1, y0 - 1, 0, 0);
+
+ /* Turn off the cursor. Otherwise it shows through the menu
+ panes, which is ugly. */
+ tty_hide_cursor (tty);
+ if (buffers_num_deleted)
+ menu->text[0][7] = ' ';
+ onepane = menu->count == 1 && menu->submenu[0];
+ if (onepane)
+ {
+ menu->width = menu->submenu[0]->width;
+ state[0].menu = menu->submenu[0];
+ }
+ else
+ {
+ state[0].menu = menu;
+ }
+ state[0].x = x0 - 1;
+ state[0].y = y0;
+ state[0].pane = onepane;
+
+ x = state[0].x;
+ y = state[0].y;
+ first_time = true;
+
+ leave = 0;
+ while (!leave)
+ {
+ mi_result input_status;
+ int min_y = state[0].y;
+ int max_y = min (min_y + state[0].menu->count, FRAME_LINES (sf) - 1) - 1;
+
+ input_status = read_menu_input (sf, &x, &y, min_y, max_y, &first_time);
+ if (input_status)
+ {
+ leave = 1;
+ switch (input_status)
+ {
+ case MI_QUIT_MENU:
+ /* Remove the last help-echo, so that it doesn't
+ re-appear after "Quit". */
+ show_help_echo (Qnil, Qnil, Qnil, Qnil);
+ result = TTYM_NO_SELECT;
+ break;
+ case MI_NEXT_ITEM:
+ if (kbd_navigation)
+ result = TTYM_NEXT;
+ else
+ leave = 0;
+ break;
+ case MI_PREV_ITEM:
+ if (kbd_navigation)
+ result = TTYM_PREV;
+ else
+ leave = 0;
+ break;
+ case MI_SCROLL_FORWARD:
+ if (y - min_y == state[0].menu->count - 1 - first_item)
+ {
+ y = min_y;
+ first_item = 0;
+ }
+ else
+ first_item++;
+ leave = 0;
+ break;
+ case MI_SCROLL_BACK:
+ if (first_item == 0)
+ {
+ y = max_y;
+ first_item = state[0].menu->count - 1 - (y - min_y);
+ }
+ else
+ first_item--;
+ leave = 0;
+ break;
+ default:
+ /* MI_ITEM_SELECTED is handled below, so nothing to do. */
+ break;
+ }
+ }
+ if (sf->mouse_moved && input_status != MI_QUIT_MENU)
+ {
+ sf->mouse_moved = 0;
+ result = TTYM_IA_SELECT;
+ for (i = 0; i < statecount; i++)
+ if (state[i].x <= x && x < state[i].x + state[i].menu->width + 2)
+ {
+ int dy = y - state[i].y + first_item;
+ if (0 <= dy && dy < state[i].menu->count)
+ {
+ if (!state[i].menu->submenu[dy])
+ {
+ if (state[i].menu->panenumber[dy])
+ result = TTYM_SUCCESS;
+ else
+ result = TTYM_IA_SELECT;
+ }
+ *pane = state[i].pane - 1;
+ *selidx = dy;
+ /* We hit some part of a menu, so drop extra menus that
+ have been opened. That does not include an open and
+ active submenu. */
+ if (i != statecount - 2
+ || state[i].menu->submenu[dy] != state[i+1].menu)
+ while (i != statecount - 1)
+ {
+ statecount--;
+ screen_update (sf, state[statecount].screen_behind);
+ state[statecount].screen_behind = NULL;
+ }
+ if (i == statecount - 1 && state[i].menu->submenu[dy])
+ {
+ tty_menu_display (state[i].menu,
+ state[i].x,
+ state[i].y,
+ state[i].pane,
+ faces, x, y, first_item, 1);
+ state[statecount].menu = state[i].menu->submenu[dy];
+ state[statecount].pane = state[i].menu->panenumber[dy];
+ state[statecount].screen_behind
+ = save_and_enable_current_matrix (sf);
+ state[statecount].x
+ = state[i].x + state[i].menu->width + 2;
+ state[statecount].y = y;
+ statecount++;
+ }
+ }
+ }
+ tty_menu_display (state[statecount - 1].menu,
+ state[statecount - 1].x,
+ state[statecount - 1].y,
+ state[statecount - 1].pane,
+ faces, x, y, first_item, 1);
+ tty_hide_cursor (tty);
+ fflush (tty->output);
+ }
+
+ /* Display the help-echo message for the currently-selected menu
+ item. */
+ if ((menu_help_message || prev_menu_help_message)
+ && menu_help_message != prev_menu_help_message)
+ {
+ help_callback (menu_help_message,
+ menu_help_paneno, menu_help_itemno);
+ tty_hide_cursor (tty);
+ fflush (tty->output);
+ prev_menu_help_message = menu_help_message;
+ }
+ }
+
+ sf->mouse_moved = 0;
+ screen_update (sf, state[0].screen_behind);
+ while (statecount--)
+ free_saved_screen (state[statecount].screen_behind);
+ tty_show_cursor (tty); /* turn cursor back on */
+ fflush (tty->output);
+
+/* Clean up any mouse events that are waiting inside Emacs event queue.
+ These events are likely to be generated before the menu was even
+ displayed, probably because the user pressed and released the button
+ (which invoked the menu) too quickly. If we don't remove these events,
+ Emacs will process them after we return and surprise the user. */
+ discard_mouse_events ();
+ if (!kbd_buffer_events_waiting ())
+ clear_input_pending ();
+ return result;
+}
+
+/* Dispose of a menu. */
+
+static void
+tty_menu_destroy (tty_menu *menu)
+{
+ int i;
+ if (menu->allocated)
+ {
+ for (i = 0; i < menu->count; i++)
+ if (menu->submenu[i])
+ tty_menu_destroy (menu->submenu[i]);
+ xfree (menu->text);
+ xfree (menu->submenu);
+ xfree (menu->panenumber);
+ xfree (menu->help_text);
+ }
+ xfree (menu);
+ menu_help_message = prev_menu_help_message = NULL;
+}
+
+/* Show help HELP_STRING, or clear help if HELP_STRING is null.
+
+ PANE is the pane number, and ITEM is the menu item number in
+ the menu (currently not used). */
+
+static void
+tty_menu_help_callback (char const *help_string, int pane, int item)
+{
+ Lisp_Object *first_item;
+ Lisp_Object pane_name;
+ Lisp_Object menu_object;
+
+ first_item = XVECTOR (menu_items)->contents;
+ if (EQ (first_item[0], Qt))
+ pane_name = first_item[MENU_ITEMS_PANE_NAME];
+ else if (EQ (first_item[0], Qquote))
+ /* This shouldn't happen, see xmenu_show. */
+ pane_name = empty_unibyte_string;
+ else
+ pane_name = first_item[MENU_ITEMS_ITEM_NAME];
+
+ /* (menu-item MENU-NAME PANE-NUMBER) */
+ menu_object = list3 (Qmenu_item, pane_name, make_number (pane));
+ show_help_echo (help_string ? build_string (help_string) : Qnil,
+ Qnil, menu_object, make_number (item));
+}
+
+static void
+tty_pop_down_menu (Lisp_Object arg)
+{
+ tty_menu *menu = XSAVE_POINTER (arg, 0);
+
+ block_input ();
+ tty_menu_destroy (menu);
+ unblock_input ();
+}
+
+/* Return the zero-based index of the last menu-bar item on frame F. */
+static int
+tty_menu_last_menubar_item (struct frame *f)
+{
+ int i = 0;
+
+ eassert (FRAME_TERMCAP_P (f) && FRAME_LIVE_P (f));
+ if (FRAME_TERMCAP_P (f) && FRAME_LIVE_P (f))
+ {
+ Lisp_Object items = FRAME_MENU_BAR_ITEMS (f);
+
+ while (i < ASIZE (items))
+ {
+ Lisp_Object str;
+
+ str = AREF (items, i + 1);
+ if (NILP (str))
+ break;
+ i += 4;
+ }
+ i -= 4; /* went one too far */
+ }
+ return i;
+}
+
+/* Find in frame F's menu bar the menu item that is next or previous
+ to the item at X/Y, and return that item's position in X/Y. WHICH
+ says which one--next or previous--item to look for. X and Y are
+ measured in character cells. This should only be called on TTY
+ frames. */
+static void
+tty_menu_new_item_coords (struct frame *f, int which, int *x, int *y)
+{
+ eassert (FRAME_TERMCAP_P (f) && FRAME_LIVE_P (f));
+ if (FRAME_TERMCAP_P (f) && FRAME_LIVE_P (f))
+ {
+ Lisp_Object items = FRAME_MENU_BAR_ITEMS (f);
+ int last_i = tty_menu_last_menubar_item (f);
+ int i, prev_x;
+
+ /* This loop assumes a single menu-bar line, and will fail to
+ find an item if it is not in the first line. Note that
+ make_lispy_event in keyboard.c makes the same assumption. */
+ for (i = 0, prev_x = -1; i < ASIZE (items); i += 4)
+ {
+ Lisp_Object pos, str;
+ int ix;
+
+ str = AREF (items, i + 1);
+ pos = AREF (items, i + 3);
+ if (NILP (str))
+ return;
+ ix = XINT (pos);
+ if (ix <= *x
+ /* We use <= so the blank between 2 items on a TTY is
+ considered part of the previous item. */
+ && *x <= ix + menu_item_width (SDATA (str)))
+ {
+ /* Found current item. Now compute the X coordinate of
+ the previous or next item. */
+ if (which == TTYM_NEXT)
+ {
+ if (i < last_i)
+ *x = XINT (AREF (items, i + 4 + 3));
+ else
+ *x = 0; /* wrap around to the first item */
+ }
+ else if (prev_x < 0)
+ {
+ /* Wrap around to the last item. */
+ *x = XINT (AREF (items, last_i + 3));
+ }
+ else
+ *x = prev_x;
+ return;
+ }
+ prev_x = ix;
+ }
+ }
+}
+
+Lisp_Object
+tty_menu_show (struct frame *f, int x, int y, bool for_click, bool keymaps,
+ Lisp_Object title, bool kbd_navigation, const char **error_name)
+{
+ tty_menu *menu;
+ int pane, selidx, lpane, status;
+ Lisp_Object entry, pane_prefix;
+ char *datap;
+ int ulx, uly, width, height;
+ int item_x, item_y;
+ int dispwidth, dispheight;
+ int i, j, lines, maxlines;
+ int maxwidth;
+ ptrdiff_t specpdl_count;
+
+ eassert (FRAME_TERMCAP_P (f));
+
+ *error_name = 0;
+ if (menu_items_n_panes == 0)
+ return Qnil;
+
+ if (menu_items_used <= MENU_ITEMS_PANE_LENGTH)
+ {
+ *error_name = "Empty menu";
+ return Qnil;
+ }
+
+ /* Make the menu on that window. */
+ menu = tty_menu_create ();
+ if (menu == NULL)
+ {
+ *error_name = "Can't create menu";
+ return Qnil;
+ }
+
+ /* Don't GC while we prepare and show the menu, because we give the
+ menu functions pointers to the contents of strings. */
+ specpdl_count = inhibit_garbage_collection ();
+
+ /* Adjust coordinates to be root-window-relative. */
+ item_x = x += f->left_pos;
+ item_y = y += f->top_pos;
+
+ /* Create all the necessary panes and their items. */
+ maxwidth = maxlines = lines = i = 0;
+ lpane = TTYM_FAILURE;
+ while (i < menu_items_used)
+ {
+ if (EQ (AREF (menu_items, i), Qt))
+ {
+ /* Create a new pane. */
+ Lisp_Object pane_name, prefix;
+ const char *pane_string;
+
+ maxlines = max (maxlines, lines);
+ lines = 0;
+ pane_name = AREF (menu_items, i + MENU_ITEMS_PANE_NAME);
+ prefix = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+ pane_string = (NILP (pane_name)
+ ? "" : SSDATA (pane_name));
+ if (keymaps && !NILP (prefix))
+ pane_string++;
+
+ lpane = tty_menu_add_pane (menu, pane_string);
+ if (lpane == TTYM_FAILURE)
+ {
+ tty_menu_destroy (menu);
+ *error_name = "Can't create pane";
+ entry = Qnil;
+ goto tty_menu_end;
+ }
+ i += MENU_ITEMS_PANE_LENGTH;
+
+ /* Find the width of the widest item in this pane. */
+ j = i;
+ while (j < menu_items_used)
+ {
+ Lisp_Object item;
+ item = AREF (menu_items, j);
+ if (EQ (item, Qt))
+ break;
+ if (NILP (item))
+ {
+ j++;
+ continue;
+ }
+ width = SBYTES (item);
+ if (width > maxwidth)
+ maxwidth = width;
+
+ j += MENU_ITEMS_ITEM_LENGTH;
+ }
+ }
+ /* Ignore a nil in the item list.
+ It's meaningful only for dialog boxes. */
+ else if (EQ (AREF (menu_items, i), Qquote))
+ i += 1;
+ else
+ {
+ /* Create a new item within current pane. */
+ Lisp_Object item_name, enable, descrip, help;
+ char *item_data;
+ char const *help_string;
+
+ 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);
+ help = AREF (menu_items, i + MENU_ITEMS_ITEM_HELP);
+ help_string = STRINGP (help) ? SSDATA (help) : NULL;
+
+ if (!NILP (descrip))
+ {
+ /* if alloca is fast, use that to make the space,
+ to reduce gc needs. */
+ item_data = (char *) alloca (maxwidth + SBYTES (descrip) + 1);
+ memcpy (item_data, SSDATA (item_name), SBYTES (item_name));
+ for (j = SCHARS (item_name); j < maxwidth; j++)
+ item_data[j] = ' ';
+ memcpy (item_data + j, SSDATA (descrip), SBYTES (descrip));
+ item_data[j + SBYTES (descrip)] = 0;
+ }
+ else
+ item_data = SSDATA (item_name);
+
+ if (lpane == TTYM_FAILURE
+ || (! tty_menu_add_selection (menu, lpane, item_data,
+ !NILP (enable), help_string)))
+ {
+ tty_menu_destroy (menu);
+ *error_name = "Can't add selection to menu";
+ entry = Qnil;
+ goto tty_menu_end;
+ }
+ i += MENU_ITEMS_ITEM_LENGTH;
+ lines++;
+ }
+ }
+
+ maxlines = max (maxlines, lines);
+
+ /* All set and ready to fly. */
+ dispwidth = f->text_cols;
+ dispheight = f->text_lines;
+ x = min (x, dispwidth);
+ y = min (y, dispheight);
+ x = max (x, 1);
+ y = max (y, 1);
+ tty_menu_locate (menu, x, y, &ulx, &uly, &width, &height);
+ if (ulx + width > dispwidth)
+ {
+ x -= (ulx + width) - dispwidth;
+ ulx = dispwidth - width;
+ }
+ if (uly + height > dispheight)
+ {
+ y -= (uly + height) - dispheight;
+ uly = dispheight - height;
+ }
+
+ if (FRAME_HAS_MINIBUF_P (f) && uly+height > dispheight - 2)
+ {
+ /* Move the menu away of the echo area, to avoid overwriting the
+ menu with help echo messages or vice versa. */
+ if (BUFFERP (echo_area_buffer[0]) && WINDOWP (echo_area_window))
+ {
+ y -= WINDOW_TOTAL_LINES (XWINDOW (echo_area_window)) + 1;
+ uly -= WINDOW_TOTAL_LINES (XWINDOW (echo_area_window)) + 1;
+ }
+ else
+ {
+ y -= 2;
+ uly -= 2;
+ }
+ }
+
+ if (ulx < 0) x -= ulx;
+ if (uly < 0) y -= uly;
+
+#if 0
+ /* This code doesn't make sense on a TTY, since it can easily annul
+ the adjustments above that carefully avoid truncation of the menu
+ items. I think it was written to fix some problem that only
+ happens on X11. */
+ if (! for_click)
+ {
+ /* If position was not given by a mouse click, adjust so upper left
+ corner of the menu as a whole ends up at given coordinates. This
+ is what x-popup-menu says in its documentation. */
+ x += width/2;
+ y += 1.5*height/(maxlines+2);
+ }
+#endif
+
+ pane = selidx = 0;
+
+ record_unwind_protect (tty_pop_down_menu, make_save_ptr (menu));
+
+ specbind (Qoverriding_terminal_local_map,
+ Fsymbol_value (Qtty_menu_navigation_map));
+ status = tty_menu_activate (menu, &pane, &selidx, x, y, &datap,
+ tty_menu_help_callback, kbd_navigation);
+ entry = pane_prefix = Qnil;
+
+ switch (status)
+ {
+ case TTYM_SUCCESS:
+ /* Find the item number SELIDX in pane number PANE. */
+ i = 0;
+ while (i < menu_items_used)
+ {
+ if (EQ (AREF (menu_items, i), Qt))
+ {
+ if (pane == 0)
+ pane_prefix
+ = AREF (menu_items, i + MENU_ITEMS_PANE_PREFIX);
+ pane--;
+ i += MENU_ITEMS_PANE_LENGTH;
+ }
+ else
+ {
+ if (pane == -1)
+ {
+ if (selidx == 0)
+ {
+ entry
+ = AREF (menu_items, i + MENU_ITEMS_ITEM_VALUE);
+ if (keymaps != 0)
+ {
+ entry = Fcons (entry, Qnil);
+ if (!NILP (pane_prefix))
+ entry = Fcons (pane_prefix, entry);
+ }
+ break;
+ }
+ selidx--;
+ }
+ i += MENU_ITEMS_ITEM_LENGTH;
+ }
+ }
+ break;
+
+ case TTYM_NEXT:
+ case TTYM_PREV:
+ tty_menu_new_item_coords (f, status, &item_x, &item_y);
+ entry = Fcons (make_number (item_x), make_number (item_y));
+ break;
+
+ case TTYM_FAILURE:
+ *error_name = "Can't activate menu";
+ case TTYM_IA_SELECT:
+ break;
+ case TTYM_NO_SELECT:
+ /* Make "Cancel" equivalent to C-g unless FOR_CLICK (which means
+ the menu was invoked with a mouse event as POSITION). */
+ if (! for_click)
+ Fsignal (Qquit, Qnil);
+ break;
+ }
+
+ tty_menu_end:
+
+ unbind_to (specpdl_count, Qnil);
+ return entry;
+}
+
+#endif /* !MSDOS */
+