ftp.nice.ch/pub/next/unix/editor/vim-5.0f.s.tar.gz#/vim-5.0f/src/gui.c

This is gui.c in view mode; [Download] [Up]

/* vi:set ts=4 sw=4:
 *
 * VIM - Vi IMproved			by Bram Moolenaar
 *								GUI/Motif support by Robert Webb
 *
 * Do ":help uganda"  in Vim to read copying and usage conditions.
 * Do ":help credits" in Vim to see a list of people who contributed.
 */

#include "vim.h"
#include "globals.h"
#include "proto.h"
#include "option.h"

/* Structure containing all the GUI information */
Gui gui;

/* Set to TRUE after adding/removing menus to ensure they are updated */
int force_menu_update = FALSE;

static void gui_check_screen __ARGS((void));
static void gui_position_components __ARGS((int, int));
static void gui_outstr __ARGS((char_u *, int));
static void gui_outstr_nowrap __ARGS((char_u *, int, int));
static int gui_get_menu_cmd_modes __ARGS((char_u *, int, int *, int *));
static void  gui_update_menus_recurse __ARGS((GuiMenu *, int));
static int gui_add_menu_path __ARGS((char_u *, int, void (*)(), char_u *, int));
static int gui_remove_menu __ARGS((GuiMenu **, char_u *, int));
static void gui_free_menu __ARGS((GuiMenu *));
static void gui_free_menu_string __ARGS((GuiMenu *, int));
static int gui_show_menus __ARGS((char_u *, int));
static void gui_show_menus_recursive __ARGS((GuiMenu *, int, int));
static char_u *gui_menu_name_skip __ARGS((char_u *name));
static void gui_create_initial_menus __ARGS((GuiMenu *, GuiMenu *));
static void gui_update_scrollbars __ARGS((int));
static void gui_update_horiz_scrollbar __ARGS((int));

/*
 * The Athena scrollbars can move the thumb to after the end of the scrollbar,
 * this makes the thumb indicate the part of the text that is shown.  Motif
 * can't do this.
 */
#ifdef USE_GUI_ATHENA
# define SCROLL_PAST_END
#endif

/*
 * gui_start -- Called when user wants to start the GUI.
 */
	void
gui_start()
{
	char_u	*old_term;
#ifdef UNIX
	pid_t	pid;
#endif

	old_term = vim_strsave(term_strings[KS_NAME]);
	mch_setmouse(FALSE);					/* first switch mouse off */

	/*
	 * Set_termname() will call gui_init() to start the GUI.
	 * Set the "starting" flag, to indicate that the GUI will start.
	 *
	 * We don't want to open the GUI window until after we've read .gvimrc,
	 * otherwise we don't know what font we will use, and hence we don't know
	 * what size the window should be.  So if there are errors in the .gvimrc
	 * file, they will have to go to the terminal: Set full_screen to FALSE.
	 */
	settmode(TMODE_COOK);						/* stop RAW mode */
	gui.starting = TRUE;
	full_screen = FALSE;
	termcapinit((char_u *)"builtin_gui");
	gui.starting = FALSE;

	if (!gui.in_use)						/* failed to start GUI */
	{
		termcapinit(old_term);				/* back to old term settings */
		full_screen = TRUE;
		settmode(TMODE_RAW);				/* restart RAW mode */
	}
	else
		highlight_gui_started();			/* re-init colors and fonts */
	full_screen = TRUE;

	vim_free(old_term);

#ifdef UNIX
	/*
	 * Quit the current process and continue in the child.
	 * Makes "gvim file" disconnect from the shell it was started in.
	 * Don't do this when Vim was started with "-f" or the 'f' flag is present
	 * in 'guioptions'.
	 */
	if (gui.in_use && gui.dofork && vim_strchr(p_guioptions, GO_FORG) == NULL)
	{
		pid = fork();
		if (pid > 0)		/* Parent */
			exit(0);
#if defined(HAVE_SETSID) || defined(HAVE_SETPGID)
		/*
		 * Change our process group.  On some systems/shells a CTRL-C in the
		 * shell where Vim was started would otherwise kill gvim!
		 */
		if (pid == 0)		/* child */
# if defined(HAVE_SETSID)
			(void)setsid();
# else
			(void)setpgid(0, 0);
# endif
#endif
	}
#endif
}

/*
 * Call this when vim starts up, whether or not the GUI is started
 */
	void
gui_prepare(argc, argv)
	int		*argc;
	char	**argv;
{
	/* Menu items may be added before the GUI is started, so set this now */
	gui.root_menu = NULL;
	gui.in_use = FALSE;				/* No GUI yet (maybe later) */
	gui.starting = FALSE;			/* No GUI yet (maybe later) */
	gui.dofork = TRUE;				/* default is to use fork() */
	gui_mch_prepare(argc, argv);
}

/*
 * This is the call which starts the GUI.
 */
	void
gui_init()
{
	char_u	*env_str;
	WIN		*wp;
	int		i;

	gui.window_created = FALSE;
	gui.dying = FALSE;
	gui.in_focus = FALSE;
	gui.dragged_sb = SBAR_NONE;
	gui.dragged_wp = NULL;
	gui.col = gui.num_cols = 0;
	gui.row = gui.num_rows = 0;

	/* Initialise gui.cursor_row: */
	INVALIDATE_CURSOR();
	gui.scroll_region_top = 0;
	gui.scroll_region_bot = Rows - 1;
	gui.highlight_mask = HL_NORMAL;
	gui.char_width = 1;
	gui.char_height = 1;
	gui.char_ascent = 0;
	gui.border_width = 0;

	gui.norm_font = (GuiFont)NULL;
	gui.bold_font = (GuiFont)NULL;
	gui.ital_font = (GuiFont)NULL;
	gui.boldital_font = (GuiFont)NULL;

	clip_init(TRUE);

	gui.menu_is_active = TRUE;		/* default: include menu */

	gui.scrollbar_width = gui.scrollbar_height = SB_DEFAULT_WIDTH;
	gui.menu_height = MENU_DEFAULT_HEIGHT;
	gui.menu_width = 0;

	gui.prev_wrap = -1;

	/*
	 * Set up system-wide default menus.
	 */
#ifdef SYS_MENU_FILE
	do_source((char_u *)SYS_MENU_FILE, FALSE);
#endif

	/*
	 * Switch on the mouse by default.
	 * This can then be changed in the .gvimrc.
	 */
	set_string_option((char_u *)"mouse", -1, (char_u *)"a", TRUE);

	/*
	 * Get system wide defaults for gvim, only when filename defined.
	 */
#ifdef SYS_GVIMRC_FILE
	do_source((char_u *)SYS_GVIMRC_FILE, FALSE);
#endif

	/*
	 * Try to read GUI initialization commands from the following places:
	 * - environment variable GVIMINIT
	 * - the user gvimrc file (~/.gvimrc for Unix)
	 * The first that exists is used, the rest is ignored.
	 */
	if ((env_str = vim_getenv((char_u *)"GVIMINIT")) != NULL && *env_str != NUL)
	{
		sourcing_name = (char_u *)"GVIMINIT";
		do_cmdline(env_str, NULL, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE);
		sourcing_name = NULL;
	}
	else
		do_source((char_u *)USR_GVIMRC_FILE, FALSE);

	/*
	 * Read initialization commands from ".gvimrc" in current directory.  This
	 * is only done if the 'exrc' option is set.  Because of security reasons
	 * we disallow shell and write commands now, except for unix if the file is
	 * owned by the user or 'secure' option has been reset in environment of
	 * global ".gvimrc".  Only do this if GVIMRC_FILE is not the same as
	 * USR_GVIMRC_FILE or SYS_GVIMRC_FILE.
	 */
	if (p_exrc)
	{
#ifdef UNIX
		{
			struct stat s;

			/* if ".gvimrc" file is not owned by user, set 'secure' mode */
			if (stat(GVIMRC_FILE, &s) || s.st_uid != getuid())
				secure = p_secure;
		}
#else
		secure = p_secure;
#endif

		i = FAIL;
		if (	   fullpathcmp((char_u *)USR_GVIMRC_FILE,
											(char_u *)GVIMRC_FILE) != FPC_SAME
#ifdef SYS_GVIMRC_FILE
				&& fullpathcmp((char_u *)SYS_GVIMRC_FILE,
											(char_u *)GVIMRC_FILE) != FPC_SAME
#endif
				)
			i = do_source((char_u *)GVIMRC_FILE, FALSE);

		if (secure == 2)
			need_wait_return = TRUE;
		secure = 0;
	}

	/*
	 * Create the GUI windows ready for opening.
	 */
	gui.in_use = TRUE;			/* Must be set after menus have been set up */
	if (gui_mch_init() == FAIL)
	{
		gui.in_use = FALSE;
		return;
	}

	/*
	 * Check validity of any generic resources that may have been loaded.
	 */
	if (gui.border_width < 0)
		gui.border_width = 0;

	/*
	 * Set up the fonts.
	 */
	if (gui_init_font() == FAIL)
	{
		gui.in_use = FALSE;
		return;
	}

	gui.num_cols = Columns;
	gui.num_rows = Rows;
	gui_reset_scroll_region();

	/* Create initial scrollbars */
	for (wp = firstwin; wp; wp = wp->w_next)
	{
		gui_create_scrollbar(&wp->w_scrollbars[SBAR_LEFT], wp);
		gui_create_scrollbar(&wp->w_scrollbars[SBAR_RIGHT], wp);
	}
	gui_create_scrollbar(&gui.bottom_sbar, NULL);

	gui_create_initial_menus(gui.root_menu, NULL);

	/* Configure the desired menu and scrollbars */
	gui_init_which_components(NULL);

	/* All components of the window have been created now */
	gui.window_created = TRUE;

	gui_set_winsize(TRUE);

	/*
	 * Actually open the GUI window.
	 */
	if (gui_mch_open() == FAIL)
	{
		gui.in_use = FALSE;
		return;
	}

	maketitle();
}

	void
