www.pudn.com > vim53src.zip > gui.c
/* vi:set ts=8 sts=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"
/* Structure containing all the GUI information */
Gui gui;
/* Set to TRUE after adding/removing menus to ensure they are updated */
int force_menu_update = FALSE;
static void gui_check_screen __ARGS((void));
static void gui_position_components __ARGS((int, int));
static void gui_outstr __ARGS((char_u *, int));
static void gui_delete_lines __ARGS((int row, int count));
static void gui_insert_lines __ARGS((int row, int count));
static int gui_get_menu_cmd_modes __ARGS((char_u *, int, int *, int *));
static char_u *popup_mode_name __ARGS((char_u *name, int idx));
static void gui_update_menus_recurse __ARGS((GuiMenu *, int));
#ifdef USE_GUI_WIN32
static int gui_add_menu_path __ARGS((char_u *, int, int *, void (*)(), char_u *, int, int));
#else
static int gui_add_menu_path __ARGS((char_u *, int, int *, void (*)(), char_u *, int));
#endif
static int gui_remove_menu __ARGS((GuiMenu **, char_u *, int, int silent));
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 int menu_name_equal __ARGS((char_u *name, GuiMenu *menu));
static int menu_namecmp __ARGS((char_u *name, char_u *mname));
static void gui_create_initial_menus __ARGS((GuiMenu *, GuiMenu *));
static void gui_update_scrollbars __ARGS((int));
static void gui_update_horiz_scrollbar __ARGS((int));
static WIN *y2win __ARGS((int y));
static int get_menu_mode __ARGS((void));
#ifdef USE_GUI_WIN32
static void gui_create_tearoffs_recurse __ARGS((GuiMenu *menu, const char_u *pname, int *pri_tab, int pri_idx));
static void gui_add_tearoff __ARGS((char_u *tearpath, int *pri_tab, int pri_idx));
static void gui_destroy_tearoffs_recurse __ARGS((GuiMenu *menu));
static int s_tearoffs = FALSE;
#endif
/*
* 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.
*/
#if defined(USE_GUI_ATHENA) || defined(macintosh)
# define SCROLL_PAST_END
#endif
/*
* While defining the system menu, gui_sys_menu is TRUE. This avoids
* overruling of menus that the user already defined.
*/
static int gui_sys_menu = FALSE;
/*
* gui_start -- Called when user wants to start the GUI.
*/
void
gui_start()
{
char_u *old_term;
#if defined(UNIX) && !defined(__BEOS__)
pid_t pid;
#endif
old_term = vim_strsave(T_NAME);
mch_setmouse(FALSE); /* first switch mouse off */
/*
* Set_termname() will call gui_init() to start the GUI.
* Set the "starting" flag, to indicate that the GUI will start.
*
* We don't want to open the GUI window until after we've read .gvimrc,
* otherwise we don't know what font we will use, and hence we don't know
* what size the window should be. So if there are errors in the .gvimrc
* file, they will have to go to the terminal: Set full_screen to FALSE.
* full_screen will be set to TRUE again by a successful termcapinit().
*/
settmode(TMODE_COOK); /* stop RAW mode */
if (full_screen)
cursor_on(); /* needed for ":gui" in .vimrc */
gui.starting = TRUE;
full_screen = FALSE;
termcapinit((char_u *)"builtin_gui");
gui.starting = FALSE;
if (!gui.in_use) /* failed to start GUI */
{
termcapinit(old_term); /* back to old term settings */
settmode(TMODE_RAW); /* restart RAW mode */
set_title_defaults(); /* set 'title' and 'icon' again */
}
vim_free(old_term);
#if defined(UNIX) && !defined(__BEOS__)
/*
* Quit the current process and continue in the child.
* Makes "gvim file" disconnect from the shell it was started in.
* Don't do this when Vim was started with "-f" or the 'f' flag is present
* in 'guioptions'.
*/
if (gui.in_use && gui.dofork && vim_strchr(p_guioptions, GO_FORG) == NULL)
{
pid = fork();
if (pid > 0) /* Parent */
exit(0);
#if defined(HAVE_SETSID) || defined(HAVE_SETPGID)
/*
* Change our process group. On some systems/shells a CTRL-C in the
* shell where Vim was started would otherwise kill gvim!
*/
if (pid == 0) /* child */
# if defined(HAVE_SETSID)
(void)setsid();
# else
(void)setpgid(0, 0);
# endif
#endif
}
#endif
}
/*
* Call this when vim starts up, whether or not the GUI is started
*/
void
gui_prepare(argc, argv)
int *argc;
char **argv;
{
/* Menu items may be added before the GUI is started, so set this now */
gui.root_menu = NULL;
gui.in_use = FALSE; /* No GUI yet (maybe later) */
gui.starting = FALSE; /* No GUI yet (maybe later) */
gui.dofork = TRUE; /* default is to use fork() */
gui_mch_prepare(argc, argv);
}
/*
* This is the call which starts the GUI.
*/
void
gui_init()
{
WIN *wp;
static int recursive = 0;
/*
* It's possible to use ":gui" in a .gvimrc file. The first halve of this
* function will then be executed at the first call, the rest by the
* recursive call. This allow the window to be opened halfway reading a
* gvimrc file.
*/
if (!recursive)
{
++recursive;
gui.window_created = FALSE;
gui.dying = FALSE;
gui.in_focus = TRUE; /* so the guicursor setting works */
gui.dragged_sb = SBAR_NONE;
gui.dragged_wp = NULL;
gui.pointer_hidden = FALSE;
gui.col = gui.num_cols = 0;
gui.row = gui.num_rows = 0;
gui.cursor_is_valid = FALSE;
gui.scroll_region_top = 0;
gui.scroll_region_bot = Rows - 1;
gui.highlight_mask = HL_NORMAL;
gui.char_width = 1;
gui.char_height = 1;
gui.char_ascent = 0;
gui.border_width = 0;
gui.norm_font = (GuiFont)NULL;
gui.bold_font = (GuiFont)NULL;
gui.ital_font = (GuiFont)NULL;
gui.boldital_font = (GuiFont)NULL;
clip_init(TRUE);
gui.menu_is_active = TRUE; /* default: include menu */
gui.scrollbar_width = gui.scrollbar_height = SB_DEFAULT_WIDTH;
gui.menu_height = MENU_DEFAULT_HEIGHT;
gui.menu_width = 0;
gui.prev_wrap = -1;
/*
* Set up system-wide default menus.
*/
#ifdef SYS_MENU_FILE
gui_sys_menu = TRUE;
do_source((char_u *)SYS_MENU_FILE, FALSE, FALSE);
gui_sys_menu = FALSE;
#endif
/*
* Switch on the mouse by default, unless the user changed it already.
* This can then be changed in the .gvimrc.
*/
if (!option_was_set((char_u *)"mouse"))
set_string_option_direct((char_u *)"mouse", -1,
(char_u *)"a", TRUE);
/*
* If -U option given, use only the initializations from that file and
* nothing else. Skip all initializations for "-U NONE".
*/
if (use_gvimrc != NULL)
{
if (STRCMP(use_gvimrc, "NONE") != 0
&& do_source(use_gvimrc, FALSE, FALSE) != OK)
EMSG2("Cannot read from \"%s\"", use_gvimrc);
}
else
{
/*
* Get system wide defaults for gvim, only when file name defined.
*/
#ifdef SYS_GVIMRC_FILE
do_source((char_u *)SYS_GVIMRC_FILE, FALSE, FALSE);
#endif
/*
* Try to read GUI initialization commands from the following
* places:
* - environment variable GVIMINIT
* - the user gvimrc file (~/.gvimrc)
* - the second user gvimrc file ($VIM/.gvimrc for Dos)
* The first that exists is used, the rest is ignored.
*/
if (process_env((char_u *)"GVIMINIT") == FAIL
&& do_source((char_u *)USR_GVIMRC_FILE, TRUE, FALSE) == FAIL)
{
#ifdef USR_GVIMRC_FILE2
(void)do_source((char_u *)USR_GVIMRC_FILE2, TRUE, FALSE);
#endif
}
/*
* 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,
* USR_GVIMRC_FILE2 or SYS_GVIMRC_FILE.
*/
if (p_exrc)
{
#ifdef UNIX
{
struct stat s;
/* if ".gvimrc" file is not owned by user, set 'secure'
* mode */
if (stat(GVIMRC_FILE, &s) || s.st_uid != getuid())
secure = p_secure;
}
#else
secure = p_secure;
#endif
if ( fullpathcmp((char_u *)USR_GVIMRC_FILE,
(char_u *)GVIMRC_FILE, FALSE) != FPC_SAME
#ifdef SYS_GVIMRC_FILE
&& fullpathcmp((char_u *)SYS_GVIMRC_FILE,
(char_u *)GVIMRC_FILE, FALSE) != FPC_SAME
#endif
#ifdef USR_GVIMRC_FILE2
&& fullpathcmp((char_u *)USR_GVIMRC_FILE2,
(char_u *)GVIMRC_FILE, FALSE) != FPC_SAME
#endif
)
do_source((char_u *)GVIMRC_FILE, FALSE, FALSE);
if (secure == 2)
need_wait_return = TRUE;
secure = 0;
}
}
if (need_wait_return || msg_didany)
wait_return(TRUE);
--recursive;
}
/* If recursive call opened the window, return here from the first call */
if (gui.in_use)
return;
/*
* Create the GUI windows ready for opening.
*/
gui.in_use = TRUE; /* Must be set after menus have been set up */
if (gui_mch_init() == FAIL)
goto error;
/*
* Check validity of any generic resources that may have been loaded.
*/
if (gui.border_width < 0)
gui.border_width = 0;
/*
* Set up the fonts.
*/
if (font_opt)
set_option_value((char_u *)"gfn", 0L, (char_u *)font_opt);
if (gui_init_font(p_guifont) == FAIL)
goto error;
gui.num_cols = Columns;
gui.num_rows = Rows;
gui_reset_scroll_region();
/* Create initial scrollbars */
for (wp = firstwin; wp; wp = wp->w_next)
{
gui_create_scrollbar(&wp->w_scrollbars[SBAR_LEFT], wp);
gui_create_scrollbar(&wp->w_scrollbars[SBAR_RIGHT], wp);
}
gui_create_scrollbar(&gui.bottom_sbar, NULL);
gui_create_initial_menus(gui.root_menu, NULL);
/* Configure the desired menu and scrollbars */
gui_init_which_components(NULL);
/* All components of the window have been created now */
gui.window_created = TRUE;
gui_set_winsize(TRUE);
/*
* Actually open the GUI window.
*/
if (gui_mch_open() != FAIL)
{
maketitle();
init_gui_options();
return;
}
error:
gui.in_use = FALSE;
clip_init(FALSE);
}
void
gui_exit(rc)
int rc;
{
free_highlight_fonts();
gui.in_use = FALSE;
gui_mch_exit(rc);
}
/*
* Set the font. Uses the 'font' option. The first font name that works is
* used. If none is found, use the default font.
* Return OK when able to set the font.
*/
int
gui_init_font(font_list)
char_u *font_list;
{
#define FONTLEN 100
char_u font_name[FONTLEN];
int font_list_empty = FALSE;
int ret = FAIL;
if (!gui.in_use)
return FAIL;
font_name[0] = NUL;
if (*font_list == NUL)
font_list_empty = TRUE;
else
while (*font_list != NUL)
{
/* Isolate one font name */
(void)copy_option_part(&font_list, font_name, FONTLEN, ",");
if (gui_mch_init_font(font_name) == OK)
{
ret = OK;
break;
}
}
if (ret != OK && STRCMP(font_name, "*") != 0
&& (font_list_empty || gui.norm_font == (GuiFont)0))
{
/*
* Couldn't load any font in 'font_list', keep the current font if
* there is one. If 'font_list' is empty, or if there is no current
* font, tell gui_mch_init_font() to try to find a font we can load.
*/
ret = gui_mch_init_font(NULL);
}
if (ret == OK)
{
/* Set normal font as current font */
gui_mch_set_font(gui.norm_font);
gui_set_winsize(
#ifdef WIN32
TRUE
#else
FALSE
#endif
);
}
return ret;
}
void
gui_set_cursor(row, col)
int row;
int col;
{
gui.row = row;
gui.col = col;
}
/*
* gui_check_screen - check if the cursor is on the screen.
*/
static void
gui_check_screen()
{
if (gui.row >= Rows)
gui.row = Rows - 1;
if (gui.col >= Columns)
gui.col = Columns - 1;
if (gui.cursor_row >= Rows || gui.cursor_col >= Columns)
gui.cursor_is_valid = FALSE;
}
/*
* Redraw the cursor if necessary or when forced.
* Careful: The contents of LinePointers[] must match what is on the screen,
* otherwise this goes wrong. May need to call out_flush() first.
*/
void
gui_update_cursor(force, clear_selection)
int force; /* when TRUE, update even when not moved */
int clear_selection;/* clear selection under cursor */
{
int cur_width = 0;
int cur_height = 0;
long_u old_hl_mask;
int idx;
int id;
GuiColor cfg, cbg, cc; /* cursor fore-/background color */
int cattr; /* cursor attributes */
int attr;
struct attr_entry *aep = NULL;
gui_check_screen();
if (!gui.cursor_is_valid || force
|| gui.row != gui.cursor_row || gui.col != gui.cursor_col)
{
gui_undraw_cursor();
if (gui.row <0)
return;
gui.cursor_row = gui.row;
gui.cursor_col = gui.col;
gui.cursor_is_valid = TRUE;
/* Only write to the screen after LinePointers[] has been initialized */
if (!screen_cleared || NextScreen == NULL)
return;
/* Clear the selection if we are about to write over it */
if (clear_selection)
clip_may_clear_selection(gui.row, gui.row);
/* Check that the cursor is inside the window (resizing may have made
* it invalid) */
if (gui.row >= screen_Rows || gui.col >= screen_Columns)
return;
/*
* How the cursor is drawn depends on the current mode.
*/
idx = get_cursor_idx();
id = cursor_table[idx].id;
/* get the colors and attributes for the cursor. Default is inverted */
cfg = (GuiColor)-1;
cbg = (GuiColor)-1;
cattr = HL_INVERSE;
gui_mch_set_blinking(cursor_table[idx].blinkwait,
cursor_table[idx].blinkon,
cursor_table[idx].blinkoff);
if (id > 0)
{
cattr = syn_id2colors(id, &cfg, &cbg);
--cbg;
--cfg;
}
/*
* Get the attributes for the character under the cursor.
* When no cursor color was given, use the character color.
*/
attr = *(LinePointers[gui.row] + gui.col + screen_Columns);
if (attr > HL_ALL)
aep = syn_gui_attr2entry(attr);
if (aep != NULL)
{
attr = aep->ae_attr;
if (cfg < 0)
cfg = ((attr & HL_INVERSE) ? aep->ae_u.gui.bg_color
: aep->ae_u.gui.fg_color) - 1;
if (cbg < 0)
cbg = ((attr & HL_INVERSE) ? aep->ae_u.gui.fg_color
: aep->ae_u.gui.bg_color) - 1;
}
if (cfg < 0)
cfg = (attr & HL_INVERSE) ? gui.back_pixel : gui.norm_pixel;
if (cbg < 0)
cbg = (attr & HL_INVERSE) ? gui.norm_pixel : gui.back_pixel;
attr &= ~HL_INVERSE;
if (cattr & HL_INVERSE)
{
cc = cbg;
cbg = cfg;
cfg = cc;
}
cattr &= ~HL_INVERSE;
/*
* When we don't have window focus, draw a hollow cursor.
*/
if (!gui.in_focus)
{
gui_mch_draw_hollow_cursor(cbg);
return;
}
old_hl_mask = gui.highlight_mask;
if (cursor_table[idx].shape == SHAPE_BLOCK)
{
/*
* Draw the text character with the cursor colors. Use the
* character attributes plus the cursor attributes.
*/
gui.highlight_mask = (cattr | attr);
gui_outstr_nowrap(LinePointers[gui.row] + gui.col, 1,
GUI_MON_IS_CURSOR | GUI_MON_NOCLEAR, cfg, cbg, 0);
}
else
{
/*
* First draw the partial cursor, then overwrite with the text
* character, using a transparant background.
*/
if (cursor_table[idx].shape == SHAPE_VER)
{
cur_height = gui.char_height;
cur_width = (gui.char_width * cursor_table[idx].percentage
+ 99) / 100;
}
else
{
cur_height = (gui.char_height * cursor_table[idx].percentage
+ 99) / 100;
cur_width = gui.char_width;
}
gui_mch_draw_part_cursor(cur_width, cur_height, cbg);
#ifndef USE_GUI_WIN32 /* doesn't seem to work for Win32 */
gui.highlight_mask = *(LinePointers[gui.row] + gui.col
+ screen_Columns);
gui_outstr_nowrap(LinePointers[gui.row] + gui.col, 1,
GUI_MON_TRS_CURSOR | GUI_MON_NOCLEAR,
(GuiColor)0, (GuiColor)0, 0);
#endif
}
gui.highlight_mask = old_hl_mask;
}
}
void
gui_position_menu()
{
if (gui.menu_is_active && gui.in_use)
gui_mch_set_menu_pos(0, 0, gui.menu_width, gui.menu_height);
}
/*
* Position the various GUI components (text area, menu). The vertical
* scrollbars are NOT handled here. See gui_update_scrollbars().
*/
/* ARGSUSED */
static void
gui_position_components(total_width, total_height)
int total_width;
int total_height;
{
int text_area_x;
int text_area_y;
int text_area_width;
int text_area_height;
gui.menu_width = total_width;
text_area_x = 0;
if (gui.which_scrollbars[SBAR_LEFT])
text_area_x += gui.scrollbar_width;
#ifdef USE_GUI_WIN32
if (vim_strchr(p_guioptions, GO_TOOLBAR) != NULL)
text_area_y = TOOLBAR_BUTTON_HEIGHT + 12;
else
#endif
text_area_y = 0;
if (gui.menu_is_active)
text_area_y += gui.menu_height;
text_area_width = gui.num_cols * gui.char_width + gui.border_offset * 2;
text_area_height = gui.num_rows * gui.char_height + gui.border_offset * 2;
gui_mch_set_text_area_pos(text_area_x,
text_area_y,
text_area_width,
text_area_height);
gui_position_menu();
if (gui.which_scrollbars[SBAR_BOTTOM])
gui_mch_set_scrollbar_pos(&gui.bottom_sbar,
text_area_x,
text_area_y + text_area_height,
text_area_width,
gui.scrollbar_height);
gui.left_sbar_x = 0;
gui.right_sbar_x = text_area_x + text_area_width;
}
int
gui_get_base_width()
{
int base_width;
base_width = 2 * gui.border_offset;
if (gui.which_scrollbars[SBAR_LEFT])
base_width += gui.scrollbar_width;
if (gui.which_scrollbars[SBAR_RIGHT])
base_width += gui.scrollbar_width;
return base_width;
}
int
gui_get_base_height()
{
int base_height;
base_height = 2 * gui.border_offset;
if (gui.which_scrollbars[SBAR_BOTTOM])
base_height += gui.scrollbar_height;
if (gui.menu_is_active)
base_height += gui.menu_height;
#ifdef USE_GUI_WIN32
if (vim_strchr(p_guioptions, GO_TOOLBAR) != NULL)
base_height += (TOOLBAR_BUTTON_HEIGHT+12);
#endif
return base_height;
}
/*
* Should be called after the GUI window has been resized. Its arguments are
* the new width and height of the window in pixels.
*/
void
gui_resize_window(pixel_width, pixel_height)
int pixel_width;
int pixel_height;
{
if (!gui.window_created)
return;
#ifdef USE_GUI_BEOS
vim_lock_screen();
#endif
/* Flush pending output before redrawing */
out_flush();
gui.num_cols = (pixel_width - gui_get_base_width()) / gui.char_width;
gui.num_rows = (pixel_height - gui_get_base_height()) / gui.char_height;
gui_position_components(pixel_width, pixel_height);
gui_reset_scroll_region();
/*
* At the "more" and ":confirm" 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 || State == CONFIRM)
gui.row = gui.num_rows;
if (gui.num_rows != screen_Rows || gui.num_cols != screen_Columns)
set_winsize(0, 0, FALSE);
#ifdef USE_GUI_BEOS
vim_unlock_screen();
#endif
gui_update_scrollbars(TRUE);
gui_update_cursor(FALSE, TRUE);
}
int
gui_get_winsize()
{
Rows = gui.num_rows;
Columns = gui.num_cols;
return OK;
}
/*
* Set the size of the window according to Rows and Columns.
*/
void
gui_set_winsize(fit_to_display)
int fit_to_display;
{
int base_width;
int base_height;
int width;
int height;
int min_width;
int min_height;
int screen_w;
int screen_h;
if (!gui.window_created)
return;
base_width = gui_get_base_width();
base_height = gui_get_base_height();
width = Columns * gui.char_width + base_width;
height = Rows * gui.char_height + base_height;
if (fit_to_display)
{
gui_mch_get_screen_dimensions(&screen_w, &screen_h);
if (width > screen_w)
{
Columns = (screen_w - base_width) / gui.char_width;
if (Columns < MIN_COLUMNS)
Columns = MIN_COLUMNS;
gui.num_cols = Columns;
gui_reset_scroll_region();
width = Columns * gui.char_width + base_width;
}
if (height > screen_h)
{
Rows = (screen_h - base_height) / gui.char_height;
if (Rows < MIN_LINES)
Rows = MIN_LINES;
gui.num_rows = Rows;
gui_reset_scroll_region();
height = Rows * gui.char_height + base_height;
}
}
min_width = base_width + MIN_COLUMNS * gui.char_width;
min_height = base_height + MIN_LINES * gui.char_height;
gui_mch_set_winsize(width, height, min_width, min_height,
base_width, base_height);
gui_position_components(width, height);
gui_update_scrollbars(TRUE);
}
/*
* Make scroll region cover whole screen.
*/
void
gui_reset_scroll_region()
{
gui.scroll_region_top = 0;
gui.scroll_region_bot = gui.num_rows - 1;
}
void
gui_start_highlight(mask)
int mask;
{
if (mask > HL_ALL) /* highlight code */
gui.highlight_mask = mask;
else /* mask */
gui.highlight_mask |= mask;
}
void
gui_stop_highlight(mask)
int mask;
{
if (mask > HL_ALL) /* highlight code */
gui.highlight_mask = HL_NORMAL;
else /* mask */
gui.highlight_mask &= ~mask;
}
/*
* Clear a rectangular region of the screen from text pos (row1, col1) to
* (row2, col2) inclusive.
*/
void
gui_clear_block(row1, col1, row2, col2)
int row1;
int col1;
int row2;
int col2;
{
/* Clear the selection if we are about to write over it */
clip_may_clear_selection(row1, row2);
gui_mch_clear_block(row1, col1, row2, col2);
/* Invalidate cursor if it was in this block */
if ( gui.cursor_row >= row1 && gui.cursor_row <= row2
&& gui.cursor_col >= col1 && gui.cursor_col <= col2)
gui.cursor_is_valid = FALSE;
}
/*
* Write code to update cursor shape later.
*/
void
gui_upd_cursor_shape()
{
OUT_STR("\033|s");
}
void
gui_write(s, len)
char_u *s;
int len;
{
char_u *p;
int arg1 = 0, arg2 = 0;
#ifdef RISCOS
int force = TRUE; /* JK230798, stop Vim being smart or our redraw speed will suffer */
#else
int force = FALSE; /* force cursor update */
#endif
/* #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("");
}
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] == 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 */
clip_scroll_selection(9999);
gui_mch_clear_all();
gui.cursor_is_valid = FALSE;
break;
case 'M': /* Move cursor */
gui_set_cursor(arg1, arg2);
break;
case 's': /* force cursor (shape) update */
force = TRUE;
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_delete_lines(gui.row, 1);
break;
case 'D': /* Delete lines */
gui_delete_lines(gui.row, arg1);
break;
case 'i': /* Insert line */
gui_insert_lines(gui.row, 1);
break;
case 'I': /* Insert lines */
gui_insert_lines(gui.row, arg1);
break;
case '$': /* Clear to end-of-line */
gui_clear_block(gui.row, gui.col, gui.row,
(int)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 */
{
if (s[0] == '\n') /* NL */
{
gui.col = 0;
if (gui.row < gui.scroll_region_bot)
gui.row++;
else
gui_delete_lines(gui.scroll_region_top, 1);
}
else if (s[0] == '\r') /* CR */
{
gui.col = 0;
}
else if (s[0] == '\b') /* Backspace */
{
if (gui.col)
--gui.col;
}
else if (s[0] == Ctrl('L')) /* cursor-right */
{
++gui.col;
}
else if (s[0] == Ctrl('G')) /* Beep */
{
gui_mch_beep();
}
/* Other Ctrl character: shouldn't happen! */
--len; /* Skip this char */
++s;
}
else
{
p = s;
while (len && *p >= 0x20)
{
len--;
p++;
}
gui_outstr(s, p - s);
s = p;
}
}
gui_update_cursor(force, TRUE);
gui_update_scrollbars(FALSE);
/*
* We need to make sure this is cleared since Athena doesn't tell us when
* he is done dragging.
*/
#ifdef USE_GUI_ATHENA
gui.dragged_sb = SBAR_NONE;
#endif
if (vim_strchr(p_guioptions, GO_ASEL) != NULL)
clip_update_selection();
gui_mch_flush(); /* In case vim decides to take a nap */
}
static void
gui_outstr(s, len)
char_u *s;
int len;
{
int this_len;
if (len == 0)
return;
if (len < 0)
len = STRLEN(s);
while (gui.col + len > Columns)
{
this_len = Columns - gui.col;
gui_outstr_nowrap(s, this_len, GUI_MON_WRAP_CURSOR, (GuiColor)0,
(GuiColor)0, 0);
s += this_len;
len -= this_len;
}
gui_outstr_nowrap(s, len, GUI_MON_WRAP_CURSOR, (GuiColor)0, (GuiColor)0, 0);
}
/*
* Output the given string at the current cursor position. If the string is
* too long to fit on the line, then it is truncated.
* "flags":
* GUI_MON_WRAP_CURSOR may be used if the cursor position should be wrapped
* when the end of the line is reached, however the string will still be
* truncated and not continue on the next line.
* GUI_MON_IS_CURSOR should only be used when this function is being called to
* actually draw (an inverted) cursor.
* GUI_MON_TRS_CURSOR is used to draw the cursor text with a transparant
* background.
*/
void
gui_outstr_nowrap(s, len, flags, fg, bg, back)
char_u *s;
int len;
int flags;
GuiColor fg, bg; /* colors for cursor */
int back; /* backup this many chars when using bold trick */
{
long_u highlight_mask;
GuiColor fg_color;
GuiColor bg_color;
GuiFont font;
struct attr_entry *aep = NULL;
int draw_flags;
int col = gui.col;
if (len == 0)
return;
if (len < 0)
len = STRLEN(s);
if (gui.highlight_mask > HL_ALL)
{
aep = syn_gui_attr2entry(gui.highlight_mask);
if (aep == NULL) /* highlighting not set */
highlight_mask = 0;
else
highlight_mask = aep->ae_attr;
}
else
highlight_mask = gui.highlight_mask;
/* Set the font */
if (aep != NULL && aep->ae_u.gui.font != 0)
font = aep->ae_u.gui.font;
else
{
if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) && gui.bold_font != 0)
{
if ((highlight_mask & HL_ITALIC) && gui.boldital_font != 0)
font = gui.boldital_font;
else
font = gui.bold_font;
}
else if ((highlight_mask & HL_ITALIC) && gui.ital_font != 0)
font = gui.ital_font;
else
font = gui.norm_font;
}
gui_mch_set_font(font);
/* Set the color */
bg_color = gui.back_pixel;
if ((flags & GUI_MON_IS_CURSOR) && gui.in_focus)
{
fg_color = fg;
bg_color = bg;
}
else if (aep != NULL)
{
fg_color = aep->ae_u.gui.fg_color;
if (fg_color == 0)
fg_color = gui.norm_pixel;
else
--fg_color;
bg_color = aep->ae_u.gui.bg_color;
if (bg_color == 0)
bg_color = gui.back_pixel;
else
--bg_color;
}
else
fg_color = gui.norm_pixel;
if (highlight_mask & (HL_INVERSE | HL_STANDOUT))
{
#if defined(AMIGA) || defined(RISCOS)
gui_mch_set_colors(bg_color, fg_color);
#else
gui_mch_set_fg_color(bg_color);
gui_mch_set_bg_color(fg_color);
#endif
}
else
{
#if defined(AMIGA) || defined(RISCOS)
gui_mch_set_colors(fg_color, bg_color);
#else
gui_mch_set_fg_color(fg_color);
gui_mch_set_bg_color(bg_color);
#endif
}
/* Clear the selection if we are about to write over it */
if (!(flags & GUI_MON_NOCLEAR))
clip_may_clear_selection(gui.row, gui.row);
draw_flags = 0;
/* If there's no bold font, then fake it */
if ((highlight_mask & (HL_BOLD | HL_STANDOUT)) &&
(gui.bold_font == 0 || (aep != NULL && aep->ae_u.gui.font != 0)))
{
draw_flags |= DRAW_BOLD;
s -= back;
len += back;
col -= back;
}
#ifdef RISCOS
/* If there's no italic font, then fake it */
if ((highlight_mask & HL_ITALIC) && gui.ital_font == 0)
draw_flags |= DRAW_ITALIC;
/* Do we underline the text? */
if (highlight_mask & HL_UNDERLINE)
draw_flags |= DRAW_UNDERL;
#else
/* Do we underline the text? */
if ((highlight_mask & HL_UNDERLINE) ||
((highlight_mask & HL_ITALIC) && gui.ital_font == 0))
draw_flags |= DRAW_UNDERL;
#endif
/* Do we draw transparantly? */
if ((flags & GUI_MON_TRS_CURSOR))
draw_flags |= DRAW_TRANSP;
/* Draw the text */
gui_mch_draw_string(gui.row, col, s, len, draw_flags);
/* May need to invert it when it's part of the selection (assumes len==1) */
if (flags & GUI_MON_NOCLEAR)
clip_may_redraw_selection(gui.row, col);
if (!(flags & (GUI_MON_IS_CURSOR | GUI_MON_TRS_CURSOR)))
{
/* Invalidate the old physical cursor position if we wrote over it */
if (gui.cursor_row == gui.row && gui.cursor_col >= col
&& gui.cursor_col < col + len)
gui.cursor_is_valid = FALSE;
/* Update the cursor position */
gui.col = col + len;
if ((flags & GUI_MON_WRAP_CURSOR) && gui.col >= Columns)
{
gui.col = 0;
gui.row++;
}
}
}
/*
* Un-draw the cursor. Actually this just redraws the character at the given
* position. The character just before it too, for when it was in bold.
*/
void
gui_undraw_cursor()
{
if (gui.cursor_is_valid)
{
if (gui_redraw_block(gui.cursor_row, gui.cursor_col,
gui.cursor_row, gui.cursor_col, GUI_MON_NOCLEAR)
&& gui.cursor_col > 0)
(void)gui_redraw_block(gui.cursor_row, gui.cursor_col - 1,
gui.cursor_row, gui.cursor_col - 1, GUI_MON_NOCLEAR);
}
}
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);
(void)gui_redraw_block(row1, col1, row2, col2, 0);
/*
* We may need to redraw the cursor, but don't take it upon us to change
* its location after a scroll.
* (maybe be more strict even and test col too?)
* These things may be outside the update/clipping region and reality may
* not reflect Vims internal ideas if these operations are clipped away.
*/
if (gui.row == gui.cursor_row)
gui_update_cursor(FALSE, TRUE);
if (clipboard.state != SELECT_CLEARED)
clip_redraw_selection(x, y, w, h);
}
/*
* Draw a rectangular block of characters, from row1 to row2 (inclusive) and
* from col1 to col2 (inclusive).
* Return TRUE when the character before the first drawn character has
* different attributes (may have to be redrawn too).
*/
int
gui_redraw_block(row1, col1, row2, col2, flags)
int row1;
int col1;
int row2;
int col2;
int flags; /* flags for gui_outstr_nowrap() */
{
int old_row, old_col;
long_u old_hl_mask;
char_u *screenp, *attrp, first_attr;
int idx, len;
int back;
int retval = FALSE;
/* Don't try to update when NextScreen is not valid */
if (!screen_cleared || NextScreen == NULL)
return retval;
/* 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);
/* 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 + screen_Columns;
len = col2 - col1 + 1;
/* Find how many chars back this highlighting starts, or where a space
* is. Needed for when the bold trick is used */
for (back = 0; back < col1; ++back)
if (attrp[-1 - back] != attrp[0] || screenp[-1 - back] == ' ')
break;
retval = (col1 && attrp[-1] && back == 0);
/* break it up in strings of characters with the same attributes */
while (len > 0)
{
first_attr = attrp[0];
for (idx = 0; len > 0 && attrp[idx] == first_attr; idx++)
--len;
gui.highlight_mask = first_attr;
gui_outstr_nowrap(screenp, idx, flags,
(GuiColor)0, (GuiColor)0, back);
screenp += idx;
attrp += idx;
back = 0;
}
}
/* Put the cursor back where it was */
gui.row = old_row;
gui.col = old_col;
gui.highlight_mask = old_hl_mask;
return retval;
}
static void
gui_delete_lines(row, count)
int row;
int count;
{
if (row == 0)
clip_scroll_selection(count);
gui_mch_delete_lines(row, count);
}
static void
gui_insert_lines(row, count)
int row;
int count;
{
if (row == 0)
clip_scroll_selection(-count);
gui_mch_insert_lines(row, count);
}
/*
* Check bounds for column number
*/
int
check_col(col)
int col;
{
if (col < 0)
return 0;
if (col >= (int)screen_Columns)
return (int)screen_Columns - 1;
return col;
}
/*
* Check bounds for row number
*/
int
check_row(row)
int row;
{
if (row < 0)
return 0;
if (row >= (int)screen_Rows)
return (int)screen_Rows - 1;
return row;
}
/*
* The main GUI input routine. Waits for a character from the keyboard.
* wtime == -1 Wait forever.
* wtime == 0 Don't wait.
* wtime > 0 Wait wtime milliseconds for a character.
* Returns OK if a character was found to be available within the given time,
* or FAIL otherwise.
*/
int
gui_wait_for_chars(wtime)
long wtime;
{
int retval;
/*
* If we're going to wait a bit, update the menus for the current
* State.
*/
if (wtime != 0)
gui_update_menus(0);
gui_mch_update();
if (!vim_is_input_buf_empty()) /* Got char, return immediately */
return OK;
if (wtime == 0) /* Don't wait for char */
return FAIL;
if (wtime > 0)
{
/* Blink when waiting for a character. Probably only does something
* for showmatch() */
gui_mch_start_blink();
retval = gui_mch_wait_for_chars(wtime);
gui_mch_stop_blink();
return retval;
}
/*
* While we are waiting indefenitely for a character, blink the cursor.
*/
gui_mch_start_blink();
if (gui_mch_wait_for_chars(p_ut) == OK)
retval = OK;
else
{
/*
* If no characters arrive within 'updatetime' milli-seconds, flush
* all the swap files to disk.
*/
updatescript(0);
retval = gui_mch_wait_for_chars(-1L);
}
gui_mch_stop_blink();
return retval;
}
/*
* 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 int num_clicks = 1;
char_u string[6];
int row, col;
#ifdef USE_CLIPBOARD
int checkfor;
int did_clip = FALSE;
/* If a clipboard selection is in progress, handle it */
if (clipboard.state == SELECT_IN_PROGRESS)
{
clip_process_selection(button, x, y, repeated_click, modifiers);
return;
}
/* Determine which mouse settings to look for based on the current mode */
switch (get_real_state())
{
case NORMAL_BUSY:
case OP_PENDING:
case NORMAL: checkfor = MOUSE_NORMAL; break;
case VISUAL: checkfor = MOUSE_VISUAL; break;
case REPLACE:
case INSERT: checkfor = MOUSE_INSERT; break;
case HITRETURN: checkfor = MOUSE_RETURN; break;
/*
* On the command line, use the clipboard selection on all lines
* but the command line. But not when pasting.
*/
case CMDLINE:
if (Y_2_ROW(y) < cmdline_row && button != MOUSE_MIDDLE)
checkfor = ' ';
else
checkfor = MOUSE_COMMAND;
break;
default:
checkfor = ' ';
break;
};
/*
* Allow clipboard selection of text on the command line in "normal"
* modes. Don't do this when dragging the status line, or extending a
* Visual selection.
*/
if ((State == NORMAL || State == NORMAL_BUSY
|| State == INSERT || State == REPLACE)
&& Y_2_ROW(y) >= gui.num_rows - p_ch
&& button != MOUSE_DRAG)
checkfor = ' ';
/*
* If the mouse settings say to not use the mouse, use the non-Visual mode
* selection. But if Visual is active, assume that only the Visual area
* will be selected.
* Exception: On the command line, both the selection is used and a mouse
* key is send.
*/
if (!mouse_has(checkfor) || checkfor == MOUSE_COMMAND)
{
/* Don't do non-visual selection in Visual mode. */
if (VIsual_active)
return;
/*
* When 'mousemodel' is "popup", shift-left is translated to right.
*/
if (mouse_model_popup())
{
if (button == MOUSE_LEFT && (modifiers & MOUSE_SHIFT))
{
button = MOUSE_RIGHT;
modifiers &= ~ MOUSE_SHIFT;
}
}
/* If the selection is done, allow the right button to extend it.
* If the selection is cleared, allow the right button to start it
* from the cursor position. */
if (button == MOUSE_RIGHT)
{
if (clipboard.state == SELECT_CLEARED)
{
if (State == CMDLINE)
{
col = msg_col;
row = msg_row;
}
else
{
col = curwin->w_wcol;
row = curwin->w_wrow + curwin->w_winpos;
}
clip_start_selection(MOUSE_LEFT, FILL_X(col), FILL_Y(row),
FALSE, 0);
}
clip_process_selection(button, x, y, repeated_click, modifiers);
did_clip = TRUE;
}
/* Allow the left button to start the selection */
else if (button ==
# ifdef RISCOS
/* Only start a drag on a drag event. Otherwise
* we don't get a release event.
*/
MOUSE_DRAG
# else
MOUSE_LEFT
# endif
)
{
clip_start_selection(button, x, y, repeated_click, modifiers);
did_clip = TRUE;
}
# ifdef RISCOS
else if (button == MOUSE_LEFT)
{
clip_clear_selection();
did_clip = TRUE;
}
# endif
/* Always allow pasting */
if (button != MOUSE_MIDDLE)
{
if (!mouse_has(checkfor) || button == MOUSE_RELEASE)
return;
button = MOUSE_LEFT;
}
repeated_click = FALSE;
}
if (clipboard.state != SELECT_CLEARED && !did_clip)
clip_clear_selection();
#endif
/*
* Don't put mouse events in the input queue while executing an external
* command.
*/
if (!termcap_active)
return;
row = check_row(Y_2_ROW(y));
col = check_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 != gui_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;
gui_prev_topline = curwin->w_topline;
string[3] = (char_u)(button | 0x20);
SET_NUM_MOUSE_CLICKS(string[3], num_clicks);
}
else
string[3] = (char_u)button;
string[3] |= modifiers;
string[4] = (char_u)(col + ' ' + 1);
string[5] = (char_u)(row + ' ' + 1);
add_to_input_buf(string, 6);
prev_row = row;
prev_col = col;
}
/*
* Menu stuff.
*/
void
gui_menu_cb(menu)
GuiMenu *menu;
{
char_u bytes[3 + sizeof(long_u)];
bytes[0] = CSI;
bytes[1] = KS_MENU;
bytes[2] = K_FILLER;
add_long_to_buf((long_u)menu, bytes + 3);
add_to_input_buf(bytes, 3 + sizeof(long_u));
}
/*
* Return the index into the menu->strings or menu->noremap arrays for the
* current state. Returns MENU_INDEX_INVALID if there is no mapping for the
* given menu in the current mode.
*/
int
gui_get_menu_index(menu, state)
GuiMenu *menu;
int state;
{
int idx;
if (VIsual_active)
idx = MENU_INDEX_VISUAL;
else if ((state & INSERT))
idx = MENU_INDEX_INSERT;
else if ((state & CMDLINE))
idx = MENU_INDEX_CMDLINE;
else if (finish_op)
idx = MENU_INDEX_OP_PENDING;
else if ((state & NORMAL))
idx = MENU_INDEX_NORMAL;
else
idx = MENU_INDEX_INVALID;
if (idx != MENU_INDEX_INVALID && menu->strings[idx] == NULL)
idx = MENU_INDEX_INVALID;
return idx;
}
/*
* Return the modes specified by the given menu command (eg :menu! returns
* MENU_CMDLINE_MODE | MENU_INSERT_MODE). If noremap is not NULL, then the
* flag it points to is set according to whether the command is a "nore"
* command. If unmenu is not NULL, then the flag it points to is set
* according to whether the command is an "unmenu" command.
*/
static int
gui_get_menu_cmd_modes(cmd, forceit, noremap, unmenu)
char_u *cmd;
int forceit; /* Was there a "!" after the command? */
int *noremap;
int *unmenu;
{
int modes;
switch (*cmd++)
{
case 'v': /* vmenu, vunmenu, vnoremenu */
modes = MENU_VISUAL_MODE;
break;
case 'o': /* omenu */
modes = MENU_OP_PENDING_MODE;
break;
case 'i': /* imenu */
modes = MENU_INSERT_MODE;
break;
case 't':
modes = MENU_TIP_MODE; /* tmenu */
break;
case 'c': /* cmenu */
modes = MENU_CMDLINE_MODE;
break;
case 'a': /* amenu */
modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE | MENU_NORMAL_MODE
| MENU_VISUAL_MODE | MENU_OP_PENDING_MODE;
break;
case 'n':
if (cmd[1] != 'o') /* nmenu */
{
modes = MENU_NORMAL_MODE;
break;
}
/* FALLTHROUGH */
default:
--cmd;
if (forceit) /* menu!! */
modes = MENU_INSERT_MODE | MENU_CMDLINE_MODE;
else /* menu */
modes = MENU_NORMAL_MODE | MENU_VISUAL_MODE
| MENU_OP_PENDING_MODE;
}
if (noremap != NULL)
*noremap = (*cmd == 'n');
if (unmenu != NULL)
*unmenu = (*cmd == 'u');
return modes;
}
#define MENUDEPTH 10 /* maximum depth of menus */
/*
* Do the :menu commands.
*/
void
do_menu(eap)
EXARG *eap; /* Ex command arguments */
{
char_u *menu_path;
int modes;
char_u *map_to;
int noremap;
int unmenu;
char_u *map_buf;
char_u *arg;
char_u *p;
int i;
int old_menu_height;
int pri_tab[MENUDEPTH + 1];
modes = gui_get_menu_cmd_modes(eap->cmd, eap->forceit, &noremap, &unmenu);
arg = eap->arg;
/* fill in the priority table */
for (p = arg; *p; ++p)
if (!isdigit(*p) && *p != '.')
break;
if (vim_iswhite(*p))
{
for (i = 0; i < MENUDEPTH && !vim_iswhite(*arg); ++i)
{
pri_tab[i] = getdigits(&arg);
if (pri_tab[i] == 0)
pri_tab[i] = 500;
if (*arg == '.')
++arg;
}
arg = skipwhite(arg);
}
else if (eap->addr_count)
{
pri_tab[0] = eap->line2;
i = 1;
}
else
i = 0;
while (i < MENUDEPTH)
pri_tab[i++] = 500;
pri_tab[MENUDEPTH] = -1; /* mark end of the table */
menu_path = arg;
if (*menu_path == NUL)
{
gui_show_menus(menu_path, modes);
return;
}
/* skip the menu name, and translate into a real TAB */
while (*arg && !vim_iswhite(*arg))
{
if ((*arg == '\\' || *arg == Ctrl('V')) && arg[1] != NUL)
arg++;
else if (STRNICMP(arg, "", 5) == 0)
{
*arg = TAB;
mch_memmove(arg + 1, arg + 5, STRLEN(arg + 4));
}
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;
}
old_menu_height = gui.menu_height;
if (unmenu)
{
if (STRCMP(menu_path, "*") == 0) /* meaning: remove all menus */
menu_path = (char_u *)"";
gui_remove_menu(&gui.root_menu, menu_path, modes, FALSE);
/*
* For the PopUp menu, remove a menu for each mode separately.
*/
if (STRNCMP(menu_path, "PopUp", 5) == 0)
{
for (i = 0; i < MENU_INDEX_TIP; ++i)
if (modes & (1 << i))
{
p = popup_mode_name(menu_path, i);
if (p != NULL)
{
gui_remove_menu(&gui.root_menu, p, 1 << i, TRUE);
vim_free(p);
}
}
}
}
else
{
/* Replace special key codes */
map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE);
gui_add_menu_path(menu_path, modes,
pri_tab, gui_menu_cb, map_to, noremap
#ifdef USE_GUI_WIN32
, TRUE
#endif
);
/*
* For the PopUp menu, add a menu for each mode separately.
*/
if (STRNCMP(menu_path, "PopUp", 5) == 0)
{
for (i = 0; i < MENU_INDEX_TIP; ++i)
if (modes & (1 << i))
{
p = popup_mode_name(menu_path, i);
if (p != NULL)
{
/* Include all modes, to make ":amenu" work */
gui_add_menu_path(p, modes,
pri_tab, gui_menu_cb, map_to, noremap
#ifdef USE_GUI_WIN32
, TRUE
#endif
);
vim_free(p);
}
}
}
vim_free(map_buf);
}
/* If the menubar height changed, resize the window */
if (gui.menu_height != old_menu_height)
gui_set_winsize(FALSE);
}
/*
* Modify a menu name starting with "PopUp" to include the mode character.
* Returns the name in allocated memory (NULL for failure).
*/
static char_u *
popup_mode_name(name, idx)
char_u *name;
int idx;
{
char_u *p;
int len = STRLEN(name);
p = vim_strnsave(name, len + 1);
if (p != NULL)
{
mch_memmove(p + 6, p + 5, len - 4);
p[5] = MENU_MODE_CHARS[idx];
}
return p;
}
/*
* Used recursively by gui_update_menus (see below)
*/
static void
gui_update_menus_recurse(menu, mode)
GuiMenu *menu;
int mode;
{
int i;
while (menu)
{
if (menu->modes & mode)
i = FALSE;
else
i = TRUE;
if (vim_strchr(p_guioptions, GO_GREY) != NULL)
gui_mch_menu_grey(menu, i);
else
gui_mch_menu_hidden(menu, i);
gui_update_menus_recurse(menu->children, mode);
menu = menu->next;
}
}
/*
* Make sure only the valid menu items appear for this mode. If
* force_menu_update is not TRUE, then we only do this if the mode has changed
* since last time. If "modes" is not 0, then we use these modes instead.
*/
void
gui_update_menus(modes)
int modes;
{
static int prev_mode = -1;
int mode = 0;
if (modes != 0x0)
mode = modes;
else
{
mode = get_menu_mode();
if (mode == MENU_INDEX_INVALID)
mode = 0;
else
mode = (1 << mode);
}
if (force_menu_update || mode != prev_mode)
{
gui_update_menus_recurse(gui.root_menu, mode);
gui_mch_draw_menubar();
prev_mode = mode;
force_menu_update = FALSE;
}
}
static int
get_menu_mode()
{
if (VIsual_active)
return MENU_INDEX_VISUAL;
if (State & INSERT)
return MENU_INDEX_INSERT;
if (State & CMDLINE)
return MENU_INDEX_CMDLINE;
if (finish_op)
return MENU_INDEX_OP_PENDING;
if (State & NORMAL)
return MENU_INDEX_NORMAL;
return MENU_INDEX_INVALID;
}
/*
* Check if a key is used as a mnemonic for a toplevel menu.
* Case of the key is ignored.
*/
int
gui_is_menu_shortcut(key)
int key;
{
GuiMenu *menu;
key = TO_LOWER(key);
for (menu = gui.root_menu; menu != NULL; menu = menu->next)
if (TO_LOWER(menu->mnemonic) == key)
return TRUE;
return FALSE;
}
#if defined(USE_GUI_WIN32) || defined(PROTO)
/*
* Deal with tearoff items that are added like a menu item.
* Currently only for Win32 GUI. Others may follow later.
*/
void
gui_mch_toggle_tearoffs(int enable)
{
int pri_tab[MENUDEPTH + 1];
int i;
if (enable)
{
for (i = 0; i < MENUDEPTH; ++i)
pri_tab[i] = 500;
pri_tab[MENUDEPTH] = -1;
gui_create_tearoffs_recurse(gui.root_menu, (char_u *)"", pri_tab, 0);
}
else
gui_destroy_tearoffs_recurse(gui.root_menu);
s_tearoffs = enable;
}
/*
* Recursively add tearoff items
*/
static void
gui_create_tearoffs_recurse(menu, pname, pri_tab, pri_idx)
GuiMenu *menu;
const char_u *pname;
int *pri_tab;
int pri_idx;
{
char_u *newpname = NULL;
int len;
char_u *s;
char_u *d;
if (pri_tab[pri_idx + 1] != -1)
++pri_idx;
while (menu != NULL)
{
if (menu->children != NULL && gui_menubar_menu(menu->name))
{
/* Add the menu name to the menu path. Insert a backslash before
* dots (it's used to separate menu names). */
len = STRLEN(pname) + STRLEN(menu->name);
for (s = menu->name; *s; ++s)
if (*s == '.' || *s == '\\')
++len;
newpname = alloc(len + TEAR_LEN + 2);
if (newpname != NULL)
{
STRCPY(newpname, pname);
d = newpname + STRLEN(newpname);
for (s = menu->name; *s; ++s)
{
if (*s == '.' || *s == '\\')
*d++ = '\\';
*d++ = *s;
}
*d = NUL;
/* check if tearoff already exists */
if (STRCMP(menu->children->name, TEAR_STRING) != 0)
{
gui_add_tearoff(newpname, pri_tab, pri_idx - 1);
*d = NUL; /* remove TEAR_STRING */
}
STRCAT(newpname, ".");
gui_create_tearoffs_recurse(menu->children, newpname,
pri_tab, pri_idx);
vim_free(newpname);
}
}
menu = menu->next;
}
}
/*
* Add tear-off menu item for a submenu.
* "tearpath" is the menu path, and must have room to add TEAR_STRING.
*/
static void
gui_add_tearoff(tearpath, pri_tab, pri_idx)
char_u *tearpath;
int *pri_tab;
int pri_idx;
{
char_u *tbuf;
int t;
tbuf = alloc(5 + STRLEN(tearpath));
if (tbuf != NULL)
{
tbuf[0] = K_SPECIAL;
tbuf[1] = K_SECOND(K_TEAROFF);
tbuf[2] = K_THIRD(K_TEAROFF);
STRCPY(tbuf + 3, tearpath);
STRCAT(tbuf + 3, "\r");
STRCAT(tearpath, ".");
STRCAT(tearpath, TEAR_STRING);
/* Priority of tear-off is always 1 */
t = pri_tab[pri_idx + 1];
pri_tab[pri_idx + 1] = 1;
gui_add_menu_path(tearpath, MENU_ALL_MODES, pri_tab,
gui_menu_cb, tbuf, TRUE, FALSE);
gui_add_menu_path(tearpath, MENU_TIP_MODE, pri_tab,
gui_menu_cb, (char_u *)"Tear off this menu", TRUE, FALSE);
pri_tab[pri_idx + 1] = t;
vim_free(tbuf);
}
}
/*
* Recursively destroy tearoff items
*/
static void
gui_destroy_tearoffs_recurse(menu)
GuiMenu *menu;
{
GuiMenu *oldmenu;
while (menu)
{
if (menu->children)
{
/* check if tearoff exists */
if ( STRCMP(menu->children->name, TEAR_STRING) == 0)
{
/* Disconnect the item */
oldmenu = menu->children;
menu->children = oldmenu->next;
/* Free the memory */
gui_free_menu(oldmenu);
}
if (menu->children != NULL) /* might have been last one */
gui_destroy_tearoffs_recurse(menu->children);
}
menu = menu->next;
}
}
#endif /*USE_GUI_WIN32*/
/*
* Add the menu with the given name to the menu hierarchy
*/
static int
gui_add_menu_path(menu_path, modes, pri_tab, call_back, call_data, noremap
#ifdef USE_GUI_WIN32
, addtearoff
#endif
)
char_u *menu_path;
int modes;
int *pri_tab;
void (*call_back)();
char_u *call_data;
int noremap;
#ifdef USE_GUI_WIN32
int addtearoff; /* may add tearoff item */
#endif
{
char_u *path_name;
GuiMenu **menup;
GuiMenu *menu = NULL;
GuiMenu *parent;
GuiMenu **lower_pri;
char_u *p;
char_u *name;
char_u *dname;
char_u *next_name;
int i;
int c;
int idx, new_idx;
int pri_idx = 0;
int old_modes = 0;
int amenu;
/* Make a copy so we can stuff around with it, since it could be const */
path_name = vim_strsave(menu_path);
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, and the simplified
* name (without mnemonic and accelerator text). */
next_name = gui_menu_name_skip(name);
dname = gui_menu_text(name, NULL, NULL);
/* See if it's already there */
lower_pri = menup;
idx = 0;
new_idx = 0;
menu = *menup;
while (menu != NULL)
{
if (menu_name_equal(name, menu) || menu_name_equal(dname, menu))
{
if (*next_name == NUL && menu->children != NULL)
{
if (!gui_sys_menu)
EMSG("Menu path must not lead to a sub-menu");
vim_free(path_name);
vim_free(dname);
return FAIL;
}
if (*next_name != NUL && menu->children == NULL
#ifdef USE_GUI_WIN32
&& addtearoff
#endif
)
{
if (!gui_sys_menu)
EMSG("Part of menu-item path is not sub-menu");
vim_free(path_name);
vim_free(dname);
return FAIL;
}
break;
}
menup = &menu->next;
/* Count menus, to find where this one needs to be inserted.
* Ignore menus that are not in the menubar (PopUp and Toolbar) */
if (parent != NULL || gui_menubar_menu(menu->name))
{
++idx;
if (menu->priority <= pri_tab[pri_idx])
{
lower_pri = menup;
new_idx = idx;
}
}
menu = menu->next;
}
if (menu == NULL)
{
if (*next_name == NUL && parent == NULL)
{
EMSG("Must not add menu items directly to menu bar");
vim_free(path_name);
vim_free(dname);
return FAIL;
}
/* Not already there, so lets add it */
menu = (GuiMenu *)alloc_clear(sizeof(GuiMenu));
if (menu == NULL)
{
vim_free(path_name);
vim_free(dname);
return FAIL;
}
menu->modes = modes;
menu->name = vim_strsave(name);
/* separate mnemonic and accelerator text from actual menu name */
menu->dname = gui_menu_text(name, &menu->mnemonic, &menu->actext);
menu->priority = pri_tab[pri_idx];
/*
* Add after menu that has lower priority.
*/
menu->next = *lower_pri;
*lower_pri = menu;
if (gui.in_use) /* Otherwise it will be added when GUI starts */
{
if (*next_name == NUL)
{
/* Real menu item, not sub-menu */
gui_mch_add_menu_item(menu, parent, new_idx);
/* 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, new_idx);
}
}
#ifdef USE_GUI_WIN32
/* When adding a new submenu, may add a tearoff item */
if ( addtearoff
&& *next_name
&& vim_strchr(p_guioptions, GO_TEAROFF) != NULL
&& gui_menubar_menu(name))
{
char_u *tearpath;
/*
* The pointers next_name & path_name refer to a string with
* \'s and ^V's stripped out. But menu_path is a "raw"
* string, so we must correct for special characters.
*/
tearpath = alloc(STRLEN(menu_path) + TEAR_LEN + 2);
if (tearpath != NULL)
{
char_u *s;
int idx;
STRCPY(tearpath, menu_path);
idx = next_name - path_name - 1;
for (s = tearpath; *s && s < tearpath + idx; ++s)
if ((*s == '\\' || *s == Ctrl('V')) && s[1])
{
++idx;
++s;
}
tearpath[idx] = NUL;
gui_add_tearoff(tearpath, pri_tab, pri_idx);
vim_free(tearpath);
}
}
#endif
old_modes = 0;
}
else
{
old_modes = menu->modes;
/*
* If this menu option was previously only available in other
* modes, then make sure it's available for this one now
*/
#ifdef USE_GUI_WIN32
/* If adding a tearbar (addtearoff == FALSE) don't update modes */
if (addtearoff)
#endif
menu->modes |= modes;
}
menup = &menu->children;
parent = menu;
name = next_name;
vim_free(dname);
if (pri_tab[pri_idx + 1] != -1)
++pri_idx;
}
vim_free(path_name);
/*
* Only add system menu items which have not been defined yet.
* First check if this was an ":amenu".
*/
amenu = ((modes & (MENU_NORMAL_MODE | MENU_INSERT_MODE)) ==
(MENU_NORMAL_MODE | MENU_INSERT_MODE));
if (gui_sys_menu)
modes &= ~old_modes;
if (menu != NULL && modes)
{
menu->cb = call_back;
p = (call_data == NULL) ? NULL : vim_strsave(call_data);
/* loop over all modes, may add more than one */
for (i = 0; i < MENU_MODES; ++i)
{
if (modes & (1 << i))
{
/* free any old menu */
gui_free_menu_string(menu, i);
/* For "amenu", may insert an extra character */
/* Don't do this if adding a tearbar (addtearoff == FALSE) */
c = 0;
if (amenu
#ifdef USE_GUI_WIN32
&& addtearoff
#endif
)
{
switch (1 << i)
{
case MENU_VISUAL_MODE:
case MENU_OP_PENDING_MODE:
case MENU_CMDLINE_MODE:
c = Ctrl('C');
break;
case MENU_INSERT_MODE:
c = Ctrl('O');
break;
}
}
if (c)
{
menu->strings[i] = alloc((unsigned)(STRLEN(call_data) + 2));
if (menu->strings[i] != NULL)
{
menu->strings[i][0] = c;
STRCPY(menu->strings[i] + 1, call_data);
}
}
else
menu->strings[i] = p;
menu->noremap[i] = 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, silent)
GuiMenu **menup;
char_u *name;
int modes;
int silent; /* don't give error messages */
{
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 || menu_name_equal(name, menu))
{
if (*p != NUL && menu->children == NULL)
{
if (!silent)
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, silent) == FAIL)
return FAIL;
}
else if (*name != NUL)
{
if (!silent)
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 (modes & MENU_TIP_MODE)
gui_free_menu_string(menu, MENU_INDEX_TIP);
if ((menu->modes & MENU_ALL_MODES) == 0)
{
/* 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)
{
if (!silent)
EMSG("No menu of that name");
return FAIL;
}
/* Recalculate modes for menu based on the new updated children */
menu->modes &= ~modes;
#ifdef USE_GUI_WIN32
if ((s_tearoffs) && (menu->children != NULL)) /* there's a tear bar.. */
child = menu->children->next; /* don't count tearoff bar */
else
#endif
child = menu->children;
for ( ; child != NULL; child = child->next)
menu->modes |= child->modes;
if (modes & MENU_TIP_MODE)
gui_free_menu_string(menu, MENU_INDEX_TIP);
if ((menu->modes & MENU_ALL_MODES) == 0)
{
/* The menu item is no longer valid in ANY mode, so delete it */
#ifdef USE_GUI_WIN32
if ((s_tearoffs) && (menu->children != NULL)) /* there's a tear bar.. */
gui_free_menu(menu->children);
#endif
*menup = menu->next;
gui_free_menu(menu);
}
}
return OK;
}
/*
* Free the given menu structure
*/
static void
gui_free_menu(menu)
GuiMenu *menu;
{
int i;
/* Free machine specific menu structures (only when already created) */
if (gui.in_use)
gui_mch_destroy_menu(menu);
vim_free(menu->name);
vim_free(menu->dname);
vim_free(menu->actext);
for (i = 0; i < MENU_MODES; 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 < MENU_MODES; i++)
if (menu->strings[i] == menu->strings[idx])
count++;
if (count == 1)
vim_free(menu->strings[idx]);
menu->strings[idx] = NULL;
}
/*
* Show the mapping associated with a menu item or hierarchy in a sub-menu.
*/
static int
gui_show_menus(path_name, modes)
char_u *path_name;
int modes;
{
char_u *p;
char_u *name;
GuiMenu *menu;
GuiMenu *parent = NULL;
menu = gui.root_menu;
name = path_name = vim_strsave(path_name);
if (path_name == NULL)
return FAIL;
/* First, find the (sub)menu with the given name */
while (*name)
{
p = gui_menu_name_skip(name);
while (menu != NULL)
{
if (menu_name_equal(name, menu))
{
/* Found menu */
if (*p != NUL && menu->children == NULL)
{
EMSG("Part of menu-item path is not sub-menu");
vim_free(path_name);
return FAIL;
}
else if ((menu->modes & modes) == 0x0)
{
EMSG("Menu only exists in another mode");
vim_free(path_name);
return FAIL;
}
break;
}
menu = menu->next;
}
if (menu == NULL)
{
EMSG("No menu of that name");
vim_free(path_name);
return FAIL;
}
name = p;
parent = menu;
menu = menu->children;
}
/* Now we have found the matching menu, and we list the mappings */
/* Highlight title */
MSG_PUTS_TITLE("\n--- Menus ---");
gui_show_menus_recursive(parent, modes, 0);
return OK;
}
/*
* Recursively show the mappings associated with the menus under the given one
*/
static void
gui_show_menus_recursive(menu, modes, depth)
GuiMenu *menu;
int modes;
int depth;
{
int i;
int bit;
if (menu != NULL && (menu->modes & modes) == 0x0)
return;
if (menu != NULL)
{
msg_putchar('\n');
if (got_int) /* "q" hit for "--more--" */
return;
for (i = 0; i < depth; i++)
MSG_PUTS(" ");
if (menu->priority)
{
msg_outnum((long)menu->priority);
MSG_PUTS(" ");
}
/* Same highlighting as for directories!? */
msg_puts_attr(menu->name, hl_attr(HLF_D));
}
if (menu != NULL && menu->children == NULL)
{
for (bit = 0; bit < MENU_MODES; bit++)
if ((menu->modes & modes & (1 << bit)) != 0)
{
msg_putchar('\n');
if (got_int) /* "q" hit for "--more--" */
return;
for (i = 0; i < depth + 2; i++)
MSG_PUTS(" ");
msg_putchar(MENU_MODE_CHARS[bit]);
if (menu->noremap[bit])
msg_putchar('*');
else
msg_putchar(' ');
MSG_PUTS(" ");
msg_outtrans_special(menu->strings[bit], TRUE);
}
}
else
{
if (menu == NULL)
{
menu = gui.root_menu;
depth--;
}
else
menu = menu->children;
/* recursively show all children. Skip PopUp[nvoci]. */
for (; menu != NULL && !got_int; menu = menu->next)
if (!gui_popup_menu(menu->dname) || menu->dname[5] == NUL)
gui_show_menus_recursive(menu, modes, depth + 1);
}
}
/*
* Used when expanding menu names.
*/
static GuiMenu *expand_menu = NULL;
static int expand_modes = 0x0;
/*
* Work out what to complete when doing command line completion of menu names.
*/
char_u *
gui_set_context_in_menu_cmd(cmd, arg, forceit)
char_u *cmd;
char_u *arg;
int forceit;
{
char_u *after_dot;
char_u *p;
char_u *path_name = NULL;
char_u *name;
int unmenu;
GuiMenu *menu;
expand_context = EXPAND_UNSUCCESSFUL;
after_dot = arg;
for (p = arg; *p && !vim_iswhite(*p); ++p)
{
if ((*p == '\\' || *p == Ctrl('V')) && p[1] != NUL)
p++;
else if (*p == '.')
after_dot = p + 1;
}
if (*p == NUL) /* Complete the menu name */
{
/*
* With :unmenu, you only want to match menus for the appropriate mode.
* With :menu though you might want to add a menu with the same name as
* one in another mode, so match menus fom other modes too.
*/
expand_modes = gui_get_menu_cmd_modes(cmd, forceit, NULL, &unmenu);
if (!unmenu)
expand_modes = MENU_ALL_MODES;
menu = gui.root_menu;
if (after_dot != arg)
{
path_name = alloc(after_dot - arg);
if (path_name == NULL)
return NULL;
STRNCPY(path_name, arg, after_dot - arg - 1);
path_name[after_dot - arg - 1] = NUL;
}
name = path_name;
while (name != NULL && *name)
{
p = gui_menu_name_skip(name);
while (menu != NULL)
{
if (menu_name_equal(name, menu))
{
/* 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;
}
/*
* Function given to ExpandGeneric() to obtain the list of group names.
*/
char_u *
get_menu_name(idx)
int idx;
{
static GuiMenu *menu = NULL;
static int get_dname = FALSE; /* return menu->dname next time */
char_u *str;
if (idx == 0) /* first call: start at first item */
{
menu = expand_menu;
get_dname = FALSE;
}
/* Skip PopUp[nvoci]. */
while (menu != NULL && gui_popup_menu(menu->dname) && menu->dname[5])
menu = menu->next;
if (menu == NULL) /* at end of linked list */
return NULL;
if (menu->modes & expand_modes)
{
if (get_dname)
{
str = menu->dname;
get_dname = FALSE;
}
else
{
str = menu->name;
if (STRCMP(menu->name, menu->dname))
get_dname = TRUE;
}
}
else
{
str = (char_u *)"";
get_dname = FALSE;
}
/* Advance to next menu entry. */
if (!get_dname)
menu = menu->next;
return str;
}
/*
* 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.
*/
char_u *
gui_menu_name_skip(name)
char_u *name;
{
char_u *p;
for (p = name; *p && *p != '.'; p++)
if (*p == '\\' || *p == Ctrl('V'))
{
mch_memmove(p, p + 1, STRLEN(p));
if (*p == NUL)
break;
}
if (*p)
*p++ = NUL;
return p;
}
/*
* Return TRUE when "name" matches with menu "menu". The name is compared in
* two ways: raw menu name and menu name without '&'. ignore part after a TAB.
*/
static int
menu_name_equal(name, menu)
char_u *name;
GuiMenu *menu;
{
return (menu_namecmp(name, menu->name) || menu_namecmp(name, menu->dname));
}
static int
menu_namecmp(name, mname)
char_u *name;
char_u *mname;
{
int i;
for (i = 0; name[i] != NUL && name[i] != TAB; ++i)
if (name[i] != mname[i])
break;
return ((name[i] == NUL || name[i] == TAB)
&& (mname[i] == NUL || mname[i] == TAB));
}
/*
* 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;
{
int idx = 0;
while (menu)
{
if (menu->children != NULL)
{
gui_mch_add_menu(menu, parent, idx);
gui_create_initial_menus(menu->children, menu);
}
else
gui_mch_add_menu_item(menu, parent, idx);
menu = menu->next;
++idx;
}
}
/*
* Set which components are present.
* If "oldval" is not NULL, "oldval" is the previous value, the new * value is
* in p_guioptions.
*/
void
gui_init_which_components(oldval)
char_u *oldval;
{
static int prev_which_scrollbars[3] = {-1, -1, -1};
static int prev_menu_is_active = -1;
#ifdef USE_GUI_WIN32_TOOLBAR
static int prev_toolbar = -1;
int using_toolbar = FALSE;
#endif
static int prev_tearoff = -1;
int using_tearoff = FALSE;
char_u *p;
int i;
int grey_old, grey_new;
char_u *temp;
WIN *wp;
int need_winsize;
if (oldval != NULL && gui.in_use)
{
/*
* Check if the menu's go from grey to non-grey or vise versa.
*/
grey_old = (vim_strchr(oldval, GO_GREY) != NULL);
grey_new = (vim_strchr(p_guioptions, GO_GREY) != NULL);
if (grey_old != grey_new)
{
temp = p_guioptions;
p_guioptions = oldval;
gui_update_menus(MENU_ALL_MODES);
p_guioptions = temp;
}
}
gui.menu_is_active = FALSE;
for (i = 0; i < 3; i++)
gui.which_scrollbars[i] = FALSE;
for (p = p_guioptions; *p; p++)
switch (*p)
{
case GO_LEFT:
gui.which_scrollbars[SBAR_LEFT] = TRUE;
break;
case GO_RIGHT:
gui.which_scrollbars[SBAR_RIGHT] = TRUE;
break;
case GO_BOT:
gui.which_scrollbars[SBAR_BOTTOM] = TRUE;
break;
case GO_MENUS:
gui.menu_is_active = TRUE;
break;
case GO_GREY:
/* make menu's have grey items, ignored here */
break;
#ifdef USE_GUI_WIN32_TOOLBAR
case GO_TOOLBAR:
using_toolbar = TRUE;
break;
#endif
case GO_TEAROFF:
using_tearoff = TRUE;
break;
default:
/* Should give error message for internal error */
break;
}
if (gui.in_use)
{
need_winsize = FALSE;
for (i = 0; i < 3; i++)
{
if (gui.which_scrollbars[i] != prev_which_scrollbars[i])
{
if (i == SBAR_BOTTOM)
{
gui_mch_enable_scrollbar(&gui.bottom_sbar,
gui.which_scrollbars[i]);
}
else
{
for (wp = firstwin; wp != NULL; wp = wp->w_next)
gui_mch_enable_scrollbar(&wp->w_scrollbars[i],
gui.which_scrollbars[i]);
}
need_winsize = TRUE;
}
prev_which_scrollbars[i] = gui.which_scrollbars[i];
}
if (gui.menu_is_active != prev_menu_is_active)
{
gui_mch_enable_menu(gui.menu_is_active);
prev_menu_is_active = gui.menu_is_active;
need_winsize = TRUE;
}
#ifdef USE_GUI_WIN32_TOOLBAR
if (using_toolbar != prev_toolbar)
{
gui_mch_show_toolbar(using_toolbar);
prev_toolbar = using_toolbar;
need_winsize = TRUE;
}
#endif
if (using_tearoff != prev_tearoff)
{
gui_mch_toggle_tearoffs(using_tearoff);
prev_tearoff = using_tearoff;
}
if (need_winsize)
gui_set_winsize(FALSE);
}
}
/*
* Scrollbar stuff:
*/
void
gui_create_scrollbar(sb, wp)
GuiScrollbar *sb;
WIN *wp;
{
static int sbar_ident = 0;
int which;
sb->ident = sbar_ident++; /* No check for too big, but would it happen? */
sb->wp = wp;
sb->value = -1;
sb->pixval = -1;
sb->size = -1;
sb->max = -1;
sb->top = -1;
sb->height = -1;
sb->status_height = -1;
gui_mch_create_scrollbar(sb, (wp == NULL) ? SBAR_HORIZ : SBAR_VERT);
if (wp != NULL)
{
which = (sb == &wp->w_scrollbars[SBAR_LEFT]) ? SBAR_LEFT : SBAR_RIGHT;
gui_mch_enable_scrollbar(sb, gui.which_scrollbars[which]);
}
}
/*
* Find the scrollbar with the given index.
*/
GuiScrollbar *
gui_find_scrollbar(ident)
long ident;
{
WIN *wp;
if (gui.bottom_sbar.ident == ident)
return &gui.bottom_sbar;
for (wp = firstwin; wp != NULL; wp = wp->w_next)
{
if (wp->w_scrollbars[SBAR_LEFT].ident == ident)
return &wp->w_scrollbars[SBAR_LEFT];
if (wp->w_scrollbars[SBAR_RIGHT].ident == ident)
return &wp->w_scrollbars[SBAR_RIGHT];
}
return NULL;
}
void
gui_drag_scrollbar(sb, value, still_dragging)
GuiScrollbar *sb;
long value;
int still_dragging;
{
char_u bytes[4 + sizeof(long_u)];
WIN *wp;
int sb_num;
int byte_count;
if (sb == NULL)
return;
/*
* Ignore the scrollbars while executing an external command.
*/
if (!termcap_active)
return;
if (still_dragging)
{
if (sb->wp == NULL)
gui.dragged_sb = SBAR_BOTTOM;
else if (sb == &sb->wp->w_scrollbars[SBAR_LEFT])
gui.dragged_sb = SBAR_LEFT;
else
gui.dragged_sb = SBAR_RIGHT;
gui.dragged_wp = sb->wp;
}
else
gui.dragged_sb = SBAR_NONE;
if (sb->wp != NULL)
{
/* Vertical sbar info is kept in the first sbar (the left one) */
sb = &sb->wp->w_scrollbars[0];
}
/*
* Check validity of value
*/
if (value < 0)
value = 0;
#ifdef SCROLL_PAST_END
else if (value > sb->max)
value = sb->max;
#else
if (value > sb->max - sb->size + 1)
value = sb->max - sb->size + 1;
#endif
sb->value = value;
#ifdef RIGHTLEFT
if (sb->wp == NULL && curwin->w_p_rl)
{
value = sb->max + 1 - sb->size - value;
if (value < 0)
value = 0;
}
#endif
if (sb->wp != NULL)
{
sb_num = 0;
for (wp = firstwin; wp != sb->wp && wp != NULL; wp = wp->w_next)
sb_num++;
if (wp == NULL)
return;
bytes[0] = CSI;
bytes[1] = KS_SCROLLBAR;
bytes[2] = K_FILLER;
bytes[3] = (char_u)sb_num;
byte_count = 4;
}
else
{
bytes[0] = CSI;
bytes[1] = KS_HORIZ_SCROLLBAR;
bytes[2] = K_FILLER;
byte_count = 3;
}
add_long_to_buf((long)value, bytes + byte_count);
add_to_input_buf(bytes, byte_count + sizeof(long_u));
}
/*
* Scrollbar stuff:
*/
static void
gui_update_scrollbars(force)
int force; /* Force all scrollbars to get updated */
{
WIN *wp;
GuiScrollbar *sb;
int val, size, max;
int which_sb;
int h, y;
/* Update the horizontal scrollbar */
gui_update_horiz_scrollbar(force);
/* Return straight away if there is neither a left nor right scrollbar */
if (!gui.which_scrollbars[SBAR_LEFT] && !gui.which_scrollbars[SBAR_RIGHT])
return;
/*
* Don't want to update a scrollbar while we're dragging it. But if we
* have both a left and right scrollbar, and we drag one of them, we still
* need to update the other one.
*/
if ( (gui.dragged_sb == SBAR_LEFT
|| gui.dragged_sb == SBAR_RIGHT)
&& (!gui.which_scrollbars[SBAR_LEFT]
|| !gui.which_scrollbars[SBAR_RIGHT])
&& !force)
return;
if (!force && (gui.dragged_sb == SBAR_LEFT || gui.dragged_sb == SBAR_RIGHT))
{
/*
* If we have two scrollbars and one of them is being dragged, just
* copy the scrollbar position from the dragged one to the other one.
*/
which_sb = SBAR_LEFT + SBAR_RIGHT - gui.dragged_sb;
if (gui.dragged_wp != NULL)
gui_mch_set_scrollbar_thumb(
&gui.dragged_wp->w_scrollbars[which_sb],
gui.dragged_wp->w_scrollbars[0].value,
gui.dragged_wp->w_scrollbars[0].size,
gui.dragged_wp->w_scrollbars[0].max);
return;
}
for (wp = firstwin; wp; wp = wp->w_next)
{
if (wp->w_buffer == NULL) /* just in case */
continue;
#ifdef SCROLL_PAST_END
max = wp->w_buffer->b_ml.ml_line_count - 1;
#else
max = wp->w_buffer->b_ml.ml_line_count + wp->w_height - 2;
#endif
if (max < 0) /* empty buffer */
max = 0;
val = wp->w_topline - 1;
size = wp->w_height;
#ifdef SCROLL_PAST_END
if (val > max) /* just in case */
val = max;
#else
if (size > max + 1) /* just in case */
size = max + 1;
if (val > max - size + 1)
val = max - size + 1;
#endif
if (val < 0) /* minimal value is 0 */
val = 0;
/*
* Scrollbar at index 0 (the left one) contains all the information.
* It would be the same info for left and right so we just store it for
* one of them.
*/
sb = &wp->w_scrollbars[0];
/*
* Note: no check for valid w_botline. If it's not valid the
* scrollbars will be updated later anyway.
*/
if (size < 1 || wp->w_botline - 2 > max)
{
/*
* This can happen during changing files. Just don't update the
* scrollbar for now.
*/
sb->height = 0; /* Force update next time */
if (gui.which_scrollbars[SBAR_LEFT])
gui_mch_enable_scrollbar(&wp->w_scrollbars[SBAR_LEFT], FALSE);
if (gui.which_scrollbars[SBAR_RIGHT])
gui_mch_enable_scrollbar(&wp->w_scrollbars[SBAR_RIGHT], FALSE);
continue;
}
if (force || sb->height != wp->w_height
|| sb->top != wp->w_winpos
|| sb->status_height != wp->w_status_height)
{
/* Height or position of scrollbar has changed */
sb->top = wp->w_winpos;
sb->height = wp->w_height;
sb->status_height = wp->w_status_height;
/* Calculate height and position in pixels */
h = (sb->height + sb->status_height) * gui.char_height;
y = sb->top * gui.char_height + gui.border_offset;
if (gui.menu_is_active)
y += gui.menu_height;
#ifdef USE_GUI_WIN32
if (vim_strchr(p_guioptions, GO_TOOLBAR) != NULL)
y += (TOOLBAR_BUTTON_HEIGHT+12);
#endif
if (wp == firstwin)
{
/* Height of top scrollbar includes width of top border */
h += gui.border_offset;
y -= gui.border_offset;
}
if (gui.which_scrollbars[SBAR_LEFT])
{
gui_mch_set_scrollbar_pos(&wp->w_scrollbars[SBAR_LEFT],
gui.left_sbar_x, y,
gui.scrollbar_width, h);
gui_mch_enable_scrollbar(&wp->w_scrollbars[SBAR_LEFT], TRUE);
}
if (gui.which_scrollbars[SBAR_RIGHT])
{
gui_mch_set_scrollbar_pos(&wp->w_scrollbars[SBAR_RIGHT],
gui.right_sbar_x, y,
gui.scrollbar_width, h);
gui_mch_enable_scrollbar(&wp->w_scrollbars[SBAR_RIGHT], TRUE);
}
}
/* reduce the number of calls to gui_mch_set_scrollbar_thumb() by
* checking if the thumb moved at least a pixel */
if (max == 0)
y = 0;
else
y = (val * (sb->height + 2) * gui.char_height + max / 2) / max;
if (force
#ifdef RISCOS
/* RISCOS does scrollbars differently - use the old check */
|| sb->pixval != val
#else
|| sb->pixval != y
#endif
|| sb->size != size || sb->max != max)
{
/* Thumb of scrollbar has moved */
sb->value = val;
sb->pixval = y;
sb->size = size;
sb->max = max;
if (gui.which_scrollbars[SBAR_LEFT] && gui.dragged_sb != SBAR_LEFT)
gui_mch_set_scrollbar_thumb(&wp->w_scrollbars[SBAR_LEFT],
val, size, max);
if (gui.which_scrollbars[SBAR_RIGHT]
&& gui.dragged_sb != SBAR_RIGHT)
gui_mch_set_scrollbar_thumb(&wp->w_scrollbars[SBAR_RIGHT],
val, size, max);
}
}
}
/*
* Scroll a window according to the values set in the globals current_scrollbar
* and scrollbar_value. Return TRUE if the cursor in the current window moved
* or FALSE otherwise.
*/
int
gui_do_scroll()
{
WIN *wp, *old_wp;
int i;
long nlines;
FPOS old_cursor;
linenr_t old_topline;
for (wp = firstwin, i = 0; i < current_scrollbar; i++)
{
if (wp == NULL)
break;
wp = wp->w_next;
}
if (wp == NULL)
{
/* Couldn't find window */
return FALSE;
}
/*
* Compute number of lines to scroll. If zero, nothing to do.
*/
nlines = (long)scrollbar_value + 1 - (long)wp->w_topline;
if (nlines == 0)
return FALSE;
old_cursor = curwin->w_cursor;
old_wp = curwin;
old_topline = wp->w_topline;
curwin = wp;
curbuf = wp->w_buffer;
if (nlines < 0)
scrolldown(-nlines);
else
scrollup(nlines);
if (old_topline != wp->w_topline)
{
if (p_so)
{
cursor_correct(); /* fix window for 'so' */
update_topline(); /* avoid up/down jump */
}
coladvance(curwin->w_curswant);
}
curwin = old_wp;
curbuf = old_wp->w_buffer;
/*
* Don't call updateWindow() when nothing has changed (it will overwrite
* the status line!).
*/
if (old_topline != wp->w_topline)
{
wp->w_redr_type = VALID;
updateWindow(wp); /* update window, status line, and cmdline */
}
return !equal(curwin->w_cursor, old_cursor);
}
/*
* Horizontal scrollbar stuff:
*/
static void
gui_update_horiz_scrollbar(force)
int force;
{
int value, size, max;
char_u *p;
if (!gui.which_scrollbars[SBAR_BOTTOM])
return;
if (!force && gui.dragged_sb == SBAR_BOTTOM)
return;
if (!force && curwin->w_p_wrap && gui.prev_wrap)
return;
/*
* It is possible for the cursor to be invalid if we're in the middle of
* something (like changing files). If so, don't do anything for now.
*/
if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
{
gui.bottom_sbar.value = -1;
return;
}
size = Columns;
if (curwin->w_p_wrap)
{
value = 0;
#ifdef SCROLL_PAST_END
max = 0;
#else
max = Columns - 1;
#endif
}
else
{
value = curwin->w_leftcol;
/* Calculate max for horizontal scrollbar */
p = ml_get_curline();
max = 0;
if (p != NULL && p[0] != NUL)
while (p[1] != NUL) /* Don't count last character */
max += chartabsize(*p++, (colnr_t)max);
#ifndef SCROLL_PAST_END
max += Columns - 1;
#endif
}
#ifndef SCROLL_PAST_END
if (value > max - size + 1)
value = max - size + 1; /* limit the value to allowable range */
#endif
#ifdef RIGHTLEFT
if (curwin->w_p_rl)
{
value = max + 1 - size - value;
if (value < 0)
{
size += value;
value = 0;
}
}
#endif
if (!force && value == gui.bottom_sbar.value && size == gui.bottom_sbar.size
&& max == gui.bottom_sbar.max)
return;
gui.bottom_sbar.value = value;
gui.bottom_sbar.size = size;
gui.bottom_sbar.max = max;
gui.prev_wrap = curwin->w_p_wrap;
gui_mch_set_scrollbar_thumb(&gui.bottom_sbar, value, size, max);
}
/*
* Do a horizontal scroll. Return TRUE if the cursor moved, FALSE otherwise.
*/
int
gui_do_horiz_scroll()
{
/* no wrapping, no scrolling */
if (curwin->w_p_wrap)
return FALSE;
if (curwin->w_leftcol == scrollbar_value)
return FALSE;
curwin->w_leftcol = scrollbar_value;
return leftcol_changed();
}
/*
* Check that none of the colors are the same as the background color
*/
void
gui_check_colors()
{
if (gui.norm_pixel == gui.back_pixel || gui.norm_pixel == (GuiColor)-1)
{
gui_set_bg_color((char_u *)"White");
if (gui.norm_pixel == gui.back_pixel || gui.norm_pixel == (GuiColor)-1)
gui_set_fg_color((char_u *)"Black");
}
}
void
gui_set_fg_color(name)
char_u *name;
{
gui.norm_pixel = gui_mch_get_color(name);
hl_set_fg_color_name(vim_strsave(name));
}
void
gui_set_bg_color(name)
char_u *name;
{
gui.back_pixel = gui_mch_get_color(name);
hl_set_bg_color_name(vim_strsave(name));
}
#ifdef USE_GUI_X11
void
gui_new_scrollbar_colors()
{
WIN *wp;
for (wp = firstwin; wp != NULL; wp = wp->w_next)
{
gui_mch_set_scrollbar_colors(&(wp->w_scrollbars[SBAR_LEFT]));
gui_mch_set_scrollbar_colors(&(wp->w_scrollbars[SBAR_RIGHT]));
}
gui_mch_set_scrollbar_colors(&gui.bottom_sbar);
}
#endif
/*
* Call this when focus has changed.
*/
void
gui_focus_change(in_focus)
int in_focus;
{
gui.in_focus = in_focus;
out_flush(); /* make sure output has been written */
gui_update_cursor(TRUE, FALSE);
}
/*
* Duplicate the menu item text and then process to see if a mnemonic key
* and/or accelerator text has been identified.
* Returns a pointer to allocated memory, or NULL for failure.
* If mnemonic != NULL, *mnemonic is set to the character after the first '&'.
* If actext != NULL, *actext is set to the text after the first TAB.
*/
char_u *
gui_menu_text(text, mnemonic, actext)
char_u *text;
int *mnemonic;
char_u **actext;
{
char_u *p;
char_u *menu_text;
/* Locate accelerator text, after the first TAB */
p = vim_strchr(text, TAB);
if (p != NULL)
{
if (actext != NULL)
*actext = vim_strsave(p + 1);
menu_text = vim_strnsave(text, (int)(p - text));
}
else
menu_text = vim_strsave(text);
if (menu_text != NULL)
{
p = vim_strchr(menu_text, '&');
if (p != NULL)
{
if (mnemonic != NULL)
*mnemonic = p[1];
mch_memmove(p, p + 1, STRLEN(p));
}
}
return menu_text;
}
/*
* Return TRUE if "name" can be a menu in the MenuBar.
*/
int
gui_menubar_menu(name)
char_u *name;
{
return (!gui_popup_menu(name)
&& !gui_toolbar_menu(name)
&& *name != MNU_HIDDEN_CHAR);
}
int
gui_popup_menu(name)
char_u *name;
{
return (STRNCMP(name, "PopUp", 5) == 0);
}
int
gui_toolbar_menu(name)
char_u *name;
{
return (STRNCMP(name, "ToolBar", 7) == 0);
}
/*
* Called when the mouse moved (but not when dragging).
* Ignore this while in command-line mode, or while changing the window layout
* (causes a mouse-move event in Win32 GUI).
*/
void
gui_mouse_moved(y)
int y;
{
WIN *wp;
char_u st[6];
int x;
if (p_mousef && State != CMDLINE && !need_mouse_correct && gui.in_focus)
{
x = gui_mch_get_mouse_x();
/* Don't move the mouse when it's left or right of the Vim window */
if (x < 0 || x > Columns * gui.char_width)
return;
wp = y2win(y);
if (wp == curwin || wp == NULL)
return; /* still in the same old window, or none at all */
/*
* format a mouse click on status line input
* ala gui_send_mouse_event(0, x, y, 0, 0);
*/
st[0] = CSI;
st[1] = KS_MOUSE;
st[2] = K_FILLER;
st[3] = (char_u)MOUSE_LEFT;
st[4] = (char_u)(2 + ' ');
st[5] = (char_u)(wp->w_height + wp->w_winpos + ' ' + 1);
add_to_input_buf(st, 6);
}
}
/*
* Called when mouse should be moved to window with focus.
*/
void
gui_mouse_correct()
{
int x, y;
WIN *wp = NULL;
need_mouse_correct = FALSE;
if (gui.in_use && p_mousef)
{
x = gui_mch_get_mouse_x();
/* Don't move the mouse when it's left or right of the Vim window */
if (x < 0 || x > Columns * gui.char_width)
return;
y = gui_mch_get_mouse_y();
if (y >= 0)
wp = y2win(y);
if (wp != curwin && wp != NULL) /* If in other than current window */
{
validate_cline_row();
gui_mch_setmouse((int)Columns * gui.char_width - 3,
(curwin->w_winpos + curwin->w_wrow) * gui.char_height
+ (gui.char_height) / 2);
}
}
}
/*
* Find window where the mouse pointer "y" coordinate is in.
*/
static WIN *
y2win(y)
int y;
{
int row;
WIN *wp;
row = Y_2_ROW(y);
if (row < firstwin->w_winpos) /* before first window (Toolbar) */
return NULL;
for (wp = firstwin; wp != NULL; wp = wp->w_next)
if (row < wp->w_winpos + wp->w_height + wp->w_status_height)
break;
return wp;
}
/*
* Display the Special "PopUp" menu as a pop-up at the current mouse
* position. The "PopUpn" menu is for Normal mode, "PopUpi" for Insert mode,
* etc.
*/
void
gui_show_popupmenu()
{
GuiMenu *menu;
int mode;
mode = get_menu_mode();
if (mode == MENU_INDEX_INVALID)
return;
mode = MENU_MODE_CHARS[mode];
for (menu = gui.root_menu; menu != NULL; menu = menu->next)
if (STRNCMP("PopUp", menu->name, 5) == 0 && menu->name[5] == mode)
break;
/* Only show a popup when it is defined and has entries */
if (menu != NULL && menu->children != NULL)
gui_mch_show_popupmenu(menu);
}