diff options
Diffstat (limited to 'usr.bin/vim/gui.c')
-rw-r--r-- | usr.bin/vim/gui.c | 2024 |
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; +} |