gui_exit()
{
	gui.in_use = FALSE;
	gui_mch_exit();
}

/*
 * Set the font. Uses the 'font' option. The first font name that works is
 * used. If none is found, use the default font.
 */
	int
gui_init_font()
{
#define FONTLEN 100
	char_u	*font_list;
	char_u	font_name[FONTLEN];
	int		ret = FAIL;

	if (!gui.in_use)
		return FAIL;

	for (font_list = p_guifont; *font_list != NUL; )
	{
		/* Isolate one font name */
		(void)copy_option_part(&font_list, font_name, FONTLEN, ",");
		if (gui_mch_init_font(font_name) == OK)
		{
			ret = OK;
			break;
		}
	}

	if (ret != OK)
	{
		/*
		 * Couldn't load any font in 'font', tell gui_mch_init_font() to try
		 * to find a font we can load.
		 */
		ret = gui_mch_init_font(NULL);
	}

	if (ret == OK)
	{
		/* Set normal font as current font */
		gui_mch_set_font(gui.norm_font);

		gui_set_winsize(FALSE);
	}

	return ret;
}

	void
gui_set_cursor(row, col)
	int		row;
	int		col;
{
	gui.row = row;
	gui.col = col;
}

/*
 * gui_check_screen - check if the cursor is on the screen.
 */
	static void
gui_check_screen()
{
	if (gui.row >= Rows)
		gui.row = Rows - 1;
	if (gui.col >= Columns)
		gui.col = Columns - 1;
	if (gui.cursor_row >= Rows || gui.cursor_col >= Columns)
		INVALIDATE_CURSOR();
}

	void
gui_update_cursor(force)
	int		force;			/* when TRUE, update even when not moved */
{
	int		cur_width = 0;
	int		cur_height = 0;
	long_u	old_hl_mask;

	gui_check_screen();
	if (gui.row != gui.cursor_row || gui.col != gui.cursor_col || force)
	{
		gui_undraw_cursor();
		gui.cursor_row = gui.row;
		gui.cursor_col = gui.col;

		/* Only write to the screen after LinePointers[] has been initialized */
		if (screen_cleared && NextScreen != NULL)
		{
			/* Clear the selection if we are about to write over it */
			if (clipboard.state == SELECT_DONE
					&& gui.row >= clipboard.start.lnum
					&& gui.row <= clipboard.end.lnum)
				clip_clear_selection();

			/*
			 * In Insert mode: Draw a vertical bar at the left of the cursor
			 * char.  In Overstrike mode: Draw a horizontal bar at the bottom.
			 * Otherwise: Draw a box, filled when our window is in focus.
			 */
			if (gui.row < screen_Rows && gui.col < screen_Columns)
			{
				if (gui.in_focus)
				{
					if (State == INSERT)
						cur_width = 3;
					else if (State == REPLACE)
						cur_height = 3;
					else if (State == CMDLINE && !cmdline_at_end())
					{
						if (cmdline_overstrike())
							cur_height = 3;
						else
							cur_width = 3;
					}
				}
				if (!gui.in_focus)
				{
					gui_mch_draw_hollow_cursor();
				}
				else if (cur_height || cur_width)
				{
					if (cur_height == 0)
						cur_height = gui.char_height;
					if (cur_width == 0)
						cur_width = gui.char_width;
					gui_mch_draw_part_cursor(cur_width, cur_height);
				}
				else
				{
					old_hl_mask = gui.highlight_mask;
					gui.highlight_mask =
							*(LinePointers[gui.row] + gui.col + Columns);
					gui_outstr_nowrap(LinePointers[gui.row] + gui.col, 1,
							GUI_MON_IS_CURSOR);
					gui.highlight_mask = old_hl_mask;
				}
			}
		}
	}
}

	void
gui_position_menu()
{
	if (gui.menu_is_active && gui.in_use)
		gui_mch_set_menu_pos(0, 0, gui.menu_width, gui.menu_height);
}

/*
 * Position the various GUI components (text area, menu).  The vertical
 * scrollbars are NOT handled here.  See gui_update_scrollbars().
 */
	static void
gui_position_components(total_width, total_height)
	int		total_width;
	int		total_height;
{
	int		text_area_x;
	int		text_area_y;
	int		text_area_width;
	int		text_area_height;

	gui.menu_width = total_width;

	text_area_x = 0;
	if (gui.which_scrollbars[SBAR_LEFT])
		text_area_x += gui.scrollbar_width;
	text_area_y = 0;
	if (gui.menu_is_active)
		text_area_y = gui.menu_height;
	text_area_width = gui.num_cols * gui.char_width + gui.border_offset * 2;
	text_area_height = gui.num_rows * gui.char_height + gui.border_offset * 2;

	gui_mch_set_text_area_pos(text_area_x,
							  text_area_y,
							  text_area_width,
							  text_area_height);
	gui_position_menu();
	if (gui.which_scrollbars[SBAR_BOTTOM])
		gui_mch_set_scrollbar_pos(&gui.bottom_sbar,
								  text_area_x,
								  text_area_y + text_area_height,
								  text_area_width,
								  gui.scrollbar_height);
	gui.left_sbar_x = 0;
	gui.right_sbar_x = text_area_x + text_area_width;
}

	int
gui_get_base_width()
{
	int		base_width;

	base_width = 2 * gui.border_offset;
	if (gui.which_scrollbars[SBAR_LEFT])
		base_width += gui.scrollbar_width;
	if (gui.which_scrollbars[SBAR_RIGHT])
		base_width += gui.scrollbar_width;
	return base_width;
}

	int
gui_get_base_height()
{
	int		base_height;

	base_height = 2 * gui.border_offset;
	if (gui.which_scrollbars[SBAR_BOTTOM])
		base_height += gui.scrollbar_height;
	if (gui.menu_is_active)
		base_height += gui.menu_height;
	return base_height;
}

/*
 * Should be called after the GUI window has been resized.  Its arguments are
 * the new width and height of the window in pixels.
 */
	void
gui_resize_window(pixel_width, pixel_height)
	int		pixel_width;
	int		pixel_height;
{
	if (!gui.window_created)
		return;

	gui.num_cols = (pixel_width - gui_get_base_width()) / gui.char_width;
	gui.num_rows = (pixel_height - gui_get_base_height()) / gui.char_height;

	gui_position_components(pixel_width, pixel_height);

	gui_reset_scroll_region();
	/*
	 * At the "more" prompt there is no redraw, put the cursor at the last
	 * line here (why does it have to be one row too low?).
	 */
	if (State == ASKMORE)
		gui.row = gui.num_rows;

	if (gui.num_rows != screen_Rows || gui.num_cols != screen_Columns)
		set_winsize(0, 0, FALSE);
	gui_update_scrollbars(TRUE);
	gui_update_cursor(FALSE);
}

	int
gui_get_winsize()
{
	Rows = gui.num_rows;
	Columns = gui.num_cols;
	return OK;
}

/*
 * Set the size of the window according to Rows and Columns.
 */
	void
gui_set_winsize(fit_to_display)
	int		fit_to_display;
{
	int		base_width;
	int		base_height;
	int		width;
	int		height;
	int		min_width;
	int		min_height;
	int		screen_w;
	int		screen_h;

	if (!gui.window_created)
		return;

	base_width = gui_get_base_width();
	base_height = gui_get_base_height();
	width = Columns * gui.char_width + base_width;
	height = Rows * gui.char_height + base_height;

	if (fit_to_display)
	{
		gui_mch_get_screen_dimensions(&screen_w, &screen_h);
		if (width > screen_w)
		{
			Columns = (screen_w - base_width) / gui.char_width;
			if (Columns < MIN_COLUMNS)
				Columns = MIN_COLUMNS;
			gui.num_cols = Columns;
			gui_reset_scroll_region();
			width = Columns * gui.char_width + base_width;
		}
		if (height > screen_h)
		{
			Rows = (screen_h - base_height) / gui.char_height;
			if (Rows < MIN_ROWS)
				Rows = MIN_ROWS;
			gui.num_rows = Rows;
			gui_reset_scroll_region();
			height = Rows * gui.char_height + base_height;
		}
	}

	min_width = base_width + MIN_COLUMNS * gui.char_width;
	min_height = base_height + MIN_ROWS * gui.char_height;

	gui_mch_set_winsize(width, height, min_width, min_height,
						base_width, base_height);
	gui_position_components(width, height);
	gui_update_scrollbars(TRUE);
}

/*
 * Make scroll region cover whole screen.
 */
	void
