summaryrefslogtreecommitdiff
path: root/usr.bin/vim/gui.c
diff options
context:
space:
mode:
authorJason Downs <downsj@cvs.openbsd.org>1996-09-07 21:40:33 +0000
committerJason Downs <downsj@cvs.openbsd.org>1996-09-07 21:40:33 +0000
commitc224fc199c25dd257673c273eb344786b9bf532c (patch)
tree8f8ed1297120c537480d9e5d46bfe7452bd8505b /usr.bin/vim/gui.c
parentd0d91e2d3d6569e4defdd5178241f28fa678d753 (diff)
Initial import of vim 4.2.
This is meant to replace nvi in the tree. Vim, in general, works better, provides more features, and does not suffer from the license problems being imposed upon nvi. On the other hand, vim lacks a non-visual ex mode, in addition to open mode. This includes the GUI (X11) code, but doesn't try to compile it.
Diffstat (limited to 'usr.bin/vim/gui.c')
-rw-r--r--usr.bin/vim/gui.c2024
1 files changed, 2024 insertions, 0 deletions
diff --git a/usr.bin/vim/gui.c b/usr.bin/vim/gui.c
new file mode 100644
index 00000000000..7ddd00f8a6b
--- /dev/null
+++ b/usr.bin/vim/gui.c
@@ -0,0 +1,2024 @@
+/* $OpenBSD: gui.c,v 1.1 1996/09/07 21:40:28 downsj Exp $ */
+/* 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_outstr __ARGS((char_u *, int));
+static void gui_update_selection __ARGS((void));
+static int gui_get_menu_cmd_modes __ARGS((char_u *, int, int *, 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((void));
+static void gui_update_horiz_scrollbar __ARGS((void));
+
+/*
+ * 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;
+
+ old_term = strsave(term_strings[KS_NAME]);
+ mch_setmouse(FALSE); /* first switch mouse off */
+
+ /* set_termname() will call gui_init() to start the GUI */
+ termcapinit((char_u *)"builtin_gui");
+
+ if (!gui.in_use) /* failed to start GUI */
+ termcapinit(old_term);
+
+ vim_free(old_term);
+
+ /*
+ * 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 && fork() > 0)
+ exit(0);
+}
+
+/*
+ * 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);
+}
+
+static struct default_menu
+{
+ char *name; /* name of menu item */
+ int mode; /* mode where menu is valid */
+ char *command; /* resulting command */
+} default_menus[] =
+{
+ /* Help menu. Some reason Motif is happier if this is added first. */
+ {"Help.Overview <F1>", MENU_NORMAL_MODE, ":help\r"},
+ {"Help.Overview <F1>", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:help\r"},
+ {"Help.How to\\.\\.\\.",MENU_NORMAL_MODE, ":help how_to\r"},
+ {"Help.How to\\.\\.\\.",MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:help how_to\r"},
+ {"Help.GUI", MENU_NORMAL_MODE, ":help gui\r"},
+ {"Help.GUI", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:help gui\r"},
+ {"Help.Version", MENU_NORMAL_MODE, ":version\r"},
+ {"Help.Version", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:version\r"},
+ {"Help.Credits", MENU_NORMAL_MODE, ":help credits\r"},
+ {"Help.Credits", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:help credits\r"},
+ {"Help.Copying", MENU_NORMAL_MODE, ":help uganda\r"},
+ {"Help.Copying", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:help uganda\r"},
+
+ /* File menu */
+ {"File.Save :w", MENU_NORMAL_MODE, ":w\r"},
+ {"File.Save :w", MENU_INSERT_MODE, "\017:w\r"},
+
+ {"File.Close :q", MENU_NORMAL_MODE, ":q\r"},
+ {"File.Close :q", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:q\r"},
+
+ {"File.Quit :qa", MENU_NORMAL_MODE, ":qa\r"},
+ {"File.Quit :qa", MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:qa\r"},
+
+ {"File.Save-Quit :wqa",MENU_NORMAL_MODE, ":wqa\r"},
+ {"File.Save-Quit :wqa",MENU_VISUAL_MODE|MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\033:wqa\r"},
+
+ /* Edit menu */
+ {"Edit.Undo", MENU_NORMAL_MODE, "u"},
+ {"Edit.Redo", MENU_NORMAL_MODE, "\022"},
+
+ {"Edit.Cut", MENU_VISUAL_MODE, "x"},
+ {"Edit.Copy", MENU_VISUAL_MODE, "y"},
+ {"Edit.Put Before", MENU_NORMAL_MODE, "[p"},
+ {"Edit.Put Before", MENU_INSERT_MODE, "\017[p"},
+ {"Edit.Put After", MENU_NORMAL_MODE, "]p"},
+ {"Edit.Put After", MENU_INSERT_MODE, "\017]p"},
+ {"Edit.Paste", MENU_NORMAL_MODE, "i\022*\033"}, /* CTRL-R * */
+ {"Edit.Paste", MENU_INSERT_MODE|MENU_CMDLINE_MODE,
+ "\022*"}, /* CTRL-R * */
+ {NULL, 0, NULL}
+};
+
+/*
+ * This is the call which starts the GUI.
+ */
+ void
+gui_init()
+{
+ char_u *env_str;
+ int i;
+
+ gui.dying = FALSE;
+ gui.in_focus = FALSE;
+ gui.dragged_sb = SB_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 = 0;
+ gui.char_height = 0;
+ gui.char_ascent = 0;
+ gui.border_width = 0;
+
+ gui.selection.owned = FALSE;
+ gui.selection.start.lnum = 0;
+ gui.selection.start.col = 0;
+ gui.selection.end.lnum = 0;
+ gui.selection.end.col = 0;
+ gui.selection.state = SELECT_CLEARED;
+
+ gui.root_menu = NULL;
+ gui.menu_is_active = TRUE; /* default: include menu */
+
+ gui.scrollbar_width = SB_DEFAULT_WIDTH;
+ gui.menu_height = MENU_DEFAULT_HEIGHT;
+ for (i = 0; i < 3; i++)
+ gui.new_sb[i] = FALSE;
+
+ gui.prev_wrap = -1;
+
+ for (i = 0; default_menus[i].name != NULL; ++i)
+ gui_add_menu_path((char_u *)default_menus[i].name,
+ default_menus[i].mode, gui_menu_cb,
+ (char_u *)default_menus[i].command, TRUE);
+
+ /*
+ * Switch on the mouse by default.
+ * This can be changed in the .gvimrc.
+ */
+ set_string_option((char_u *)"mouse", -1, (char_u *)"a", TRUE);
+
+ /*
+ * Get system wide defaults for gvim (Unix only)
+ */
+#ifdef HAVE_CONFIG_H
+ do_source(sys_gvimrc_fname, 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, TRUE, TRUE);
+ 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_fname.
+ */
+ 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 HAVE_CONFIG_H
+ && fullpathcmp(sys_gvimrc_fname,
+ (char_u *)GVIMRC_FILE) != FPC_SAME
+#endif
+ )
+ i = do_source((char_u *)GVIMRC_FILE, FALSE);
+ }
+
+ /*
+ * Actually start the GUI itself.
+ */
+ gui.in_use = TRUE; /* Must be set after menus have been set up */
+ if (gui_mch_init() == FAIL)
+ {
+ gui.in_use = FALSE;
+ return;
+ }
+
+ maketitle();
+
+ gui_create_initial_menus(gui.root_menu, NULL);
+}
+
+ 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];
+
+ 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)
+ return OK;
+ }
+
+ /*
+ * Couldn't load any font in 'font', tell gui_mch_init_font() to try and
+ * find a font we can load.
+ */
+ return gui_mch_init_font(NULL);
+}
+
+ 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()
+{
+ gui_check_screen();
+ if (gui.row != gui.cursor_row || gui.col != gui.cursor_col)
+ {
+ gui_undraw_cursor();
+ gui.cursor_row = gui.row;
+ gui.cursor_col = gui.col;
+ gui_mch_draw_cursor();
+ }
+}
+
+/*
+ * 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;
+{
+ gui.num_cols = (pixel_width - 2 * gui.border_offset) / gui.char_width;
+ gui.num_rows = (pixel_height - 2 * gui.border_offset) / gui.char_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_cursor();
+}
+
+/*
+ * 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;
+{
+ gui.highlight_mask |= mask;
+}
+
+ void
+gui_stop_highlight(mask)
+ long_u mask;
+{
+ gui.highlight_mask &= ~mask;
+}
+
+ 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')
+ {
+ 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')
+ {
+ len--;
+ s++;
+ gui.col = 0;
+ }
+ 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_mch_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_mch_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();
+ gui_update_scrollbars();
+ gui_update_horiz_scrollbar();
+
+ /*
+ * We need to make sure this is cleared since Athena doesn't tell us when
+ * he is done dragging.
+ */
+ gui.dragged_sb = SB_NONE;
+
+ if (vim_strchr(p_guioptions, GO_ASEL) != NULL)
+ gui_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_mch_outstr_nowrap(s, this_len, TRUE, FALSE);
+ s += this_len;
+ len -= this_len;
+ }
+ gui_mch_outstr_nowrap(s, len, TRUE, FALSE);
+}
+
+/*
+ * 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();
+}
+
+ 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)
+ {
+ switch (attrp[0])
+ {
+ case CHAR_INVERT:
+ gui.highlight_mask = HL_INVERSE;
+ break;
+ case CHAR_UNDERL:
+ gui.highlight_mask = HL_UNDERLINE;
+ break;
+ case CHAR_BOLD:
+ gui.highlight_mask = HL_BOLD;
+ break;
+ case CHAR_STDOUT:
+ gui.highlight_mask = HL_STANDOUT;
+ break;
+ case CHAR_ITALIC:
+ gui.highlight_mask = HL_ITAL;
+ break;
+ case CHAR_NORMAL:
+ default:
+ gui.highlight_mask = HL_NORMAL;
+ break;
+ }
+ first_attr = attrp[0];
+ for (idx = 0; len > 0 && attrp[idx] == first_attr; idx++)
+ --len;
+ gui_mch_outstr_nowrap(screenp, idx, FALSE, FALSE);
+ 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;
+}
+
+/*
+ * 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;
+
+ 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;
+}
+
+/*
+ * Selection stuff, for cutting and pasting text to other windows.
+ */
+
+/*
+ * Check whether the VIsual area has changed, and if so try to become the owner
+ * of the selection, and free any old converted selection we may still have
+ * lying around. If the VIsual mode has ended, make a copy of what was
+ * selected so we can still give it to others. Will probably have to make sure
+ * this is called whenever VIsual mode is ended.
+ */
+ static void
+gui_update_selection()
+{
+ /* If visual mode is only due to a redo command ("."), then ignore it */
+ if (redo_VIsual_busy)
+ return;
+ if (!VIsual_active)
+ {
+ gui_mch_clear_selection();
+ gui.selection.start = gui.selection.end = VIsual;
+ }
+ else if (lt(VIsual, curwin->w_cursor))
+ {
+ if (!equal(gui.selection.start, VIsual) ||
+ !equal(gui.selection.end, curwin->w_cursor))
+ {
+ gui_mch_clear_selection();
+ gui.selection.start = VIsual;
+ gui.selection.end = curwin->w_cursor;
+ gui_free_selection();
+ gui_own_selection();
+ }
+ }
+ else
+ {
+ if (!equal(gui.selection.start, curwin->w_cursor) ||
+ !equal(gui.selection.end, VIsual))
+ {
+ gui_mch_clear_selection();
+ gui.selection.start = curwin->w_cursor;
+ gui.selection.end = VIsual;
+ gui_free_selection();
+ gui_own_selection();
+ }
+ }
+}
+
+ void
+gui_own_selection()
+{
+ /*
+ * Also want to check somehow that we are reading from the keyboard rather
+ * than a mapping etc.
+ */
+ if (!gui.selection.owned && gui_mch_own_selection())
+ {
+ gui_free_selection();
+ gui.selection.owned = TRUE;
+ }
+}
+
+ void
+gui_lose_selection()
+{
+ gui_free_selection();
+ gui.selection.owned = FALSE;
+ gui_mch_lose_selection();
+}
+
+ void
+gui_copy_selection()
+{
+ if (VIsual_active)
+ {
+ if (vim_strchr(p_guioptions, GO_ASEL) == NULL)
+ gui_update_selection();
+ gui_own_selection();
+ if (gui.selection.owned)
+ gui_get_selection();
+ }
+}
+
+ void
+gui_auto_select()
+{
+ if (vim_strchr(p_guioptions, GO_ASEL) != NULL)
+ gui_copy_selection();
+}
+
+/*
+ * 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, force, noremap, unmenu)
+ char_u *cmd;
+ int force; /* 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 (force) /* 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, force)
+ char_u *cmd;
+ char_u *arg;
+ int force;
+{
+ 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, force, &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);
+ }
+}
+
+/*
+ * 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 i;
+
+ /* Make a copy so we can stuff around with it, since it could be const */
+ path_name = 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 = 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) */
+ gui_mch_add_menu(menu, parent);
+ }
+ }
+ *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 : 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;
+
+ gui_mch_destroy_menu(menu); /* Free machine specific menu structures */
+ 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 = 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 */
+ set_highlight('t'); /* Highlight title */
+ start_highlight();
+ MSG_OUTSTR("\n--- Menus ---");
+ stop_highlight();
+
+ 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_outchar('\n');
+ if (got_int) /* "q" hit for "--more--" */
+ return;
+ for (i = 0; i < depth; i++)
+ MSG_OUTSTR(" ");
+ set_highlight('d'); /* Same as for directories!? */
+ start_highlight();
+ msg_outstr(menu->name);
+ stop_highlight();
+ }
+
+ if (menu != NULL && menu->children == NULL)
+ {
+ for (bit = 0; bit < 4; bit++)
+ if ((menu->modes & modes & (1 << bit)) != 0)
+ {
+ msg_outchar('\n');
+ if (got_int) /* "q" hit for "--more--" */
+ return;
+ for (i = 0; i < depth + 2; i++)
+ MSG_OUTSTR(" ");
+ msg_outchar("nvic"[bit]);
+ if (menu->noremap[bit])
+ msg_outchar('*');
+ else
+ msg_outchar(' ');
+ MSG_OUTSTR(" ");
+ 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, force)
+ char_u *cmd;
+ char_u *arg;
+ int force;
+{
+ 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, force, 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)
+ 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++] = 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;
+{
+ char_u *p;
+ int i;
+ int grey_old, grey_new;
+ char_u *temp;
+
+ if (oldval != NULL)
+ {
+ /*
+ * 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_x11_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[SB_LEFT] = TRUE;
+ break;
+ case GO_RIGHT:
+ gui.which_scrollbars[SB_RIGHT] = TRUE;
+ break;
+ case GO_BOT:
+ gui.which_scrollbars[SB_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)
+ gui_mch_create_which_components();
+}
+
+
+/*
+ * Vertical scrollbar stuff:
+ */
+
+ static void
+gui_update_scrollbars()
+{
+ WIN *wp;
+ int worst_update = SB_UPDATE_NOTHING;
+ int val, size, max;
+ int which_sb;
+ int cmdline_height;
+
+ /*
+ * 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 == SB_LEFT || gui.dragged_sb == SB_RIGHT) &&
+ (!gui.which_scrollbars[SB_LEFT] || !gui.which_scrollbars[SB_RIGHT]))
+ return;
+
+ if (gui.dragged_sb == SB_LEFT || gui.dragged_sb == SB_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 = SB_LEFT + SB_RIGHT - gui.dragged_sb;
+ if (gui.dragged_wp != NULL)
+ gui.dragged_wp->w_scrollbar.update[which_sb] = SB_UPDATE_VALUE;
+ else
+ gui.cmdline_sb.update[which_sb] = SB_UPDATE_VALUE;
+
+ gui_mch_update_scrollbars(SB_UPDATE_VALUE, which_sb);
+ return;
+ }
+
+ /* Return straight away if there is neither a left nor right scrollbar */
+ if (!gui.which_scrollbars[SB_LEFT] && !gui.which_scrollbars[SB_RIGHT])
+ 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;
+#else
+ max = wp->w_buffer->b_ml.ml_line_count + wp->w_height - 1;
+#endif
+ if (max < 1) /* empty buffer */
+ max = 1;
+ val = wp->w_topline;
+ size = wp->w_height;
+#ifdef SCROLL_PAST_END
+ if (val > max) /* just in case */
+ val = max;
+#else
+ if (size > max) /* just in case */
+ size = max;
+ if (val > max - size + 1)
+ {
+ val = max - size + 1;
+ if (val < 1) /* minimal value is 1 */
+ val = 1;
+ }
+#endif
+ if (size < 1 || wp->w_botline - 1 > max)
+ {
+ /*
+ * This can happen during changing files. Just don't update the
+ * scrollbar for now.
+ */
+ }
+ else if (wp->w_scrollbar.height == 0)
+ {
+ /* Must be a new window */
+ wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_CREATE;
+ wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_CREATE;
+ wp->w_scrollbar.value = val;
+ wp->w_scrollbar.size = size;
+ wp->w_scrollbar.max = max;
+ wp->w_scrollbar.top = wp->w_winpos;
+ wp->w_scrollbar.height = wp->w_height;
+ wp->w_scrollbar.status_height = wp->w_status_height;
+ gui.num_scrollbars++;
+ worst_update = SB_UPDATE_CREATE;
+ }
+ else if (wp->w_scrollbar.top != wp->w_winpos
+ || wp->w_scrollbar.height != wp->w_height
+ || wp->w_scrollbar.status_height != wp->w_status_height)
+ {
+ /* Height of scrollbar has changed */
+ wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_HEIGHT;
+ wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_HEIGHT;
+ wp->w_scrollbar.value = val;
+ wp->w_scrollbar.size = size;
+ wp->w_scrollbar.max = max;
+ wp->w_scrollbar.top = wp->w_winpos;
+ wp->w_scrollbar.height = wp->w_height;
+ wp->w_scrollbar.status_height = wp->w_status_height;
+ if (worst_update < SB_UPDATE_HEIGHT)
+ worst_update = SB_UPDATE_HEIGHT;
+ }
+ else if (wp->w_scrollbar.value != val
+ || wp->w_scrollbar.size != size
+ || wp->w_scrollbar.max != max)
+ {
+ /* Thumb of scrollbar has moved */
+ wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_VALUE;
+ wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_VALUE;
+ wp->w_scrollbar.value = val;
+ wp->w_scrollbar.size = size;
+ wp->w_scrollbar.max = max;
+ if (worst_update < SB_UPDATE_VALUE)
+ worst_update = SB_UPDATE_VALUE;
+ }
+
+ /*
+ * We may have just created the left scrollbar say, when we already had
+ * the right one.
+ */
+ if (gui.new_sb[SB_LEFT])
+ wp->w_scrollbar.update[SB_LEFT] = SB_UPDATE_CREATE;
+ if (gui.new_sb[SB_RIGHT])
+ wp->w_scrollbar.update[SB_RIGHT] = SB_UPDATE_CREATE;
+ }
+ if (cmdline_height < 1)
+ cmdline_height = 1; /* Shouldn't happen, but just in case */
+
+ /* Check the command line scrollbar */
+ if (gui.cmdline_sb.height != cmdline_height
+ || gui.cmdline_sb.status_height != lastwin->w_status_height)
+ {
+ /* Height of scrollbar has changed */
+ gui.cmdline_sb.update[SB_LEFT] = SB_UPDATE_HEIGHT;
+ gui.cmdline_sb.update[SB_RIGHT] = SB_UPDATE_HEIGHT;
+ gui.cmdline_sb.value = 0;
+ gui.cmdline_sb.size = 1; /* No thumb */
+ gui.cmdline_sb.max = 0;
+ gui.cmdline_sb.top = Rows - cmdline_height;
+ gui.cmdline_sb.height = cmdline_height;
+ gui.cmdline_sb.status_height = lastwin->w_status_height;
+ if (worst_update < SB_UPDATE_HEIGHT)
+ worst_update = SB_UPDATE_HEIGHT;
+ }
+
+ /*
+ * If we have just created the left or right scrollbar-box, then we need to
+ * update the height of the command line scrollbar (it will already be
+ * created).
+ */
+ if (gui.new_sb[SB_LEFT])
+ {
+ gui.cmdline_sb.update[SB_LEFT] = SB_UPDATE_HEIGHT;
+ worst_update = SB_UPDATE_CREATE;
+ gui.new_sb[SB_LEFT] = FALSE;
+ }
+ if (gui.new_sb[SB_RIGHT])
+ {
+ gui.cmdline_sb.update[SB_RIGHT] = SB_UPDATE_HEIGHT;
+ worst_update = SB_UPDATE_CREATE;
+ gui.new_sb[SB_RIGHT] = FALSE;
+ }
+
+ if (worst_update != SB_UPDATE_NOTHING)
+ {
+ if (gui.which_scrollbars[SB_LEFT] && gui.dragged_sb != SB_LEFT)
+ gui_mch_update_scrollbars(worst_update, SB_LEFT);
+ if (gui.which_scrollbars[SB_RIGHT] && gui.dragged_sb != SB_RIGHT)
+ gui_mch_update_scrollbars(worst_update, SB_RIGHT);
+ }
+}
+
+/*
+ * 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;
+ FPOS old_cursor;
+
+ for (wp = firstwin, i = 0; i < current_scrollbar; i++)
+ {
+ if (wp == NULL)
+ break;
+ wp = wp->w_next;
+ }
+ if (wp != NULL)
+ {
+ old_cursor = curwin->w_cursor;
+ old_wp = curwin;
+ curwin = wp;
+ curbuf = wp->w_buffer;
+ i = (long)scrollbar_value - (long)wp->w_topline;
+ if (i < 0)
+ scrolldown(-i);
+ else if (i > 0)
+ scrollup(i);
+ if (p_so)
+ cursor_correct();
+ coladvance(curwin->w_curswant);
+
+ curwin = old_wp;
+ curbuf = old_wp->w_buffer;
+
+ if (wp == curwin)
+ cursupdate(); /* fix window for 'so' */
+ wp->w_redr_type = VALID;
+ updateWindow(wp); /* update window, status line, and cmdline */
+
+ return !equal(curwin->w_cursor, old_cursor);
+ }
+ else
+ {
+ /* Command-line scrollbar, unimplemented */
+ return FALSE;
+ }
+}
+
+
+/*
+ * Horizontal scrollbar stuff:
+ */
+
+ static void
+gui_update_horiz_scrollbar()
+{
+ int value, size, max;
+
+ if (!gui.which_scrollbars[SB_BOTTOM])
+ return;
+
+ if (gui.dragged_sb == SB_BOTTOM)
+ return;
+
+ if (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)
+ return;
+
+ if (curwin->w_p_wrap)
+ {
+ value = 0;
+ size = Columns;
+#ifdef SCROLL_PAST_END
+ max = 0;
+#else
+ max = Columns - 1;
+#endif
+ }
+ else
+ {
+ value = curwin->w_leftcol;
+ size = Columns;
+#ifdef SCROLL_PAST_END
+ max = gui_get_max_horiz_scroll();
+#else
+ max = gui_get_max_horiz_scroll() + Columns - 1;
+#endif
+ }
+ gui_mch_update_horiz_scrollbar(value, size, max + 1);
+ gui.prev_wrap = curwin->w_p_wrap;
+}
+
+/*
+ * Determine the maximum value for scrolling right.
+ */
+ int
+gui_get_max_horiz_scroll()
+{
+ int max = 0;
+ char_u *p;
+
+ p = ml_get_curline();
+ if (p[0] != NUL)
+ while (p[1] != NUL) /* Don't count last character */
+ max += chartabsize(*p++, (colnr_t)max);
+ return max;
+}
+
+/*
+ * Do a horizontal scroll. Return TRUE if the cursor moved, or FALSE otherwise
+ */
+ int
+gui_do_horiz_scroll()
+{
+ char_u *p;
+ int i;
+ int vcol;
+ int ret_val = FALSE;
+
+ /* no wrapping, no scrolling */
+ if (curwin->w_p_wrap)
+ return FALSE;
+
+ curwin->w_leftcol = scrollbar_value;
+
+ i = 0;
+ vcol = 0;
+ p = ml_get_curline();
+ while (p[i] && i < curwin->w_cursor.col && vcol < curwin->w_leftcol)
+ vcol += chartabsize(p[i++], (colnr_t)vcol);
+ if (vcol < curwin->w_leftcol)
+ {
+ /*
+ * Cursor is on a character that is at least partly off the left hand
+ * side of the screen.
+ */
+ while (p[i] && vcol < curwin->w_leftcol)
+ vcol += chartabsize(p[i++], (colnr_t)vcol);
+ curwin->w_cursor.col = i;
+ curwin->w_set_curswant = TRUE;
+ ret_val = TRUE;
+ }
+
+ while (p[i] && i <= curwin->w_cursor.col
+ && vcol <= curwin->w_leftcol + Columns)
+ vcol += chartabsize(p[i++], (colnr_t)vcol);
+ if (vcol > curwin->w_leftcol + Columns)
+ {
+ /*
+ * Cursor is on a character that is at least partly off the right hand
+ * side of the screen.
+ */
+ if (i < 2)
+ i = 0;
+ else
+ i -= 2;
+ curwin->w_cursor.col = i;
+ curwin->w_set_curswant = TRUE;
+ ret_val = TRUE;
+ }
+ updateScreen(NOT_VALID);
+ return ret_val;
+}