gui_reset_scroll_region()
{
	gui.scroll_region_top = 0;
	gui.scroll_region_bot = gui.num_rows - 1;
}

	void
gui_start_highlight(mask)
	long_u	mask;
{
	if (mask > HL_ALL)				/* highlight code */
		gui.highlight_mask = mask;
	else							/* mask */
		gui.highlight_mask |= mask;
}

	void
gui_stop_highlight(mask)
    long_u	mask;
{
	if (mask > HL_ALL)				/* highlight code */
		gui.highlight_mask = HL_NORMAL;
	else							/* mask */
		gui.highlight_mask &= ~mask;
}

/*
 * Clear a rectangular region of the screen from text pos (row1, col1) to
 * (row2, col2) inclusive.
 */
	void
gui_clear_block(row1, col1, row2, col2)
	int		row1;
	int		col1;
	int		row2;
	int		col2;
{
	/* Clear the selection if we are about to write over it */
	if (clipboard.state == SELECT_DONE
			&& row2 >= clipboard.start.lnum
			&& row1 <= clipboard.end.lnum)
		clip_clear_selection();

	gui_mch_clear_block(row1, col1, row2, col2);

	/* Invalidate cursor if it was in this block */
	if (gui.cursor_row >= row1 && gui.cursor_row <= row2
	 && gui.cursor_col >= col1 && gui.cursor_col <= col2)
		INVALIDATE_CURSOR();
}

	void
gui_write(s, len)
	char_u	*s;
	int		len;
{
	char_u	*p;
	int		arg1 = 0, arg2 = 0;

/* #define DEBUG_GUI_WRITE */
#ifdef DEBUG_GUI_WRITE
	{
		int i;
		char_u *str;

		printf("gui_write(%d):\n    ", len);
		for (i = 0; i < len; i++)
			if (s[i] == ESC)
			{
				if (i != 0)
					printf("\n    ");
				printf("<ESC>");
			}
			else
			{
				str = transchar(s[i]);
				if (str[0] && str[1])
					printf("<%s>", (char *)str);
				else
					printf("%s", (char *)str);
			}
		printf("\n");
	}
#endif
	while (len)
	{
		if (s[0] == '\n')				/* NL */
		{
			len--;
			s++;
			gui.col = 0;
			if (gui.row < gui.scroll_region_bot)
				gui.row++;
			else
				gui_mch_delete_lines(gui.scroll_region_top, 1);
		}
		else if (s[0] == '\r')			/* CR */
		{
			len--;
			s++;
			gui.col = 0;
		}
		else if (s[0] == '\b')			/* Backspace */
		{
			len--;
			s++;
			if (gui.col)
				--gui.col;
		}
		else if (s[0] == Ctrl('G'))		/* Beep */
		{
			gui_mch_beep();
			len--;
			s++;
		}
		else if (s[0] == ESC && s[1] == '|')
		{
			p = s + 2;
			if (isdigit(*p))
			{
				arg1 = getdigits(&p);
				if (p > s + len)
					break;
				if (*p == ';')
				{
					++p;
					arg2 = getdigits(&p);
					if (p > s + len)
						break;
				}
			}
			switch (*p)
			{
				case 'C':		/* Clear screen */
					gui_clear_block(0, 0, Rows - 1, Columns - 1);
					break;
				case 'M':		/* Move cursor */
					gui_set_cursor(arg1, arg2);
					break;
				case 'R':		/* Set scroll region */
					if (arg1 < arg2)
					{
						gui.scroll_region_top = arg1;
						gui.scroll_region_bot = arg2;
					}
					else
					{
						gui.scroll_region_top = arg2;
						gui.scroll_region_bot = arg1;
					}
					break;
				case 'd':		/* Delete line */
					gui_mch_delete_lines(gui.row, 1);
					break;
				case 'D':		/* Delete lines */
					gui_mch_delete_lines(gui.row, arg1);
					break;
				case 'i':		/* Insert line */
					gui_mch_insert_lines(gui.row, 1);
					break;
				case 'I':		/* Insert lines */
					gui_mch_insert_lines(gui.row, arg1);
					break;
				case '$':		/* Clear to end-of-line */
					gui_clear_block(gui.row, gui.col, gui.row, Columns - 1);
					break;
				case 'h':		/* Turn on highlighting */
					gui_start_highlight(arg1);
					break;
				case 'H':		/* Turn off highlighting */
					gui_stop_highlight(arg1);
					break;
				case 'f':		/* flash the window (visual bell) */
					gui_mch_flash();
					break;
				default:
					p = s + 1;	/* Skip the ESC */
					break;
			}
			len -= ++p - s;
			s = p;
		}
		else if (s[0] < 0x20)			/* Ctrl character, shouldn't happen */
		{
			/*
			 * For some reason vim sends me a ^M after hitting return on the
			 * ':' line.  Make sure we ignore this here.
			 */
			len--;		/* Skip this char */
			s++;
		}
		else
		{
			p = s;
			while (len && *p >= 0x20)
			{
				len--;
				p++;
			}
			gui_outstr(s, p - s);
			s = p;
		}
	}
	gui_update_cursor(FALSE);
	gui_update_scrollbars(FALSE);

	/*
	 * We need to make sure this is cleared since Athena doesn't tell us when
	 * he is done dragging.
	 */
	gui.dragged_sb = SBAR_NONE;

	if (vim_strchr(p_guioptions, GO_ASEL) != NULL)
		clip_update_selection();
	gui_mch_flush();				/* In case vim decides to take a nap */
}

	static void
gui_outstr(s, len)
	char_u	*s;
	int		len;
{
	int		this_len;

	if (len == 0)
		return;

	if (len < 0)
		len = STRLEN(s);

	while (gui.col + len > Columns)
	{
		this_len = Columns - gui.col;
		gui_outstr_nowrap(s, this_len, GUI_MON_WRAP_CURSOR);
		s += this_len;
		len -= this_len;
	}
	gui_outstr_nowrap(s, len, GUI_MON_WRAP_CURSOR);
}

/*
 * Output the given string at the current cursor position.	If the string is
 * too long to fit on the line, then it is truncated.  GUI_MON_WRAP_CURSOR may
 * be used if the cursor position should be wrapped when the end of the line
 * is reached, however the string will still be truncated and not continue on
 * the next line.  GUI_MON_IS_CURSOR should only be used when this function is
 * being called to actually draw (an inverted) cursor.
 */
	static void
gui_outstr_nowrap(s, len, flags)
	char_u	*s;
	int		len;
	int		flags;
{
	long_u			highlight_mask;
	GuiColor		fg_color;
	GuiColor		bg_color;
	GuiFont			font;
	struct attr_entry *aep = NULL;
	int				fake_bold;
	int				underline;

	if (len == 0)
		return;

	if (len < 0)
		len = STRLEN(s);

	if (gui.highlight_mask > HL_ALL)
	{
		aep = syn_gui_attr2entry(gui.highlight_mask);
		if (aep == NULL)			/* highlighting not set */
			highlight_mask = 0;
		else
			highlight_mask = aep->ae_attr;
	}
	else
		highlight_mask = gui.highlight_mask;

	if ((flags & GUI_MON_IS_CURSOR) && gui.in_focus)
		highlight_mask |= HL_INVERSE;

	/* Set the font */
	if (aep != NULL && aep->ae_u.gui.font != 0)
		font = aep->ae_u.gui.font;
	else
	{
		if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) && gui.bold_font != 0)
			if ((highlight_mask & HL_ITALIC) && gui.boldital_font != 0)
				font = gui.boldital_font;
			else
				font = gui.bold_font;
		else if ((highlight_mask & HL_ITALIC) && gui.ital_font != 0)
			font = gui.ital_font;
		else
			font = gui.norm_font;
	}
	gui_mch_set_font(font);

	/* Set the color */
	bg_color = gui.back_pixel;
	if ((flags & GUI_MON_IS_CURSOR) && gui.in_focus)
		fg_color = gui.cursor_pixel;
	else if (aep != NULL)
	{
		fg_color = aep->ae_u.gui.fg_color;
		if (fg_color == 0)
			fg_color = gui.norm_pixel;
		else
			--fg_color;
		bg_color = aep->ae_u.gui.bg_color;
		if (bg_color == 0)
			bg_color = gui.back_pixel;
		else
			--bg_color;
	}
	else
	{
		if (highlight_mask & (HL_BOLD | HL_STANDOUT))
			fg_color = gui.bold_pixel;
		else if (highlight_mask & HL_ITALIC)
			fg_color = gui.ital_pixel;
		else if (highlight_mask & HL_UNDERLINE)
			fg_color = gui.underline_pixel;
		else
			fg_color = gui.norm_pixel;
	}

	if (highlight_mask & (HL_INVERSE | HL_STANDOUT))
	{
		gui_mch_set_fg_color(bg_color);
		gui_mch_set_bg_color(fg_color);
	}
	else
	{
		gui_mch_set_fg_color(fg_color);
		gui_mch_set_bg_color(bg_color);
	}

	/* Clear the selection if we are about to write over it */
	if (clipboard.state == SELECT_DONE
			&& gui.row >= clipboard.start.lnum
			&& gui.row <= clipboard.end.lnum)
		clip_clear_selection();

	fake_bold = FALSE;
	underline = FALSE;

	/* If there's no bold font, then fake it */
	if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) &&
			(gui.bold_font == 0 || (aep != NULL && aep->ae_u.gui.font != 0)))
		fake_bold = TRUE;

	/* Do we underline the text? */
	if ((highlight_mask & HL_UNDERLINE) ||
			((highlight_mask & HL_ITALIC) && gui.ital_font == 0))
		underline = TRUE;

	/* Draw the text */
	gui_mch_draw_string(gui.row, gui.col, s, len, fake_bold, underline);

	if (!(flags & GUI_MON_IS_CURSOR))
	{
		/* Invalidate the old physical cursor position if we wrote over it */
		if (gui.cursor_row == gui.row && gui.cursor_col >= gui.col
		 && gui.cursor_col < gui.col + len)
			INVALIDATE_CURSOR();

		/* Update the cursor position */
		gui.col += len;
		if ((flags & GUI_MON_WRAP_CURSOR) && gui.col >= Columns)
		{
			gui.col = 0;
			gui.row++;
		}
	}
}

/*
 * Un-draw the cursor.  Actually this just redraws the character at the given
 * position.
 */
	void
gui_undraw_cursor()
{
	if (IS_CURSOR_VALID())
		gui_redraw_block(gui.cursor_row, gui.cursor_col, gui.cursor_row,
															gui.cursor_col);
}

	void
gui_redraw(x, y, w, h)
	int		x;
	int		y;
	int		w;
	int		h;
{
	int		row1, col1, row2, col2;

	row1 = Y_2_ROW(y);
	col1 = X_2_COL(x);
	row2 = Y_2_ROW(y + h - 1);
	col2 = X_2_COL(x + w - 1);

	gui_redraw_block(row1, col1, row2, col2);

	/* We may need to redraw the cursor */
	gui_update_cursor(FALSE);

	if (clipboard.state != SELECT_CLEARED)
		clip_redraw_selection(x, y, w, h);
}

	void
gui_redraw_block(row1, col1, row2, col2)
	int		row1;
	int		col1;
	int		row2;
	int		col2;
{
	int		old_row, old_col;
	long_u	old_hl_mask;
	char_u	*screenp, *attrp, first_attr;
	int		idx, len;

	/* Don't try to draw outside the window! */
	/* Check everything, strange values may be caused by big border width */
	col1 = check_col(col1);
	col2 = check_col(col2);
	row1 = check_row(row1);
	row2 = check_row(row2);

	/* Don't try to update when NextScreen is not valid */
	if (!screen_cleared || NextScreen == NULL)
		return;

	/* Remember where our cursor was */
	old_row = gui.row;
	old_col = gui.col;
	old_hl_mask = gui.highlight_mask;

	for (gui.row = row1; gui.row <= row2; gui.row++)
	{
		gui.col = col1;
		screenp = LinePointers[gui.row] + gui.col;
		attrp = screenp + Columns;
		len = col2 - col1 + 1;
		while (len > 0)
		{
			first_attr = attrp[0];
			for (idx = 0; len > 0 && attrp[idx] == first_attr; idx++)
				--len;
			gui.highlight_mask = first_attr;
			gui_outstr_nowrap(screenp, idx, 0);
			screenp += idx;
			attrp += idx;
		}
	}

	/* Put the cursor back where it was */
	gui.row = old_row;
	gui.col = old_col;
	gui.highlight_mask = old_hl_mask;
}

/*
 * Check bounds for column number
 */
	int
check_col(col)
	int		col;
{
	if (col < 0)
		return 0;
	if (col >= (int)Columns)
		return (int)Columns - 1;
	return col;
}

/*
 * Check bounds for row number
 */
	int
check_row(row)
	int		row;
{
	if (row < 0)
		return 0;
	if (row >= (int)Rows)
		return (int)Rows - 1;
	return row;
}

/*
 * The main GUI input routine.	Waits for a character from the keyboard.
 * wtime == -1		Wait forever.
 * wtime == 0		Don't wait.
 * wtime > 0		Wait wtime milliseconds for a character.
 * Returns OK if a character was found to be available within the given time,
 * or FAIL otherwise.
 */
	int
gui_wait_for_chars(wtime)
	int		wtime;
{
	/*
	 * If we're going to wait a bit, update the menus for the current
	 * State.
	 */
	if (wtime != 0)
		gui_update_menus(0);
	gui_mch_update();
	if (!is_input_buf_empty())	/* Got char, return immediately */
		return OK;
	else if (wtime == 0)		/* Don't wait for char */
		return FAIL;
	else if (wtime > 0)
		return gui_mch_wait_for_chars(wtime);
	else
	{
		if (gui_mch_wait_for_chars(p_ut))
			return OK;
		else
		{
			/*
			 * If no characters arrive within 'updatetime' milli-seconds, flush
			 * all the swap files to disk.
			 */
			updatescript(0);
			return gui_mch_wait_for_chars(-1);
		}
	}
}

/*
 * Generic mouse support function.  Add a mouse event to the input buffer with
 * the given properties.
 *	button			--- may be any of MOUSE_LEFT, MOUSE_MIDDLE, MOUSE_RIGHT,
 *						MOUSE_DRAG, or MOUSE_RELEASE.
 *	x, y			--- Coordinates of mouse in pixels.
 *	repeated_click	--- TRUE if this click comes only a short time after a
 *						previous click.
 *	modifiers		--- Bit field which may be any of the following modifiers
 *						or'ed together: MOUSE_SHIFT | MOUSE_CTRL | MOUSE_ALT.
 * This function will ignore drag events where the mouse has not moved to a new
 * character.
 */
	void
gui_send_mouse_event(button, x, y, repeated_click, modifiers)
	int		button;
	int		x;
	int		y;
	int		repeated_click;
	int_u	modifiers;
{
	static int		prev_row = 0, prev_col = 0;
	static int		prev_button = -1;
	static linenr_t prev_topline = 0;
	static int		num_clicks = 1;
	char_u			string[6];
	int				row, col;

#ifdef USE_CLIPBOARD

	int				checkfor;

	/* If a non-Visual mode selection is in progress, finish it */
	if (clipboard.state == SELECT_IN_PROGRESS)
	{
		clip_process_selection(button, x, y, repeated_click, modifiers);
		return;
	}

	/* Determine which mouse settings to look for based on the current mode */
	switch (State)
	{
		case NORMAL_BUSY:
		case NORMAL:		checkfor = MOUSE_NORMAL;	break;
		case VISUAL:		checkfor = MOUSE_VISUAL;	break;
		case REPLACE:
		case INSERT:		checkfor = MOUSE_INSERT;	break;
		case HITRETURN:		checkfor = MOUSE_RETURN;	break;

			/*
			 * On the command line, use the non-Visual mode selection on all
			 * lines but the command line.
			 */
		case CMDLINE:
			if (Y_2_ROW(y) < cmdline_row)
				checkfor = ' ';
			else
				checkfor = MOUSE_COMMAND;
			break;

		default:
			checkfor = ' ';
			break;
	};
	/*
	 * Allow selection of text in the command line in "normal" modes.
	 */
	if ((State == NORMAL || State == NORMAL_BUSY ||
									   State == INSERT || State == REPLACE) &&
											Y_2_ROW(y) >= gui.num_rows - p_ch)
		checkfor = ' ';

	/*
	 * If the mouse settings say to not use the mouse, use the non-Visual mode
	 * selection.  But if Visual is active, assume that only the Visual area
	 * will be selected.
	 */
	if (!mouse_has(checkfor) && !VIsual_active)
	{
		/* If the selection is done, allow the right button to extend it */
		if (clipboard.state == SELECT_DONE && button == MOUSE_RIGHT)
		{
			clip_process_selection(button, x, y, repeated_click, modifiers);
			return;
		}

		/* Allow the left button to start the selection */
		else if (button == MOUSE_LEFT)
		{
			clip_start_selection(button, x, y, repeated_click, modifiers);
			return;
		}
	}

	if (clipboard.state != SELECT_CLEARED)
		clip_clear_selection();
#endif

	row = Y_2_ROW(y);
	col = X_2_COL(x);

	/*
	 * If we are dragging and the mouse hasn't moved far enough to be on a
	 * different character, then don't send an event to vim.
	 */
	if (button == MOUSE_DRAG && row == prev_row && col == prev_col)
		return;

	/*
	 * If topline has changed (window scrolled) since the last click, reset
	 * repeated_click, because we don't want starting Visual mode when
	 * clicking on a different character in the text.
	 */
	if (curwin->w_topline != prev_topline)
		repeated_click = FALSE;

	string[0] = CSI;	/* this sequence is recognized by check_termcode() */
	string[1] = KS_MOUSE;
	string[2] = K_FILLER;
	if (button != MOUSE_DRAG && button != MOUSE_RELEASE)
	{
		if (repeated_click)
		{
			/*
			 * Handle multiple clicks.  They only count if the mouse is still
			 * pointing at the same character.
			 */
			if (button != prev_button || row != prev_row || col != prev_col)
				num_clicks = 1;
			else if (++num_clicks > 4)
				num_clicks = 1;
		}
		else
			num_clicks = 1;
		prev_button = button;
		prev_topline = curwin->w_topline;

		string[3] = (char_u)(button | 0x20);
		SET_NUM_MOUSE_CLICKS(string[3], num_clicks);
	}
	else
		string[3] = (char_u)button;

	string[3] |= modifiers;
	string[4] = (char_u)(col + ' ' + 1);
	string[5] = (char_u)(row + ' ' + 1);
	add_to_input_buf(string, 6);

	prev_row = row;
	prev_col = col;
}

/*
 * Menu stuff.
 */

	void
gui_menu_cb(menu)
	GuiMenu	*menu;
{
	char_u	bytes[3 + sizeof(long_u)];

	bytes[0] = CSI;
	bytes[1] = KS_MENU;
	bytes[2] = K_FILLER;
	add_long_to_buf((long_u)menu, bytes + 3);
	add_to_input_buf(bytes, 3 + sizeof(long_u));
}

/*
 * Return the index into the menu->strings or menu->noremap arrays for the
 * current state.  Returns MENU_INDEX_INVALID if there is no mapping for the
 * given menu in the current mode.
 */
	int
gui_get_menu_index(menu, state)
	GuiMenu	*menu;
	int		state;
{
	int		idx;

	if (VIsual_active)
		idx = MENU_INDEX_VISUAL;
	else if ((state & NORMAL))
		idx = MENU_INDEX_NORMAL;
	else if ((state & INSERT))
		idx = MENU_INDEX_INSERT;
	else if ((state & CMDLINE))
		idx = MENU_INDEX_CMDLINE;
	else
		idx = MENU_INDEX_INVALID;

	if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
		idx = MENU_INDEX_INVALID;
	return idx;
}

/*
 * Return the modes specified by the given menu command (eg :menu! returns
 * MENU_CMDLINE_MODE | MENU_INSERT_MODE).  If noremap is not NULL, then the
 * flag it points to is set according to whether the command is a "nore"
 * command.  If unmenu is not NULL, then the flag it points to is set
 * according to whether the command is an "unmenu" command.
 */
	static int
gui_get_menu_cmd_modes(cmd, forceit, noremap, unmenu)
	char_u	*cmd;
	int		forceit;		/* Was there a "!" after the command? */
	int		*noremap;
	int		*unmenu;
{
	int		modes = 0x0;

	if (*cmd == 'n' && cmd[1] != 'o')	/* nmenu, nnoremenu */
	{
		modes |= MENU_NORMAL_MODE;
		cmd++;
	}
	else if (*cmd == 'v')				/* vmenu, vnoremenu */
	{
		modes |= MENU_VISUAL_MODE;
		cmd++;
	}
	else if (*cmd == 'i')				/* imenu, inoremenu */
	{
		modes |= MENU_INSERT_MODE;
		cmd++;
	}
	else if (*cmd == 'c')				/* cmenu, cnoremenu */
	{
		modes |= MENU_CMDLINE_MODE;
		cmd++;
	}
	else if (forceit)					/* menu!, noremenu! */
		modes |= MENU_INSERT_MODE | MENU_CMDLINE_MODE;
	else							/* menu, noremenu */
		modes |= MENU_NORMAL_MODE | MENU_VISUAL_MODE;

	if (noremap != NULL)
		*noremap = (*cmd == 'n');
	if (unmenu != NULL)
		*unmenu = (*cmd == 'u');
	return modes;
}

/*
 * Do the :menu commands.
 */
	void
gui_do_menu(cmd, arg, forceit)
	char_u	*cmd;
	char_u	*arg;
	int		forceit;
{
	char_u	*menu_path;
	int		modes;
	char_u	*map_to;
	int		noremap;
	int		unmenu;
	char_u	*map_buf;

	modes = gui_get_menu_cmd_modes(cmd, forceit, &noremap, &unmenu);
	menu_path = arg;
	if (*menu_path == NUL)
	{
		gui_show_menus(menu_path, modes);
		return;
	}
	while (*arg && !vim_iswhite(*arg))
	{
		if ((*arg == '\\' || *arg == Ctrl('V')) && arg[1] != NUL)
			arg++;
		arg++;
	}
	if (*arg != NUL)
		*arg++ = NUL;
	arg = skipwhite(arg);
	map_to = arg;
	if (*map_to == NUL && !unmenu)
	{
		gui_show_menus(menu_path, modes);
		return;
	}
	else if (*map_to != NUL && unmenu)
	{
		EMSG("Trailing characters");
		return;
	}
	if (unmenu)
	{
		if (STRCMP(menu_path, "*") == 0)		/* meaning: remove all menus */
			menu_path = (char_u *)"";
		gui_remove_menu(&gui.root_menu, menu_path, modes);
	}
	else
	{
		/* Replace special key codes */
		map_to = replace_termcodes(map_to, &map_buf, FALSE);
		gui_add_menu_path(menu_path, modes, gui_menu_cb, map_to, noremap);
		vim_free(map_buf);
	}
}

/*
 * Used recursively by gui_update_menus (see below)
 */
	static void
gui_update_menus_recurse(menu, mode)
	GuiMenu	*menu;
	int		mode;
{
	while (menu)
	{
		if (menu->modes & mode)
		{
			if (vim_strchr(p_guioptions, GO_GREY) != NULL)
				gui_mch_menu_grey(menu, FALSE);
			else
				gui_mch_menu_hidden(menu, FALSE);
			gui_update_menus_recurse(menu->children, mode);
		}
		else
		{
			if (vim_strchr(p_guioptions, GO_GREY) != NULL)
				gui_mch_menu_grey(menu, TRUE);
			else
				gui_mch_menu_hidden(menu, TRUE);
		}
		menu = menu->next;
	}
}

/*
 * Make sure only the valid menu items appear for this mode.  If
 * force_menu_update is not TRUE, then we only do this if the mode has changed
 * since last time.  If "modes" is not 0, then we use these modes instead.
 */
	void
gui_update_menus(modes)
	int		modes;
{
	static int prev_mode = -1;

	int mode = 0;

	if (modes != 0x0)
		mode = modes;
	else if (VIsual_active)
		mode = MENU_VISUAL_MODE;
	else if (State & NORMAL)
		mode = MENU_NORMAL_MODE;
	else if (State & INSERT)
		mode = MENU_INSERT_MODE;
	else if (State & CMDLINE)
		mode = MENU_CMDLINE_MODE;

	if (force_menu_update || mode != prev_mode)
	{
		gui_update_menus_recurse(gui.root_menu, mode);
		gui_mch_draw_menubar();
		prev_mode = mode;
		force_menu_update = FALSE;
	}
}

/*
 * Add the menu with the given name to the menu hierarchy
 */
	static int
gui_add_menu_path(path_name, modes, call_back, call_data, noremap)
	char_u	*path_name;
	int		modes;
	void	(*call_back)();
	char_u	*call_data;
	int		noremap;
{
	GuiMenu	**menup;
	GuiMenu	*menu = NULL;
	GuiMenu	*parent;
	char_u	*p;
	char_u	*name;
	int		old_menu_height;
	int		i;

	/* Make a copy so we can stuff around with it, since it could be const */
	path_name = vim_strsave(path_name);
	if (path_name == NULL)
		return FAIL;
	menup = &gui.root_menu;
	parent = NULL;
	name = path_name;
	while (*name)
	{
		/* Get name of this element in the menu hierarchy */
		p = gui_menu_name_skip(name);

		/* See if it's already there */
		menu = *menup;
		while (menu != NULL)
		{
			if (STRCMP(name, menu->name) == 0)
			{
				if (*p == NUL && menu->children != NULL)
				{
					EMSG("Menu path must not lead to a sub-menu");
					vim_free(path_name);
					return FAIL;
				}
				else if (*p != NUL && menu->children == NULL)
				{
					EMSG("Part of menu-item path is not sub-menu");
					vim_free(path_name);
					return FAIL;
				}
				break;
			}
			menup = &menu->next;
			menu = menu->next;
		}
		if (menu == NULL)
		{
			if (*p == NUL && parent == NULL)
			{
				EMSG("Must not add menu items directly to menu bar");
				vim_free(path_name);
				return FAIL;
			}

			/* Not already there, so lets add it */
			menu = (GuiMenu *)alloc(sizeof(GuiMenu));
			if (menu == NULL)
			{
				vim_free(path_name);
				return FAIL;
			}
			menu->modes = modes;
			menu->name = vim_strsave(name);
			menu->cb = NULL;
			for (i = 0; i < 4; i++)
			{
				menu->strings[i] = NULL;
				menu->noremap[i] = FALSE;
			}
			menu->children = NULL;
			menu->next = NULL;
			if (gui.in_use)	 /* Otherwise it will be added when GUI starts */
			{
				if (*p == NUL)
				{
					/* Real menu item, not sub-menu */
					gui_mch_add_menu_item(menu, parent);

					/* Want to update menus now even if mode not changed */
					force_menu_update = TRUE;
				}
				else
				{
					/* Sub-menu (not at end of path yet) */
					old_menu_height = gui.menu_height;
					gui_mch_add_menu(menu, parent);
					if (gui.menu_height != old_menu_height)
						gui_set_winsize(FALSE);
				}
			}
			*menup = menu;
		}
		else
		{
			/*
			 * If this menu option was previously only available in other
			 * modes, then make sure it's available for this one now
			 */
			menu->modes |= modes;
		}

		menup = &menu->children;
		parent = menu;
		name = p;
	}
	vim_free(path_name);

	if (menu != NULL)
	{
		menu->cb = call_back;
		p = (call_data == NULL) ? NULL : vim_strsave(call_data);

		/* May match more than one of these */
		if (modes & MENU_NORMAL_MODE)
		{
			gui_free_menu_string(menu, MENU_INDEX_NORMAL);
			menu->strings[MENU_INDEX_NORMAL] = p;
			menu->noremap[MENU_INDEX_NORMAL] = noremap;
		}
		if (modes & MENU_VISUAL_MODE)
		{
			gui_free_menu_string(menu, MENU_INDEX_VISUAL);
			menu->strings[MENU_INDEX_VISUAL] = p;
			menu->noremap[MENU_INDEX_VISUAL] = noremap;
		}
		if (modes & MENU_INSERT_MODE)
		{
			gui_free_menu_string(menu, MENU_INDEX_INSERT);
			menu->strings[MENU_INDEX_INSERT] = p;
			menu->noremap[MENU_INDEX_INSERT] = noremap;
		}
		if (modes & MENU_CMDLINE_MODE)
		{
			gui_free_menu_string(menu, MENU_INDEX_CMDLINE);
			menu->strings[MENU_INDEX_CMDLINE] = p;
			menu->noremap[MENU_INDEX_CMDLINE] = noremap;
		}
	}
	return OK;
}

/*
 * Remove the (sub)menu with the given name from the menu hierarchy
 * Called recursively.
 */
	static int
gui_remove_menu(menup, name, modes)
	GuiMenu	**menup;
	char_u	*name;
	int		modes;
{
	GuiMenu	*menu;
	GuiMenu	*child;
	char_u	*p;

	if (*menup == NULL)
		return OK;			/* Got to bottom of hierarchy */

	/* Get name of this element in the menu hierarchy */
	p = gui_menu_name_skip(name);

	/* Find the menu */
	menu = *menup;
	while (menu != NULL)
	{
		if (*name == NUL || STRCMP(name, menu->name) == 0)
		{
			if (*p != NUL && menu->children == NULL)
			{
				EMSG("Part of menu-item path is not sub-menu");
				return FAIL;
			}
			if ((menu->modes & modes) != 0x0)
			{
				if (gui_remove_menu(&menu->children, p, modes) == FAIL)
					return FAIL;
			}
			else if (*name != NUL)
			{
				EMSG("Menu only exists in another mode");
				return FAIL;
			}

			/*
			 * When name is empty, we are removing all menu items for the given
			 * modes, so keep looping, otherwise we are just removing the named
			 * menu item (which has been found) so break here.
			 */
			if (*name != NUL)
				break;

			/* Remove the menu item for the given mode[s] */
			menu->modes &= ~modes;

			if (menu->modes == 0x0)
			{
				/* The menu item is no longer valid in ANY mode, so delete it */
				*menup = menu->next;
				gui_free_menu(menu);
			}
			else
				menup = &menu->next;
		}
		else
			menup = &menu->next;
		menu = *menup;
	}
	if (*name != NUL)
	{
		if (menu == NULL)
		{
			EMSG("No menu of that name");
			return FAIL;
		}

		/* Recalculate modes for menu based on the new updated children */
		menu->modes = 0x0;
		for (child = menu->children; child != NULL; child = child->next)
			menu->modes |= child->modes;
		if (menu->modes == 0x0)
		{
			/* The menu item is no longer valid in ANY mode, so delete it */
			*menup = menu->next;
			gui_free_menu(menu);
		}
	}

	return OK;
}

/*
 * Free the given menu structure
 */
	static void
gui_free_menu(menu)
	GuiMenu	*menu;
{
	int		i;

	/* Free machine specific menu structures (only when already created) */
	if (gui.in_use)
		gui_mch_destroy_menu(menu);
	vim_free(menu->name);
	for (i = 0; i < 4; i++)
		gui_free_menu_string(menu, i);
	vim_free(menu);

	/* Want to update menus now even if mode not changed */
	force_menu_update = TRUE;
}

/*
 * Free the menu->string with the given index.
 */
	static void
gui_free_menu_string(menu, idx)
	GuiMenu	*menu;
	int		idx;
{
	int		count = 0;
	int		i;

	for (i = 0; i < 4; i++)
		if (menu->strings[i] == menu->strings[idx])
			count++;
	if (count == 1)
		vim_free(menu->strings[idx]);
	menu->strings[idx] = NULL;
}

/*
 * Show the mapping associated with a menu item or hierarchy in a sub-menu.
 */
	static int
gui_show_menus(path_name, modes)
	char_u	*path_name;
	int		modes;
{
	char_u	*p;
	char_u	*name;
	GuiMenu	*menu;
	GuiMenu	*parent = NULL;

	menu = gui.root_menu;
	name = path_name = vim_strsave(path_name);
	if (path_name == NULL)
		return FAIL;

	/* First, find the (sub)menu with the given name */
	while (*name)
	{
		p = gui_menu_name_skip(name);
		while (menu != NULL)
		{
			if (STRCMP(name, menu->name) == 0)
			{
				/* Found menu */
				if (*p != NUL && menu->children == NULL)
				{
					EMSG("Part of menu-item path is not sub-menu");
					vim_free(path_name);
					return FAIL;
				}
				else if ((menu->modes & modes) == 0x0)
				{
					EMSG("Menu only exists in another mode");
					vim_free(path_name);
					return FAIL;
				}
				break;
			}
			menu = menu->next;
		}
		if (menu == NULL)
		{
			EMSG("No menu of that name");
			vim_free(path_name);
			return FAIL;
		}
		name = p;
		parent = menu;
		menu = menu->children;
	}

	/* Now we have found the matching menu, and we list the mappings */
													/* Highlight title */
	MSG_PUTS_TITLE("\n--- Menus ---");

	gui_show_menus_recursive(parent, modes, 0);
	return OK;
}

/*
 * Recursively show the mappings associated with the menus under the given one
 */
	static void
gui_show_menus_recursive(menu, modes, depth)
	GuiMenu	*menu;
	int		modes;
	int		depth;
{
	int		i;
	int		bit;

	if (menu != NULL && (menu->modes & modes) == 0x0)
		return;

	if (menu != NULL)
	{
		msg_putchar('\n');
		if (got_int)			/* "q" hit for "--more--" */
			return;
		for (i = 0; i < depth; i++)
			MSG_PUTS("  ");
								/* Same highlighting as for directories!? */
		msg_puts_attr(menu->name, highlight_attr[HLF_D]);
	}

	if (menu != NULL && menu->children == NULL)
	{
		for (bit = 0; bit < 4; bit++)
			if ((menu->modes & modes & (1 << bit)) != 0)
			{
				msg_putchar('\n');
				if (got_int)			/* "q" hit for "--more--" */
					return;
				for (i = 0; i < depth + 2; i++)
					MSG_PUTS("  ");
				msg_putchar("nvic"[bit]);
				if (menu->noremap[bit])
					msg_putchar('*');
				else
					msg_putchar(' ');
				MSG_PUTS("  ");
				msg_outtrans_special(menu->strings[bit], TRUE);
			}
	}
	else
	{
		if (menu == NULL)
		{
			menu = gui.root_menu;
			depth--;
		}
		else
			menu = menu->children;
		for (; menu != NULL; menu = menu->next)
			gui_show_menus_recursive(menu, modes, depth + 1);
	}
}

/*
 * Used when expanding menu names.
 */
static GuiMenu	*expand_menu = NULL;
static int		expand_modes = 0x0;

/*
 * Work out what to complete when doing command line completion of menu names.
 */
	char_u *
gui_set_context_in_menu_cmd(cmd, arg, forceit)
	char_u	*cmd;
	char_u	*arg;
	int		forceit;
{
	char_u	*after_dot;
	char_u	*p;
	char_u	*path_name = NULL;
	char_u	*name;
	int		unmenu;
	GuiMenu	*menu;

	expand_context = EXPAND_UNSUCCESSFUL;

	after_dot = arg;
	for (p = arg; *p && !vim_iswhite(*p); ++p)
	{
		if ((*p == '\\' || *p == Ctrl('V')) && p[1] != NUL)
			p++;
		else if (*p == '.')
			after_dot = p + 1;
	}
	if (*p == NUL)				/* Complete the menu name */
	{
		/*
		 * With :unmenu, you only want to match menus for the appropriate mode.
		 * With :menu though you might want to add a menu with the same name as
		 * one in another mode, so match menus fom other modes too.
		 */
		expand_modes = gui_get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
		if (!unmenu)
			expand_modes = MENU_ALL_MODES;

		menu = gui.root_menu;
		if (after_dot != arg)
		{
			path_name = alloc(after_dot - arg);
			if (path_name == NULL)
				return NULL;
			STRNCPY(path_name, arg, after_dot - arg - 1);
			path_name[after_dot - arg - 1] = NUL;
		}
		name = path_name;
		while (name != NULL && *name)
		{
			p = gui_menu_name_skip(name);
			while (menu != NULL)
			{
				if (STRCMP(name, menu->name) == 0)
				{
					/* Found menu */
					if ((*p != NUL && menu->children == NULL)
						|| ((menu->modes & expand_modes) == 0x0))
					{
						/*
						 * Menu path continues, but we have reached a leaf.
						 * Or menu exists only in another mode.
						 */
						vim_free(path_name);
						return NULL;
					}
					break;
				}
				menu = menu->next;
			}
			if (menu == NULL)
			{
				/* No menu found with the name we were looking for */
				vim_free(path_name);
				return NULL;
			}
			name = p;
			menu = menu->children;
		}

		expand_context = EXPAND_MENUS;
		expand_pattern = after_dot;
		expand_menu = menu;
	}
	else						/* We're in the mapping part */
		expand_context = EXPAND_NOTHING;
	return NULL;
}

/*
 * Expand the menu names.
 */
	int
gui_ExpandMenuNames(prog, num_file, file)
	vim_regexp	*prog;
	int			*num_file;
	char_u		***file;
{
	GuiMenu	*menu;
	int		round;
	int		count;

	/*
	 * round == 1: Count the matches.
	 * round == 2: Save the matches into the array.
	 */
	for (round = 1; round <= 2; ++round)
	{
		count = 0;
		for (menu = expand_menu; menu != NULL; menu = menu->next)
			if ((menu->modes & expand_modes) != 0x0
				&& vim_regexec(prog, menu->name, TRUE))
			{
				if (round == 1)
					count++;
				else
					(*file)[count++] = vim_strsave_escaped(menu->name,
													   (char_u *)" \t\\.");
			}
		if (round == 1)
		{
			*num_file = count;
			if (count == 0 || (*file = (char_u **)
						 alloc((unsigned)(count * sizeof(char_u *)))) == NULL)
				return FAIL;
		}
	}
	return OK;
}

/*
 * Skip over this element of the menu path and return the start of the next
 * element.  Any \ and ^Vs are removed from the current element.
 */
	static char_u *
gui_menu_name_skip(name)
	char_u	*name;
{
	char_u	*p;

	for (p = name; *p && *p != '.'; p++)
		if (*p == '\\' || *p == Ctrl('V'))
		{
			STRCPY(p, p + 1);
			if (*p == NUL)
				break;
		}
	if (*p)
		*p++ = NUL;
	return p;
}

/*
 * After we have started the GUI, then we can create any menus that have been
 * defined.  This is done once here.  gui_add_menu_path() may have already been
 * called to define these menus, and may be called again.  This function calls
 * itself recursively.	Should be called at the top level with:
 * gui_create_initial_menus(gui.root_menu, NULL);
 */
	static void
gui_create_initial_menus(menu, parent)
	GuiMenu	*menu;
	GuiMenu	*parent;
{
	while (menu)
	{
		if (menu->children != NULL)
		{
			gui_mch_add_menu(menu, parent);
			gui_create_initial_menus(menu->children, menu);
		}
		else
			gui_mch_add_menu_item(menu, parent);
		menu = menu->next;
	}
}


/*
 * Set which components are present.
 * If "oldval" is not NULL, "oldval" is the previous value, the new * value is
 * in p_guioptions.
 */
	void
gui_init_which_components(oldval)
	char_u	*oldval;
{
	static int prev_which_scrollbars[3] = {-1, -1, -1};
	static int prev_menu_is_active = -1;

	char_u	*p;
	int		i;
	int		grey_old, grey_new;
	char_u	*temp;
	WIN		*wp;
	int		changed;

	if (oldval != NULL && gui.in_use)
	{
		/*
		 * Check if the menu's go from grey to non-grey or vise versa.
		 */
		grey_old = (vim_strchr(oldval, GO_GREY) != NULL);
		grey_new = (vim_strchr(p_guioptions, GO_GREY) != NULL);
		if (grey_old != grey_new)
		{
			temp = p_guioptions;
			p_guioptions = oldval;
			gui_update_menus(MENU_ALL_MODES);
			p_guioptions = temp;
		}
	}

	gui.menu_is_active = FALSE;
	for (i = 0; i < 3; i++)
		gui.which_scrollbars[i] = FALSE;
	for (p = p_guioptions; *p; p++)
		switch (*p)
		{
			case GO_LEFT:
				gui.which_scrollbars[SBAR_LEFT] = TRUE;
				break;
			case GO_RIGHT:
				gui.which_scrollbars[SBAR_RIGHT] = TRUE;
				break;
			case GO_BOT:
				gui.which_scrollbars[SBAR_BOTTOM] = TRUE;
				break;
			case GO_MENUS:
				gui.menu_is_active = TRUE;
				break;
			case GO_GREY:
				/* make menu's have grey items, ignored here */
				break;
			default:
				/* Should give error message for internal error */
				break;
		}
	if (gui.in_use)
	{
		changed = FALSE;
		for (i = 0; i < 3; i++)
		{
			if (gui.which_scrollbars[i] != prev_which_scrollbars[i])
			{
				if (i == SBAR_BOTTOM)
				{
					gui_mch_enable_scrollbar(&gui.bottom_sbar,
											 gui.which_scrollbars[i]);
				}
				else
				{
					for (wp = firstwin; wp != NULL; wp = wp->w_next)
						gui_mch_enable_scrollbar(&wp->w_scrollbars[i],
												 gui.which_scrollbars[i]);
				}
				changed = TRUE;
			}
			prev_which_scrollbars[i] = gui.which_scrollbars[i];
		}

		if (gui.menu_is_active != prev_menu_is_active)
		{
			gui_mch_enable_menu(gui.menu_is_active);
			prev_menu_is_active = gui.menu_is_active;
			changed = TRUE;
		}

		if (changed)
		{
			/* must_redraw = CLEAR; */
			/* if (vimForm != (Widget)NULL && XtIsRealized(vimForm)) */
			gui_set_winsize(FALSE);
		}
	}
}


/*
 * Scrollbar stuff:
 */

	void
gui_create_scrollbar(sb, wp)
	GuiScrollbar	*sb;
	WIN				*wp;
{
	static int	sbar_ident = 0;
	int		which;

	sb->ident = sbar_ident++;	/* No check for too big, but would it happen? */
	sb->wp = wp;
	sb->value = -1;
	sb->size = -1;
	sb->max = -1;
	sb->top = -1;
	sb->height = -1;
	sb->status_height = -1;
	gui_mch_create_scrollbar(sb, (wp == NULL) ? SBAR_HORIZ : SBAR_VERT);
	if (wp != NULL)
	{
		which = (sb == &wp->w_scrollbars[SBAR_LEFT]) ? SBAR_LEFT : SBAR_RIGHT;
		gui_mch_enable_scrollbar(sb, gui.which_scrollbars[which]);
	}
}

/*
 * Find the scrollbar with the given index.
 */
	GuiScrollbar *
gui_find_scrollbar(ident)
	long		ident;
{
	WIN				*wp;

	if (gui.bottom_sbar.ident == ident)
		return &gui.bottom_sbar;
	for (wp = firstwin; wp != NULL; wp = wp->w_next)
	{
		if (wp->w_scrollbars[SBAR_LEFT].ident == ident)
			return &wp->w_scrollbars[SBAR_LEFT];
		if (wp->w_scrollbars[SBAR_RIGHT].ident == ident)
			return &wp->w_scrollbars[SBAR_RIGHT];
	}
	return NULL;
}

	void
gui_drag_scrollbar(sb, value, still_dragging)
	GuiScrollbar	*sb;
	int				value;
	int				still_dragging;
{
	char_u		bytes[4 + sizeof(long_u)];
	WIN			*wp;
	int			sb_num;
	int         byte_count;

	if (sb == NULL)
		return;
	if (still_dragging)
	{
		if (sb->wp == NULL)
			gui.dragged_sb = SBAR_BOTTOM;
		else if (sb == &sb->wp->w_scrollbars[SBAR_LEFT])
			gui.dragged_sb = SBAR_LEFT;
		else
			gui.dragged_sb = SBAR_RIGHT;
		gui.dragged_wp = sb->wp;
	}
	else
		gui.dragged_sb = SBAR_NONE;

	if (sb->wp != NULL)
	{
		/* Vertical sbar info is kept in the first sbar (the left one) */
		sb = &sb->wp->w_scrollbars[0];
	}

	/*
	 * Check validity of value
	 */
	if (value < 0)
		value = 0;
#ifdef SCROLL_PAST_END
	else if (value > sb->max)
		value = sb->max;
#else
	if (value > sb->max - sb->size + 1)
		value = sb->max - sb->size + 1;
#endif

	sb->value = value;

	if (sb->wp != NULL)
	{
		sb_num = 0;
		for (wp = firstwin; wp != sb->wp && wp != NULL; wp = wp->w_next)
			sb_num++;

		if (wp == NULL)
			return;

		bytes[0] = CSI;
		bytes[1] = KS_SCROLLBAR;
		bytes[2] = K_FILLER;
		bytes[3] = (char_u)sb_num;
		byte_count = 4;
	}
	else
	{
		bytes[0] = CSI;
		bytes[1] = KS_HORIZ_SCROLLBAR;
		bytes[2] = K_FILLER;
		byte_count = 3;
	}

	add_long_to_buf(value, bytes + byte_count);
	add_to_input_buf(bytes, byte_count + sizeof(long_u));
}

/*
 * Vertical scrollbar stuff:
 */

	static void
gui_update_scrollbars(force)
	int				force;			/* Force all scrollbars to get updated */
{
	WIN				*wp;
	GuiScrollbar	*sb;
	int				val, size, max;
	int				which_sb;
	int				cmdline_height;
	int				h, y, tmp;

	/* Update the horizontal scrollbar */
	gui_update_horiz_scrollbar(force);

	/* Return straight away if there is neither a left nor right scrollbar */
	if (!gui.which_scrollbars[SBAR_LEFT] && !gui.which_scrollbars[SBAR_RIGHT])
		return;

	/*
	 * Don't want to update a scrollbar while we're dragging it.  But if we
	 * have both a left and right scrollbar, and we drag one of them, we still
	 * need to update the other one.
	 */
	if (	   (gui.dragged_sb == SBAR_LEFT
				|| gui.dragged_sb == SBAR_RIGHT)
			&& (!gui.which_scrollbars[SBAR_LEFT]
				|| !gui.which_scrollbars[SBAR_RIGHT])
			&& !force)
		return;

	if (!force && (gui.dragged_sb == SBAR_LEFT || gui.dragged_sb == SBAR_RIGHT))
	{
		/*
		 * If we have two scrollbars and one of them is being dragged, just
		 * copy the scrollbar position from the dragged one to the other one.
		 */
		which_sb = SBAR_LEFT + SBAR_RIGHT - gui.dragged_sb;
		if (gui.dragged_wp != NULL)
			gui_mch_set_scrollbar_thumb(
					&gui.dragged_wp->w_scrollbars[which_sb],
					gui.dragged_wp->w_scrollbars[0].value,
					gui.dragged_wp->w_scrollbars[0].size,
					gui.dragged_wp->w_scrollbars[0].max);
		return;
	}

	cmdline_height = Rows;
	for (wp = firstwin; wp; wp = wp->w_next)
	{
		cmdline_height -= wp->w_height + wp->w_status_height;
		if (wp->w_buffer == NULL)		/* just in case */
			continue;
#ifdef SCROLL_PAST_END
		max = wp->w_buffer->b_ml.ml_line_count - 1;
#else
		max = wp->w_buffer->b_ml.ml_line_count + wp->w_height - 2;
#endif
		if (max < 0)					/* empty buffer */
			max = 0;
		val = wp->w_topline - 1;
		size = wp->w_height;
#ifdef SCROLL_PAST_END
		if (val > max)					/* just in case */
			val = max;
#else
		if (size > max + 1)				/* just in case */
			size = max + 1;
		if (val > max - size + 1)
			val = max - size + 1;
#endif
		if (val < 0)					/* minimal value is 0 */
			val = 0;

		/*
		 * Scrollbar at index 0 (the left one) contains all the information.
		 * It would be the same info for left and right so we just store it for
		 * one of them.
		 */
		sb = &wp->w_scrollbars[0];

		/*
		 * Note: no check for valid w_botline.  If it's not valid the
		 * scrollbars will be updated later anyway.
		 */
		if (size < 1 || wp->w_botline - 2 > max)
		{
			/*
			 * This can happen during changing files.  Just don't update the
			 * scrollbar for now.
			 */
			sb->height = 0;			/* Force update next time */
			continue;
		}
		if (force || sb->height != wp->w_height
			|| sb->top != wp->w_winpos
			|| sb->status_height != wp->w_status_height)
		{
			/* Height or position of scrollbar has changed */
			sb->top = wp->w_winpos;
			sb->height = wp->w_height;
			sb->status_height = wp->w_status_height;

			/* Calculate height and position in pixels */
			h = sb->height * gui.char_height
					+ sb->status_height * gui.char_height / 2;
			y = sb->top * gui.char_height + gui.border_offset;
			if (gui.menu_is_active)
				y += gui.menu_height;

			if (wp == firstwin)
			{
				/* Height of top scrollbar includes width of top border */
				h += gui.border_offset;
				y -= gui.border_offset;
			}
			else
			{
				/*
				 * Height of other scrollbars includes half of status bar above
				 */
				tmp = wp->w_prev->w_status_height * (gui.char_height + 1) / 2;
				h += tmp;
				y -= tmp;
			}
			if (gui.which_scrollbars[SBAR_LEFT])
				gui_mch_set_scrollbar_pos(&wp->w_scrollbars[SBAR_LEFT],
										  gui.left_sbar_x, y,
										  gui.scrollbar_width, h);
			if (gui.which_scrollbars[SBAR_RIGHT])
				gui_mch_set_scrollbar_pos(&wp->w_scrollbars[SBAR_RIGHT],
										  gui.right_sbar_x, y,
										  gui.scrollbar_width, h);
		}
		if (force || sb->value != val
			|| sb->size != size
			|| sb->max != max)
		{
			/* Thumb of scrollbar has moved */
			sb->value = val;
			sb->size = size;
			sb->max = max;
			if (gui.which_scrollbars[SBAR_LEFT] && gui.dragged_sb != SBAR_LEFT)
				gui_mch_set_scrollbar_thumb(&wp->w_scrollbars[SBAR_LEFT],
											val, size, max);
			if (gui.which_scrollbars[SBAR_RIGHT]
										&& gui.dragged_sb != SBAR_RIGHT)
				gui_mch_set_scrollbar_thumb(&wp->w_scrollbars[SBAR_RIGHT],
											val, size, max);
		}
	}
}

/*
 * Scroll a window according to the values set in the globals current_scrollbar
 * and scrollbar_value.  Return TRUE if the cursor in the current window moved
 * or FALSE otherwise.
 */
	int
gui_do_scroll()
{
	WIN			*wp, *old_wp;
	int			i;
	long		nlines;
	FPOS		old_cursor;
	linenr_t	old_topline;

	for (wp = firstwin, i = 0; i < current_scrollbar; i++)
	{
		if (wp == NULL)
			break;
		wp = wp->w_next;
	}
	if (wp == NULL)
	{
		/* Couldn't find window */
		return FALSE;
	}

	/*
	 * Compute number of lines to scroll.  If zero, nothing to do.
	 */
	nlines = (long)scrollbar_value + 1 - (long)wp->w_topline;
	if (nlines == 0)
		return FALSE;

	old_cursor = curwin->w_cursor;
	old_wp = curwin;
	old_topline = wp->w_topline;
	curwin = wp;
	curbuf = wp->w_buffer;
	if (nlines < 0)
		scrolldown(-nlines);
	else
		scrollup(nlines);
	if (old_topline != wp->w_topline)
	{
		if (p_so)
		{
			cursor_correct();			/* fix window for 'so' */
			update_topline();			/* avoid up/down jump */
		}
		coladvance(curwin->w_curswant);
	}

	curwin = old_wp;
	curbuf = old_wp->w_buffer;

	/*
	 * Don't call updateWindow() when nothing has changed (it will overwrite
	 * the status line!).
	 */
	if (old_topline != wp->w_topline)
	{
		wp->w_redr_type = VALID;
		updateWindow(wp);	/* update window, status line, and cmdline */
	}

	return !equal(curwin->w_cursor, old_cursor);
}


/*
 * Horizontal scrollbar stuff:
 */

	static void
gui_update_horiz_scrollbar(force)
	int		force;
{
	int		value, size, max;
	char_u	*p;

	if (!gui.which_scrollbars[SBAR_BOTTOM])
		return;

	if (!force && gui.dragged_sb == SBAR_BOTTOM)
		return;

	if (!force && curwin->w_p_wrap && gui.prev_wrap)
		return;

	/*
	 * It is possible for the cursor to be invalid if we're in the middle of
	 * something (like changing files).  If so, don't do anything for now.
	 */
	if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
	{
		gui.bottom_sbar.value = -1;
		return;
	}

	size = Columns;
	if (curwin->w_p_wrap)
	{
		value = 0;
#ifdef SCROLL_PAST_END
		max = 0;
#else
		max = Columns - 1;
#endif
	}
	else
	{
		value = curwin->w_leftcol;

		/* Calculate max for horizontal scrollbar */
		p = ml_get_curline();
		max = 0;
		if (p != NULL && p[0] != NUL)
			while (p[1] != NUL)				/* Don't count last character */
				max += chartabsize(*p++, (colnr_t)max);
#ifndef SCROLL_PAST_END
		max += Columns - 1;
#endif
	}

#ifndef SCROLL_PAST_END
	if (value > max - size + 1)
		value = max - size + 1;		/* limit the value to allowable range */
#endif

	if (!force && value == gui.bottom_sbar.value && size == gui.bottom_sbar.size
												&& max == gui.bottom_sbar.max)
		return;

	gui.bottom_sbar.value = value;
	gui.bottom_sbar.size = size;
	gui.bottom_sbar.max = max;
	gui.prev_wrap = curwin->w_p_wrap;

	gui_mch_set_scrollbar_thumb(&gui.bottom_sbar, value, size, max);
}

/*
 * Do a horizontal scroll.  Return TRUE if the cursor moved, or FALSE otherwise
 */
	int
gui_do_horiz_scroll()
{
	/* no wrapping, no scrolling */
	if (curwin->w_p_wrap)
		return FALSE;

	curwin->w_leftcol = scrollbar_value;
	return leftcol_changed();
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.