mirror of
https://github.com/vim/vim
synced 2025-03-16 06:47:52 +01:00
Problem: GTK3: using wrong style for pre-edit area Solution: remove the widget name, adjust css (lilydjwg) closes: #13972 Signed-off-by: lilydjwg <lilydjwg@gmail.com> Signed-off-by: Christian Brabandt <cb@256bit.org>
1841 lines
46 KiB
C
1841 lines
46 KiB
C
/* vi:set ts=8 sts=4 sw=4 noet:
|
|
*
|
|
* VIM - Vi IMproved by Bram Moolenaar
|
|
*
|
|
* Do ":help uganda" in Vim to read copying and usage conditions.
|
|
* Do ":help credits" in Vim to see a list of people who contributed.
|
|
* See README.txt for an overview of the Vim source code.
|
|
*/
|
|
|
|
/*
|
|
* gui_xim.c: functions for the X Input Method
|
|
*/
|
|
|
|
#include "vim.h"
|
|
|
|
#if !defined(GTK_CHECK_VERSION)
|
|
# define GTK_CHECK_VERSION(a, b, c) 0
|
|
#endif
|
|
#if !defined(FEAT_GUI_GTK) && defined(PROTO)
|
|
typedef int GtkWidget;
|
|
typedef int GtkIMContext;
|
|
typedef int gchar;
|
|
typedef int gpointer;
|
|
typedef int PangoAttrIterator;
|
|
typedef int GdkEventKey;
|
|
#endif
|
|
|
|
#if defined(FEAT_GUI_GTK) && defined(FEAT_XIM)
|
|
# if GTK_CHECK_VERSION(3,0,0)
|
|
# include <gdk/gdkkeysyms-compat.h>
|
|
# else
|
|
# include <gdk/gdkkeysyms.h>
|
|
# endif
|
|
# ifdef MSWIN
|
|
# include <gdk/gdkwin32.h>
|
|
# else
|
|
# include <gdk/gdkx.h>
|
|
# endif
|
|
#endif
|
|
|
|
/*
|
|
* XIM often causes trouble. Define XIM_DEBUG to get a log of XIM callbacks
|
|
* in the "xim.log" file.
|
|
*/
|
|
// #define XIM_DEBUG
|
|
#if defined(XIM_DEBUG) && defined(FEAT_GUI_GTK)
|
|
static void xim_log(char *s, ...) ATTRIBUTE_FORMAT_PRINTF(1, 2);
|
|
|
|
static void
|
|
xim_log(char *s, ...)
|
|
{
|
|
va_list arglist;
|
|
static FILE *fd = NULL;
|
|
|
|
if (fd == (FILE *)-1)
|
|
return;
|
|
if (fd == NULL)
|
|
{
|
|
fd = mch_fopen("xim.log", "w");
|
|
if (fd == NULL)
|
|
{
|
|
emsg("Cannot open xim.log");
|
|
fd = (FILE *)-1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
va_start(arglist, s);
|
|
vfprintf(fd, s, arglist);
|
|
va_end(arglist);
|
|
}
|
|
#endif
|
|
|
|
#if defined(FEAT_GUI_MSWIN)
|
|
# define USE_IMACTIVATEFUNC (!gui.in_use && *p_imaf != NUL)
|
|
# define USE_IMSTATUSFUNC (!gui.in_use && *p_imsf != NUL)
|
|
#else
|
|
# define USE_IMACTIVATEFUNC (*p_imaf != NUL)
|
|
# define USE_IMSTATUSFUNC (*p_imsf != NUL)
|
|
#endif
|
|
|
|
#if (defined(FEAT_EVAL) && \
|
|
(defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL))) || \
|
|
defined(PROTO)
|
|
static callback_T imaf_cb; // 'imactivatefunc' callback function
|
|
static callback_T imsf_cb; // 'imstatusfunc' callback function
|
|
|
|
char *
|
|
did_set_imactivatefunc(optset_T *args UNUSED)
|
|
{
|
|
if (option_set_callback_func(p_imaf, &imaf_cb) == FAIL)
|
|
return e_invalid_argument;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
char *
|
|
did_set_imstatusfunc(optset_T *args UNUSED)
|
|
{
|
|
if (option_set_callback_func(p_imsf, &imsf_cb) == FAIL)
|
|
return e_invalid_argument;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
call_imactivatefunc(int active)
|
|
{
|
|
typval_T argv[2];
|
|
int save_KeyTyped = KeyTyped;
|
|
|
|
argv[0].v_type = VAR_NUMBER;
|
|
argv[0].vval.v_number = active ? 1 : 0;
|
|
argv[1].v_type = VAR_UNKNOWN;
|
|
(void)call_callback_retnr(&imaf_cb, 1, argv);
|
|
|
|
KeyTyped = save_KeyTyped;
|
|
}
|
|
|
|
static int
|
|
call_imstatusfunc(void)
|
|
{
|
|
int is_active;
|
|
int save_KeyTyped = KeyTyped;
|
|
|
|
// FIXME: Don't execute user function in unsafe situation.
|
|
if (exiting || is_autocmd_blocked())
|
|
return FALSE;
|
|
// FIXME: :py print 'xxx' is shown duplicate result.
|
|
// Use silent to avoid it.
|
|
++msg_silent;
|
|
is_active = call_callback_retnr(&imsf_cb, 0, NULL);
|
|
--msg_silent;
|
|
|
|
KeyTyped = save_KeyTyped;
|
|
return (is_active > 0);
|
|
}
|
|
#endif
|
|
|
|
#if defined(EXITFREE) || defined(PROTO)
|
|
void
|
|
free_xim_stuff(void)
|
|
{
|
|
# if defined(FEAT_EVAL) && \
|
|
(defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL))
|
|
free_callback(&imaf_cb);
|
|
free_callback(&imsf_cb);
|
|
# endif
|
|
}
|
|
#endif
|
|
|
|
#if defined(FEAT_EVAL) || defined(PROTO)
|
|
/*
|
|
* Mark the global 'imactivatefunc' and 'imstatusfunc' callbacks with "copyID"
|
|
* so that they are not garbage collected.
|
|
*/
|
|
int
|
|
set_ref_in_im_funcs(int copyID UNUSED)
|
|
{
|
|
int abort = FALSE;
|
|
|
|
# if defined(FEAT_XIM) || defined(IME_WITHOUT_XIM) || defined(VIMDLL)
|
|
abort = set_ref_in_callback(&imaf_cb, copyID);
|
|
abort = abort || set_ref_in_callback(&imsf_cb, copyID);
|
|
# endif
|
|
|
|
return abort;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(FEAT_XIM) || defined(PROTO)
|
|
|
|
# if defined(FEAT_GUI_GTK) || defined(PROTO)
|
|
static int xim_has_preediting = FALSE; // IM current status
|
|
|
|
/*
|
|
* Set preedit_start_col to the current cursor position.
|
|
*/
|
|
static void
|
|
init_preedit_start_col(void)
|
|
{
|
|
if (State & MODE_CMDLINE)
|
|
preedit_start_col = cmdline_getvcol_cursor();
|
|
else if (curwin != NULL && curwin->w_buffer != NULL)
|
|
getvcol(curwin, &curwin->w_cursor, &preedit_start_col, NULL, NULL);
|
|
// Prevent that preediting marks the buffer as changed.
|
|
xim_changed_while_preediting = curbuf->b_changed;
|
|
}
|
|
|
|
static int im_is_active = FALSE; // IM is enabled for current mode
|
|
static int preedit_is_active = FALSE;
|
|
static int im_preedit_cursor = 0; // cursor offset in characters
|
|
static int im_preedit_trailing = 0; // number of characters after cursor
|
|
|
|
static unsigned long im_commit_handler_id = 0;
|
|
static unsigned int im_activatekey_keyval = GDK_VoidSymbol;
|
|
static unsigned int im_activatekey_state = 0;
|
|
|
|
static GtkWidget *preedit_window = NULL;
|
|
static GtkWidget *preedit_label = NULL;
|
|
|
|
static void im_preedit_window_set_position(void);
|
|
|
|
void
|
|
im_set_active(int active)
|
|
{
|
|
int was_active;
|
|
|
|
was_active = !!im_get_status();
|
|
im_is_active = (active && !p_imdisable);
|
|
|
|
if (im_is_active != was_active)
|
|
xim_reset();
|
|
}
|
|
|
|
void
|
|
xim_set_focus(int focus)
|
|
{
|
|
if (xic == NULL)
|
|
return;
|
|
|
|
if (focus)
|
|
gtk_im_context_focus_in(xic);
|
|
else
|
|
gtk_im_context_focus_out(xic);
|
|
}
|
|
|
|
void
|
|
im_set_position(int row, int col)
|
|
{
|
|
if (xic == NULL)
|
|
return;
|
|
|
|
GdkRectangle area;
|
|
|
|
area.x = FILL_X(col);
|
|
area.y = FILL_Y(row);
|
|
area.width = gui.char_width * (mb_lefthalve(row, col) ? 2 : 1);
|
|
area.height = gui.char_height;
|
|
|
|
gtk_im_context_set_cursor_location(xic, &area);
|
|
|
|
if (p_imst == IM_OVER_THE_SPOT)
|
|
im_preedit_window_set_position();
|
|
}
|
|
|
|
# if 0 || defined(PROTO) // apparently only used in gui_x11.c
|
|
void
|
|
xim_set_preedit(void)
|
|
{
|
|
im_set_position(gui.row, gui.col);
|
|
}
|
|
# endif
|
|
|
|
static void
|
|
im_add_to_input(char_u *str, int len)
|
|
{
|
|
// Convert from 'termencoding' (always "utf-8") to 'encoding'
|
|
if (input_conv.vc_type != CONV_NONE)
|
|
{
|
|
str = string_convert(&input_conv, str, &len);
|
|
g_return_if_fail(str != NULL);
|
|
}
|
|
|
|
add_to_input_buf_csi(str, len);
|
|
|
|
if (input_conv.vc_type != CONV_NONE)
|
|
vim_free(str);
|
|
|
|
if (p_mh) // blank out the pointer if necessary
|
|
gui_mch_mousehide(TRUE);
|
|
}
|
|
|
|
static void
|
|
im_preedit_window_set_position(void)
|
|
{
|
|
int x, y, width, height;
|
|
int screen_x, screen_y, screen_width, screen_height;
|
|
|
|
if (preedit_window == NULL)
|
|
return;
|
|
|
|
gui_gtk_get_screen_geom_of_win(gui.drawarea, 0, 0,
|
|
&screen_x, &screen_y, &screen_width, &screen_height);
|
|
gdk_window_get_origin(gtk_widget_get_window(gui.drawarea), &x, &y);
|
|
gtk_window_get_size(GTK_WINDOW(preedit_window), &width, &height);
|
|
x = x + FILL_X(gui.col);
|
|
y = y + FILL_Y(gui.row);
|
|
if (x + width > screen_x + screen_width)
|
|
x = screen_x + screen_width - width;
|
|
if (y + height > screen_y + screen_height)
|
|
y = screen_y + screen_height - height;
|
|
gtk_window_move(GTK_WINDOW(preedit_window), x, y);
|
|
}
|
|
|
|
static void
|
|
im_preedit_window_open(void)
|
|
{
|
|
char *preedit_string;
|
|
#if !GTK_CHECK_VERSION(3,16,0)
|
|
char buf[8];
|
|
#endif
|
|
PangoAttrList *attr_list;
|
|
PangoLayout *layout;
|
|
#if GTK_CHECK_VERSION(3,0,0)
|
|
# if !GTK_CHECK_VERSION(3,16,0)
|
|
GdkRGBA color;
|
|
# endif
|
|
#else
|
|
GdkColor color;
|
|
#endif
|
|
gint w, h;
|
|
|
|
if (preedit_window == NULL)
|
|
{
|
|
preedit_window = gtk_window_new(GTK_WINDOW_POPUP);
|
|
gtk_window_set_transient_for(GTK_WINDOW(preedit_window),
|
|
GTK_WINDOW(gui.mainwin));
|
|
preedit_label = gtk_label_new("");
|
|
gtk_widget_set_name(preedit_label, "vim-gui-preedit-area");
|
|
gtk_container_add(GTK_CONTAINER(preedit_window), preedit_label);
|
|
}
|
|
|
|
#if GTK_CHECK_VERSION(3,16,0)
|
|
{
|
|
GtkStyleContext * const context
|
|
= gtk_widget_get_style_context(preedit_label);
|
|
GtkCssProvider * const provider = gtk_css_provider_new();
|
|
gchar *css = NULL;
|
|
const char * const fontname
|
|
= pango_font_description_get_family(gui.norm_font);
|
|
gint fontsize
|
|
= pango_font_description_get_size(gui.norm_font) / PANGO_SCALE;
|
|
gchar *fontsize_propval = NULL;
|
|
|
|
if (!pango_font_description_get_size_is_absolute(gui.norm_font))
|
|
{
|
|
// fontsize was given in points. Convert it into that in pixels
|
|
// to use with CSS.
|
|
GdkScreen * const screen
|
|
= gdk_window_get_screen(gtk_widget_get_window(gui.mainwin));
|
|
const gdouble dpi = gdk_screen_get_resolution(screen);
|
|
fontsize = dpi * fontsize / 72;
|
|
}
|
|
if (fontsize > 0)
|
|
fontsize_propval = g_strdup_printf("%dpx", fontsize);
|
|
else
|
|
fontsize_propval = g_strdup_printf("inherit");
|
|
|
|
css = g_strdup_printf(
|
|
"#vim-gui-preedit-area {\n"
|
|
" font-family: %s,monospace;\n"
|
|
" font-size: %s;\n"
|
|
" color: #%.2lx%.2lx%.2lx;\n"
|
|
" background-color: #%.2lx%.2lx%.2lx;\n"
|
|
"}\n",
|
|
fontname != NULL ? fontname : "inherit",
|
|
fontsize_propval,
|
|
(gui.norm_pixel >> 16) & 0xff,
|
|
(gui.norm_pixel >> 8) & 0xff,
|
|
gui.norm_pixel & 0xff,
|
|
(gui.back_pixel >> 16) & 0xff,
|
|
(gui.back_pixel >> 8) & 0xff,
|
|
gui.back_pixel & 0xff);
|
|
|
|
gtk_css_provider_load_from_data(provider, css, -1, NULL);
|
|
gtk_style_context_add_provider(context,
|
|
GTK_STYLE_PROVIDER(provider), G_MAXUINT);
|
|
|
|
g_free(css);
|
|
g_free(fontsize_propval);
|
|
g_object_unref(provider);
|
|
}
|
|
#elif GTK_CHECK_VERSION(3,0,0)
|
|
gtk_widget_override_font(preedit_label, gui.norm_font);
|
|
|
|
vim_snprintf(buf, sizeof(buf), "#%06X", gui.norm_pixel);
|
|
gdk_rgba_parse(&color, buf);
|
|
gtk_widget_override_color(preedit_label, GTK_STATE_FLAG_NORMAL, &color);
|
|
|
|
vim_snprintf(buf, sizeof(buf), "#%06X", gui.back_pixel);
|
|
gdk_rgba_parse(&color, buf);
|
|
gtk_widget_override_background_color(preedit_label, GTK_STATE_FLAG_NORMAL,
|
|
&color);
|
|
#else
|
|
gtk_widget_modify_font(preedit_label, gui.norm_font);
|
|
|
|
vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.norm_pixel);
|
|
gdk_color_parse(buf, &color);
|
|
gtk_widget_modify_fg(preedit_label, GTK_STATE_NORMAL, &color);
|
|
|
|
vim_snprintf(buf, sizeof(buf), "#%06X", (unsigned)gui.back_pixel);
|
|
gdk_color_parse(buf, &color);
|
|
gtk_widget_modify_bg(preedit_window, GTK_STATE_NORMAL, &color);
|
|
#endif
|
|
|
|
gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL);
|
|
|
|
if (preedit_string[0] != NUL)
|
|
{
|
|
gtk_label_set_text(GTK_LABEL(preedit_label), preedit_string);
|
|
gtk_label_set_attributes(GTK_LABEL(preedit_label), attr_list);
|
|
|
|
layout = gtk_label_get_layout(GTK_LABEL(preedit_label));
|
|
pango_layout_get_pixel_size(layout, &w, &h);
|
|
h = MAX(h, gui.char_height);
|
|
gtk_window_resize(GTK_WINDOW(preedit_window), w, h);
|
|
|
|
gtk_widget_show_all(preedit_window);
|
|
|
|
im_preedit_window_set_position();
|
|
}
|
|
|
|
g_free(preedit_string);
|
|
pango_attr_list_unref(attr_list);
|
|
}
|
|
|
|
static void
|
|
im_preedit_window_close(void)
|
|
{
|
|
if (preedit_window != NULL)
|
|
gtk_widget_hide(preedit_window);
|
|
}
|
|
|
|
static void
|
|
im_show_preedit(void)
|
|
{
|
|
im_preedit_window_open();
|
|
|
|
if (p_mh) // blank out the pointer if necessary
|
|
gui_mch_mousehide(TRUE);
|
|
}
|
|
|
|
static void
|
|
im_delete_preedit(void)
|
|
{
|
|
char_u bskey[] = {CSI, 'k', 'b'};
|
|
char_u delkey[] = {CSI, 'k', 'D'};
|
|
|
|
if (p_imst == IM_OVER_THE_SPOT)
|
|
{
|
|
im_preedit_window_close();
|
|
return;
|
|
}
|
|
|
|
if (State & MODE_NORMAL
|
|
#ifdef FEAT_TERMINAL
|
|
&& !term_use_loop()
|
|
#endif
|
|
)
|
|
{
|
|
im_preedit_cursor = 0;
|
|
return;
|
|
}
|
|
for (; im_preedit_cursor > 0; --im_preedit_cursor)
|
|
add_to_input_buf(bskey, (int)sizeof(bskey));
|
|
|
|
for (; im_preedit_trailing > 0; --im_preedit_trailing)
|
|
add_to_input_buf(delkey, (int)sizeof(delkey));
|
|
}
|
|
|
|
/*
|
|
* Move the cursor left by "num_move_back" characters.
|
|
* Note that ins_left() checks im_is_preediting() to avoid breaking undo for
|
|
* these K_LEFT keys.
|
|
*/
|
|
static void
|
|
im_correct_cursor(int num_move_back)
|
|
{
|
|
char_u backkey[] = {CSI, 'k', 'l'};
|
|
|
|
if (State & MODE_NORMAL)
|
|
return;
|
|
# ifdef FEAT_RIGHTLEFT
|
|
if ((State & MODE_CMDLINE) == 0 && curwin != NULL && curwin->w_p_rl)
|
|
backkey[2] = 'r';
|
|
# endif
|
|
for (; num_move_back > 0; --num_move_back)
|
|
add_to_input_buf(backkey, (int)sizeof(backkey));
|
|
}
|
|
|
|
static int xim_expected_char = NUL;
|
|
static int xim_ignored_char = FALSE;
|
|
|
|
/*
|
|
* Update the mode and cursor while in an IM callback.
|
|
*/
|
|
static void
|
|
im_show_info(void)
|
|
{
|
|
int old_vgetc_busy;
|
|
|
|
old_vgetc_busy = vgetc_busy;
|
|
vgetc_busy = TRUE;
|
|
showmode();
|
|
vgetc_busy = old_vgetc_busy;
|
|
if ((State & MODE_NORMAL) || (State & MODE_INSERT))
|
|
setcursor();
|
|
out_flush();
|
|
}
|
|
|
|
/*
|
|
* Callback invoked when the user finished preediting.
|
|
* Put the final string into the input buffer.
|
|
*/
|
|
static void
|
|
im_commit_cb(GtkIMContext *context UNUSED,
|
|
const gchar *str,
|
|
gpointer data UNUSED)
|
|
{
|
|
int slen = (int)STRLEN(str);
|
|
int add_to_input = TRUE;
|
|
int clen;
|
|
int len = slen;
|
|
int commit_with_preedit = TRUE;
|
|
char_u *im_str;
|
|
|
|
#ifdef XIM_DEBUG
|
|
xim_log("im_commit_cb(): %s\n", str);
|
|
#endif
|
|
|
|
if (p_imst == IM_ON_THE_SPOT)
|
|
{
|
|
// The imhangul module doesn't reset the preedit string before
|
|
// committing. Call im_delete_preedit() to work around that.
|
|
im_delete_preedit();
|
|
|
|
// Indicate that preediting has finished.
|
|
if (preedit_start_col == MAXCOL)
|
|
{
|
|
init_preedit_start_col();
|
|
commit_with_preedit = FALSE;
|
|
}
|
|
|
|
// The thing which setting "preedit_start_col" to MAXCOL means that
|
|
// "preedit_start_col" will be set forcedly when calling
|
|
// preedit_changed_cb() next time.
|
|
// "preedit_start_col" should not reset with MAXCOL on this part. Vim
|
|
// is simulating the preediting by using add_to_input_str(). when
|
|
// preedit begin immediately before committed, the typebuf is not
|
|
// flushed to screen, then it can't get correct "preedit_start_col".
|
|
// Thus, it should calculate the cells by adding cells of the committed
|
|
// string.
|
|
if (input_conv.vc_type != CONV_NONE)
|
|
{
|
|
im_str = string_convert(&input_conv, (char_u *)str, &len);
|
|
g_return_if_fail(im_str != NULL);
|
|
}
|
|
else
|
|
im_str = (char_u *)str;
|
|
|
|
clen = mb_string2cells(im_str, len);
|
|
|
|
if (input_conv.vc_type != CONV_NONE)
|
|
vim_free(im_str);
|
|
preedit_start_col += clen;
|
|
}
|
|
|
|
// Is this a single character that matches a keypad key that's just
|
|
// been pressed? If so, we don't want it to be entered as such - let
|
|
// us carry on processing the raw keycode so that it may be used in
|
|
// mappings as <kSomething>.
|
|
if (xim_expected_char != NUL)
|
|
{
|
|
// We're currently processing a keypad or other special key
|
|
if (slen == 1 && str[0] == xim_expected_char)
|
|
{
|
|
// It's a match - don't do it here
|
|
xim_ignored_char = TRUE;
|
|
add_to_input = FALSE;
|
|
}
|
|
else
|
|
{
|
|
// Not a match
|
|
xim_ignored_char = FALSE;
|
|
}
|
|
}
|
|
|
|
if (add_to_input)
|
|
im_add_to_input((char_u *)str, slen);
|
|
|
|
if (p_imst == IM_ON_THE_SPOT)
|
|
{
|
|
// Inserting chars while "im_is_active" is set does not cause a
|
|
// change of buffer. When the chars are committed the buffer must be
|
|
// marked as changed.
|
|
if (!commit_with_preedit)
|
|
preedit_start_col = MAXCOL;
|
|
|
|
// This flag is used in changed() at next call.
|
|
xim_changed_while_preediting = TRUE;
|
|
}
|
|
|
|
if (gtk_main_level() > 0)
|
|
gtk_main_quit();
|
|
}
|
|
|
|
/*
|
|
* Callback invoked after start to the preedit.
|
|
*/
|
|
static void
|
|
im_preedit_start_cb(GtkIMContext *context UNUSED, gpointer data UNUSED)
|
|
{
|
|
#ifdef XIM_DEBUG
|
|
xim_log("im_preedit_start_cb()\n");
|
|
#endif
|
|
|
|
im_is_active = TRUE;
|
|
preedit_is_active = TRUE;
|
|
gui_update_cursor(TRUE, FALSE);
|
|
im_show_info();
|
|
}
|
|
|
|
/*
|
|
* Callback invoked after end to the preedit.
|
|
*/
|
|
static void
|
|
im_preedit_end_cb(GtkIMContext *context UNUSED, gpointer data UNUSED)
|
|
{
|
|
#ifdef XIM_DEBUG
|
|
xim_log("im_preedit_end_cb()\n");
|
|
#endif
|
|
im_delete_preedit();
|
|
|
|
// Indicate that preediting has finished
|
|
if (p_imst == IM_ON_THE_SPOT)
|
|
preedit_start_col = MAXCOL;
|
|
xim_has_preediting = FALSE;
|
|
|
|
#if 0
|
|
// Removal of this line suggested by Takuhiro Nishioka. Fixes that IM was
|
|
// switched off unintentionally. We now use preedit_is_active (added by
|
|
// SungHyun Nam).
|
|
im_is_active = FALSE;
|
|
#endif
|
|
preedit_is_active = FALSE;
|
|
gui_update_cursor(TRUE, FALSE);
|
|
im_show_info();
|
|
}
|
|
|
|
/*
|
|
* Callback invoked after changes to the preedit string. If the preedit
|
|
* string was empty before, remember the preedit start column so we know
|
|
* where to apply feedback attributes. Delete the previous preedit string
|
|
* if there was one, save the new preedit cursor offset, and put the new
|
|
* string into the input buffer.
|
|
*
|
|
* TODO: The pragmatic "put into input buffer" approach used here has
|
|
* several fundamental problems:
|
|
*
|
|
* - The characters in the preedit string are subject to remapping.
|
|
* That's broken, only the finally committed string should be remapped.
|
|
*
|
|
* - There is a race condition involved: The retrieved value for the
|
|
* current cursor position will be wrong if any unprocessed characters
|
|
* are still queued in the input buffer.
|
|
*
|
|
* - Due to the lack of synchronization between the file buffer in memory
|
|
* and any typed characters, it's practically impossible to implement the
|
|
* "retrieve_surrounding" and "delete_surrounding" signals reliably. IM
|
|
* modules for languages such as Thai are likely to rely on this feature
|
|
* for proper operation.
|
|
*
|
|
* Conclusions: I think support for preediting needs to be moved to the
|
|
* core parts of Vim. Ideally, until it has been committed, the preediting
|
|
* string should only be displayed and not affect the buffer content at all.
|
|
* The question how to deal with the synchronization issue still remains.
|
|
* Circumventing the input buffer is probably not desirable. Anyway, I think
|
|
* implementing "retrieve_surrounding" is the only hard problem.
|
|
*
|
|
* One way to solve all of this in a clean manner would be to queue all key
|
|
* press/release events "as is" in the input buffer, and apply the IM filtering
|
|
* at the receiving end of the queue. This, however, would have a rather large
|
|
* impact on the code base. If there is an easy way to force processing of all
|
|
* remaining input from within the "retrieve_surrounding" signal handler, this
|
|
* might not be necessary. Gotta ask on vim-dev for opinions.
|
|
*/
|
|
static void
|
|
im_preedit_changed_cb(GtkIMContext *context, gpointer data UNUSED)
|
|
{
|
|
char *preedit_string = NULL;
|
|
int cursor_index = 0;
|
|
int num_move_back = 0;
|
|
char_u *str;
|
|
char_u *p;
|
|
int i;
|
|
|
|
if (p_imst == IM_ON_THE_SPOT)
|
|
gtk_im_context_get_preedit_string(context,
|
|
&preedit_string, NULL,
|
|
&cursor_index);
|
|
else
|
|
gtk_im_context_get_preedit_string(context,
|
|
&preedit_string, NULL,
|
|
NULL);
|
|
|
|
#ifdef XIM_DEBUG
|
|
xim_log("im_preedit_changed_cb(): %s\n", preedit_string);
|
|
#endif
|
|
|
|
g_return_if_fail(preedit_string != NULL); // just in case
|
|
|
|
if (p_imst == IM_OVER_THE_SPOT)
|
|
{
|
|
if (preedit_string[0] == NUL)
|
|
{
|
|
xim_has_preediting = FALSE;
|
|
im_delete_preedit();
|
|
}
|
|
else
|
|
{
|
|
xim_has_preediting = TRUE;
|
|
im_show_preedit();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If preedit_start_col is MAXCOL set it to the current cursor position.
|
|
if (preedit_start_col == MAXCOL && preedit_string[0] != '\0')
|
|
{
|
|
xim_has_preediting = TRUE;
|
|
|
|
// Urgh, this breaks if the input buffer isn't empty now
|
|
init_preedit_start_col();
|
|
}
|
|
else if (cursor_index == 0 && preedit_string[0] == '\0')
|
|
{
|
|
xim_has_preediting = FALSE;
|
|
|
|
// If at the start position (after typing backspace)
|
|
// preedit_start_col must be reset.
|
|
preedit_start_col = MAXCOL;
|
|
}
|
|
|
|
im_delete_preedit();
|
|
|
|
// Compute the end of the preediting area: "preedit_end_col".
|
|
// According to the documentation of
|
|
// gtk_im_context_get_preedit_string(), the cursor_pos output argument
|
|
// returns the offset in bytes. This is unfortunately not true -- real
|
|
// life shows the offset is in characters, and the GTK+ source code
|
|
// agrees with me. Will file a bug later.
|
|
if (preedit_start_col != MAXCOL)
|
|
preedit_end_col = preedit_start_col;
|
|
str = (char_u *)preedit_string;
|
|
for (p = str, i = 0; *p != NUL; p += utf_byte2len(*p), ++i)
|
|
{
|
|
int is_composing;
|
|
|
|
is_composing = ((*p & 0x80) != 0
|
|
&& utf_iscomposing(utf_ptr2char(p)));
|
|
// These offsets are used as counters when generating <BS> and
|
|
// <Del> to delete the preedit string. So don't count composing
|
|
// characters unless 'delcombine' is enabled.
|
|
if (!is_composing || p_deco)
|
|
{
|
|
if (i < cursor_index)
|
|
++im_preedit_cursor;
|
|
else
|
|
++im_preedit_trailing;
|
|
}
|
|
if (!is_composing && i >= cursor_index)
|
|
{
|
|
// This is essentially the same as im_preedit_trailing, except
|
|
// composing characters are not counted even if p_deco is set.
|
|
++num_move_back;
|
|
}
|
|
if (preedit_start_col != MAXCOL)
|
|
preedit_end_col += utf_ptr2cells(p);
|
|
}
|
|
|
|
if (p > str)
|
|
{
|
|
im_add_to_input(str, (int)(p - str));
|
|
im_correct_cursor(num_move_back);
|
|
}
|
|
}
|
|
|
|
g_free(preedit_string);
|
|
|
|
if (gtk_main_level() > 0)
|
|
gtk_main_quit();
|
|
}
|
|
|
|
/*
|
|
* Translate the Pango attributes at iter to Vim highlighting attributes.
|
|
* Ignore attributes not supported by Vim highlighting. This shouldn't have
|
|
* too much impact -- right now we handle even more attributes than necessary
|
|
* for the IM modules I tested with.
|
|
*/
|
|
static int
|
|
translate_pango_attributes(PangoAttrIterator *iter)
|
|
{
|
|
PangoAttribute *attr;
|
|
int char_attr = HL_NORMAL;
|
|
|
|
attr = pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE);
|
|
if (attr != NULL && ((PangoAttrInt *)attr)->value
|
|
!= (int)PANGO_UNDERLINE_NONE)
|
|
char_attr |= HL_UNDERLINE;
|
|
|
|
attr = pango_attr_iterator_get(iter, PANGO_ATTR_WEIGHT);
|
|
if (attr != NULL && ((PangoAttrInt *)attr)->value >= (int)PANGO_WEIGHT_BOLD)
|
|
char_attr |= HL_BOLD;
|
|
|
|
attr = pango_attr_iterator_get(iter, PANGO_ATTR_STYLE);
|
|
if (attr != NULL && ((PangoAttrInt *)attr)->value
|
|
!= (int)PANGO_STYLE_NORMAL)
|
|
char_attr |= HL_ITALIC;
|
|
|
|
attr = pango_attr_iterator_get(iter, PANGO_ATTR_BACKGROUND);
|
|
if (attr != NULL)
|
|
{
|
|
const PangoColor *color = &((PangoAttrColor *)attr)->color;
|
|
|
|
// Assume inverse if black background is requested
|
|
if ((color->red | color->green | color->blue) == 0)
|
|
char_attr |= HL_INVERSE;
|
|
}
|
|
|
|
return char_attr;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the highlighting attributes at column col in the preedit string.
|
|
* Return -1 if not in preediting mode or if col is out of range.
|
|
*/
|
|
int
|
|
im_get_feedback_attr(int col)
|
|
{
|
|
char *preedit_string = NULL;
|
|
PangoAttrList *attr_list = NULL;
|
|
int char_attr = -1;
|
|
|
|
if (xic == NULL)
|
|
return char_attr;
|
|
|
|
gtk_im_context_get_preedit_string(xic, &preedit_string, &attr_list, NULL);
|
|
|
|
if (preedit_string != NULL && attr_list != NULL)
|
|
{
|
|
int idx;
|
|
|
|
// Get the byte index as used by PangoAttrIterator
|
|
for (idx = 0; col > 0 && preedit_string[idx] != '\0'; --col)
|
|
idx += utfc_ptr2len((char_u *)preedit_string + idx);
|
|
|
|
if (preedit_string[idx] != '\0')
|
|
{
|
|
PangoAttrIterator *iter;
|
|
int start, end;
|
|
|
|
char_attr = HL_NORMAL;
|
|
iter = pango_attr_list_get_iterator(attr_list);
|
|
|
|
// Extract all relevant attributes from the list.
|
|
do
|
|
{
|
|
pango_attr_iterator_range(iter, &start, &end);
|
|
|
|
if (idx >= start && idx < end)
|
|
char_attr |= translate_pango_attributes(iter);
|
|
}
|
|
while (pango_attr_iterator_next(iter));
|
|
|
|
pango_attr_iterator_destroy(iter);
|
|
}
|
|
}
|
|
|
|
if (attr_list != NULL)
|
|
pango_attr_list_unref(attr_list);
|
|
g_free(preedit_string);
|
|
|
|
return char_attr;
|
|
}
|
|
|
|
void
|
|
xim_init(void)
|
|
{
|
|
#ifdef XIM_DEBUG
|
|
xim_log("xim_init()\n");
|
|
#endif
|
|
|
|
g_return_if_fail(gui.drawarea != NULL);
|
|
g_return_if_fail(gtk_widget_get_window(gui.drawarea) != NULL);
|
|
|
|
xic = gtk_im_multicontext_new();
|
|
g_object_ref(xic);
|
|
|
|
im_commit_handler_id = g_signal_connect(G_OBJECT(xic), "commit",
|
|
G_CALLBACK(&im_commit_cb), NULL);
|
|
g_signal_connect(G_OBJECT(xic), "preedit_changed",
|
|
G_CALLBACK(&im_preedit_changed_cb), NULL);
|
|
g_signal_connect(G_OBJECT(xic), "preedit_start",
|
|
G_CALLBACK(&im_preedit_start_cb), NULL);
|
|
g_signal_connect(G_OBJECT(xic), "preedit_end",
|
|
G_CALLBACK(&im_preedit_end_cb), NULL);
|
|
|
|
gtk_im_context_set_client_window(xic, gtk_widget_get_window(gui.drawarea));
|
|
}
|
|
|
|
void
|
|
im_shutdown(void)
|
|
{
|
|
#ifdef XIM_DEBUG
|
|
xim_log("im_shutdown()\n");
|
|
#endif
|
|
|
|
if (xic != NULL)
|
|
{
|
|
gtk_im_context_focus_out(xic);
|
|
g_object_unref(xic);
|
|
xic = NULL;
|
|
}
|
|
im_is_active = FALSE;
|
|
im_commit_handler_id = 0;
|
|
if (p_imst == IM_ON_THE_SPOT)
|
|
preedit_start_col = MAXCOL;
|
|
xim_has_preediting = FALSE;
|
|
}
|
|
|
|
/*
|
|
* Convert the string argument to keyval and state for GdkEventKey.
|
|
* If str is valid return TRUE, otherwise FALSE.
|
|
*
|
|
* See 'imactivatekey' for documentation of the format.
|
|
*/
|
|
static int
|
|
im_string_to_keyval(const char *str, unsigned int *keyval, unsigned int *state)
|
|
{
|
|
const char *mods_end;
|
|
unsigned tmp_keyval;
|
|
unsigned tmp_state = 0;
|
|
|
|
mods_end = strrchr(str, '-');
|
|
mods_end = (mods_end != NULL) ? mods_end + 1 : str;
|
|
|
|
// Parse modifier keys
|
|
while (str < mods_end)
|
|
switch (*str++)
|
|
{
|
|
case '-': break;
|
|
case 'S': case 's': tmp_state |= (unsigned)GDK_SHIFT_MASK; break;
|
|
case 'L': case 'l': tmp_state |= (unsigned)GDK_LOCK_MASK; break;
|
|
case 'C': case 'c': tmp_state |= (unsigned)GDK_CONTROL_MASK;break;
|
|
case '1': tmp_state |= (unsigned)GDK_MOD1_MASK; break;
|
|
case '2': tmp_state |= (unsigned)GDK_MOD2_MASK; break;
|
|
case '3': tmp_state |= (unsigned)GDK_MOD3_MASK; break;
|
|
case '4': tmp_state |= (unsigned)GDK_MOD4_MASK; break;
|
|
case '5': tmp_state |= (unsigned)GDK_MOD5_MASK; break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
tmp_keyval = gdk_keyval_from_name(str);
|
|
|
|
if (tmp_keyval == 0 || tmp_keyval == GDK_VoidSymbol)
|
|
return FALSE;
|
|
|
|
if (keyval != NULL)
|
|
*keyval = tmp_keyval;
|
|
if (state != NULL)
|
|
*state = tmp_state;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Return TRUE if p_imak is valid, otherwise FALSE. As a special case, an
|
|
* empty string is also regarded as valid.
|
|
*
|
|
* Note: The numerical key value of p_imak is cached if it was valid; thus
|
|
* boldly assuming im_xim_isvalid_imactivate() will always be called whenever
|
|
* 'imak' changes. This is currently the case but not obvious -- should
|
|
* probably rename the function for clarity.
|
|
*/
|
|
int
|
|
im_xim_isvalid_imactivate(void)
|
|
{
|
|
if (p_imak[0] == NUL)
|
|
{
|
|
im_activatekey_keyval = GDK_VoidSymbol;
|
|
im_activatekey_state = 0;
|
|
return TRUE;
|
|
}
|
|
|
|
return im_string_to_keyval((const char *)p_imak,
|
|
&im_activatekey_keyval,
|
|
&im_activatekey_state);
|
|
}
|
|
|
|
static void
|
|
im_synthesize_keypress(unsigned int keyval, unsigned int state)
|
|
{
|
|
GdkEventKey *event;
|
|
|
|
event = (GdkEventKey *)gdk_event_new(GDK_KEY_PRESS);
|
|
g_object_ref(gtk_widget_get_window(gui.drawarea));
|
|
// unreffed by gdk_event_free()
|
|
event->window = gtk_widget_get_window(gui.drawarea);
|
|
event->send_event = TRUE;
|
|
event->time = GDK_CURRENT_TIME;
|
|
event->state = state;
|
|
event->keyval = keyval;
|
|
event->hardware_keycode = // needed for XIM
|
|
XKeysymToKeycode(GDK_WINDOW_XDISPLAY(event->window), (KeySym)keyval);
|
|
event->length = 0;
|
|
event->string = NULL;
|
|
|
|
gtk_im_context_filter_keypress(xic, event);
|
|
|
|
// For consistency, also send the corresponding release event.
|
|
event->type = GDK_KEY_RELEASE;
|
|
event->send_event = FALSE;
|
|
gtk_im_context_filter_keypress(xic, event);
|
|
|
|
gdk_event_free((GdkEvent *)event);
|
|
}
|
|
|
|
void
|
|
xim_reset(void)
|
|
{
|
|
# ifdef FEAT_EVAL
|
|
if (USE_IMACTIVATEFUNC)
|
|
call_imactivatefunc(im_is_active);
|
|
else
|
|
# endif
|
|
if (xic != NULL)
|
|
{
|
|
gtk_im_context_reset(xic);
|
|
|
|
if (p_imdisable)
|
|
im_shutdown();
|
|
else
|
|
{
|
|
xim_set_focus(gui.in_focus);
|
|
|
|
if (im_activatekey_keyval != GDK_VoidSymbol)
|
|
{
|
|
if (im_is_active)
|
|
{
|
|
g_signal_handler_block(xic, im_commit_handler_id);
|
|
im_synthesize_keypress(im_activatekey_keyval,
|
|
im_activatekey_state);
|
|
g_signal_handler_unblock(xic, im_commit_handler_id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
im_shutdown();
|
|
xim_init();
|
|
xim_set_focus(gui.in_focus);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (p_imst == IM_ON_THE_SPOT)
|
|
preedit_start_col = MAXCOL;
|
|
xim_has_preediting = FALSE;
|
|
}
|
|
|
|
int
|
|
xim_queue_key_press_event(GdkEventKey *event, int down)
|
|
{
|
|
#ifdef FEAT_GUI_GTK
|
|
if (event->state & GDK_SUPER_MASK) return FALSE;
|
|
#endif
|
|
if (down)
|
|
{
|
|
// Workaround GTK2 XIM 'feature' that always converts keypad keys to
|
|
// chars., even when not part of an IM sequence (ref. feature of
|
|
// gdk/gdkkeyuni.c).
|
|
// Flag any keypad keys that might represent a single char.
|
|
// If this (on its own - i.e., not part of an IM sequence) is
|
|
// committed while we're processing one of these keys, we can ignore
|
|
// that commit and go ahead & process it ourselves. That way we can
|
|
// still distinguish keypad keys for use in mappings.
|
|
// Also add GDK_space to make <S-Space> work.
|
|
switch (event->keyval)
|
|
{
|
|
case GDK_KP_Add: xim_expected_char = '+'; break;
|
|
case GDK_KP_Subtract: xim_expected_char = '-'; break;
|
|
case GDK_KP_Divide: xim_expected_char = '/'; break;
|
|
case GDK_KP_Multiply: xim_expected_char = '*'; break;
|
|
case GDK_KP_Decimal: xim_expected_char = '.'; break;
|
|
case GDK_KP_Equal: xim_expected_char = '='; break;
|
|
case GDK_KP_0: xim_expected_char = '0'; break;
|
|
case GDK_KP_1: xim_expected_char = '1'; break;
|
|
case GDK_KP_2: xim_expected_char = '2'; break;
|
|
case GDK_KP_3: xim_expected_char = '3'; break;
|
|
case GDK_KP_4: xim_expected_char = '4'; break;
|
|
case GDK_KP_5: xim_expected_char = '5'; break;
|
|
case GDK_KP_6: xim_expected_char = '6'; break;
|
|
case GDK_KP_7: xim_expected_char = '7'; break;
|
|
case GDK_KP_8: xim_expected_char = '8'; break;
|
|
case GDK_KP_9: xim_expected_char = '9'; break;
|
|
case GDK_space: xim_expected_char = ' '; break;
|
|
default: xim_expected_char = NUL;
|
|
}
|
|
xim_ignored_char = FALSE;
|
|
}
|
|
|
|
// When typing fFtT, XIM may be activated. Thus it must pass
|
|
// gtk_im_context_filter_keypress() in Normal mode.
|
|
// And while doing :sh too.
|
|
if (xic != NULL && !p_imdisable
|
|
&& (State & (MODE_INSERT | MODE_CMDLINE
|
|
| MODE_NORMAL | MODE_EXTERNCMD)))
|
|
{
|
|
// Filter 'imactivatekey' and map it to CTRL-^. This way, Vim is
|
|
// always aware of the current status of IM, and can even emulate
|
|
// the activation key for modules that don't support one.
|
|
if (event->keyval == im_activatekey_keyval
|
|
&& (event->state & im_activatekey_state) == im_activatekey_state)
|
|
{
|
|
unsigned int state_mask;
|
|
|
|
// Require the state of the 3 most used modifiers to match exactly.
|
|
// Otherwise e.g. <S-C-space> would be unusable for other purposes
|
|
// if the IM activate key is <S-space>.
|
|
state_mask = im_activatekey_state;
|
|
state_mask |= ((int)GDK_SHIFT_MASK | (int)GDK_CONTROL_MASK
|
|
| (int)GDK_MOD1_MASK);
|
|
|
|
if ((event->state & state_mask) != im_activatekey_state)
|
|
return FALSE;
|
|
|
|
// Don't send it a second time on GDK_KEY_RELEASE.
|
|
if (event->type != GDK_KEY_PRESS)
|
|
return TRUE;
|
|
|
|
if (map_to_exists_mode((char_u *)"", MODE_LANGMAP, FALSE))
|
|
{
|
|
im_set_active(FALSE);
|
|
|
|
// ":lmap" mappings exists, toggle use of mappings.
|
|
State ^= MODE_LANGMAP;
|
|
if (State & MODE_LANGMAP)
|
|
{
|
|
curbuf->b_p_iminsert = B_IMODE_NONE;
|
|
State &= ~MODE_LANGMAP;
|
|
}
|
|
else
|
|
{
|
|
curbuf->b_p_iminsert = B_IMODE_LMAP;
|
|
State |= MODE_LANGMAP;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
return gtk_im_context_filter_keypress(xic, event);
|
|
}
|
|
|
|
// Don't filter events through the IM context if IM isn't active
|
|
// right now. Unlike with GTK+ 1.2 we cannot rely on the IM module
|
|
// not doing anything before the activation key was sent.
|
|
if (im_activatekey_keyval == GDK_VoidSymbol || im_is_active)
|
|
{
|
|
int imresult = gtk_im_context_filter_keypress(xic, event);
|
|
|
|
if (p_imst == IM_ON_THE_SPOT)
|
|
{
|
|
// Some XIM send following sequence:
|
|
// 1. preedited string.
|
|
// 2. committed string.
|
|
// 3. line changed key.
|
|
// 4. preedited string.
|
|
// 5. remove preedited string.
|
|
// if 3, Vim can't move back the above line for 5.
|
|
// thus, this part should not parse the key.
|
|
if (!imresult && preedit_start_col != MAXCOL
|
|
&& event->keyval == GDK_Return)
|
|
{
|
|
im_synthesize_keypress(GDK_Return, 0U);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// If XIM tried to commit a keypad key as a single char.,
|
|
// ignore it so we can use the keypad key 'raw', for mappings.
|
|
if (xim_expected_char != NUL && xim_ignored_char)
|
|
// We had a keypad key, and XIM tried to thieve it
|
|
return FALSE;
|
|
|
|
// This is supposed to fix a problem with iBus, that space
|
|
// characters don't work in input mode.
|
|
xim_expected_char = NUL;
|
|
|
|
// Normal processing
|
|
return imresult;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
int
|
|
im_get_status(void)
|
|
{
|
|
# ifdef FEAT_EVAL
|
|
if (USE_IMSTATUSFUNC)
|
|
return call_imstatusfunc();
|
|
# endif
|
|
return im_is_active;
|
|
}
|
|
|
|
int
|
|
preedit_get_status(void)
|
|
{
|
|
return preedit_is_active;
|
|
}
|
|
|
|
int
|
|
im_is_preediting(void)
|
|
{
|
|
return xim_has_preediting;
|
|
}
|
|
|
|
# else // !FEAT_GUI_GTK
|
|
|
|
static int xim_is_active = FALSE; // XIM should be active in the current
|
|
// mode
|
|
static int xim_has_focus = FALSE; // XIM is really being used for Vim
|
|
# ifdef FEAT_GUI_X11
|
|
static XIMStyle input_style;
|
|
static int status_area_enabled = TRUE;
|
|
# endif
|
|
|
|
/*
|
|
* Switch using XIM on/off. This is used by the code that changes "State".
|
|
* When 'imactivatefunc' is defined use that function instead.
|
|
*/
|
|
void
|
|
im_set_active(int active_arg)
|
|
{
|
|
int active = active_arg;
|
|
|
|
// If 'imdisable' is set, XIM is never active.
|
|
if (p_imdisable)
|
|
active = FALSE;
|
|
else if (input_style & XIMPreeditPosition)
|
|
// There is a problem in switching XIM off when preediting is used,
|
|
// and it is not clear how this can be solved. For now, keep XIM on
|
|
// all the time, like it was done in Vim 5.8.
|
|
active = TRUE;
|
|
|
|
# if defined(FEAT_EVAL)
|
|
if (USE_IMACTIVATEFUNC)
|
|
{
|
|
if (active != im_get_status())
|
|
{
|
|
call_imactivatefunc(active);
|
|
xim_has_focus = active;
|
|
}
|
|
return;
|
|
}
|
|
# endif
|
|
|
|
if (xic == NULL)
|
|
return;
|
|
|
|
// Remember the active state, it is needed when Vim gets keyboard focus.
|
|
xim_is_active = active;
|
|
xim_set_preedit();
|
|
}
|
|
|
|
/*
|
|
* Adjust using XIM for gaining or losing keyboard focus. Also called when
|
|
* "xim_is_active" changes.
|
|
*/
|
|
void
|
|
xim_set_focus(int focus)
|
|
{
|
|
if (xic == NULL)
|
|
return;
|
|
|
|
// XIM only gets focus when the Vim window has keyboard focus and XIM has
|
|
// been set active for the current mode.
|
|
if (focus && xim_is_active)
|
|
{
|
|
if (!xim_has_focus)
|
|
{
|
|
xim_has_focus = TRUE;
|
|
XSetICFocus(xic);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (xim_has_focus)
|
|
{
|
|
xim_has_focus = FALSE;
|
|
XUnsetICFocus(xic);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
im_set_position(int row UNUSED, int col UNUSED)
|
|
{
|
|
xim_set_preedit();
|
|
}
|
|
|
|
/*
|
|
* Set the XIM to the current cursor position.
|
|
*/
|
|
void
|
|
xim_set_preedit(void)
|
|
{
|
|
XVaNestedList attr_list;
|
|
XRectangle spot_area;
|
|
XPoint over_spot;
|
|
int line_space;
|
|
|
|
if (xic == NULL)
|
|
return;
|
|
|
|
xim_set_focus(TRUE);
|
|
|
|
if (!xim_has_focus)
|
|
{
|
|
// hide XIM cursor
|
|
over_spot.x = 0;
|
|
over_spot.y = -100; // arbitrary invisible position
|
|
attr_list = (XVaNestedList) XVaCreateNestedList(0,
|
|
XNSpotLocation,
|
|
&over_spot,
|
|
NULL);
|
|
XSetICValues(xic, XNPreeditAttributes, attr_list, NULL);
|
|
XFree(attr_list);
|
|
return;
|
|
}
|
|
|
|
if (input_style & XIMPreeditPosition)
|
|
{
|
|
if (xim_fg_color == INVALCOLOR)
|
|
{
|
|
xim_fg_color = gui.def_norm_pixel;
|
|
xim_bg_color = gui.def_back_pixel;
|
|
}
|
|
over_spot.x = TEXT_X(gui.col);
|
|
over_spot.y = TEXT_Y(gui.row);
|
|
spot_area.x = 0;
|
|
spot_area.y = 0;
|
|
spot_area.height = gui.char_height * Rows;
|
|
spot_area.width = gui.char_width * Columns;
|
|
line_space = gui.char_height;
|
|
attr_list = (XVaNestedList) XVaCreateNestedList(0,
|
|
XNSpotLocation, &over_spot,
|
|
XNForeground, (Pixel) xim_fg_color,
|
|
XNBackground, (Pixel) xim_bg_color,
|
|
XNArea, &spot_area,
|
|
XNLineSpace, line_space,
|
|
NULL);
|
|
if (XSetICValues(xic, XNPreeditAttributes, attr_list, NULL))
|
|
emsg(_(e_cannot_set_ic_values));
|
|
XFree(attr_list);
|
|
}
|
|
}
|
|
|
|
# if defined(FEAT_GUI_X11) || defined(PROTO)
|
|
# if defined(XtSpecificationRelease) && XtSpecificationRelease >= 6 && !defined(SUN_SYSTEM)
|
|
# define USE_X11R6_XIM
|
|
# endif
|
|
|
|
static int xim_real_init(Window x11_window, Display *x11_display);
|
|
|
|
|
|
# ifdef USE_X11R6_XIM
|
|
static void
|
|
xim_instantiate_cb(
|
|
Display *display,
|
|
XPointer client_data UNUSED,
|
|
XPointer call_data UNUSED)
|
|
{
|
|
Window x11_window;
|
|
Display *x11_display;
|
|
|
|
# ifdef XIM_DEBUG
|
|
xim_log("xim_instantiate_cb()\n");
|
|
# endif
|
|
|
|
gui_get_x11_windis(&x11_window, &x11_display);
|
|
if (display != x11_display)
|
|
return;
|
|
|
|
xim_real_init(x11_window, x11_display);
|
|
gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
|
|
if (xic != NULL)
|
|
XUnregisterIMInstantiateCallback(x11_display, NULL, NULL, NULL,
|
|
xim_instantiate_cb, NULL);
|
|
}
|
|
|
|
static void
|
|
xim_destroy_cb(
|
|
XIM im UNUSED,
|
|
XPointer client_data UNUSED,
|
|
XPointer call_data UNUSED)
|
|
{
|
|
Window x11_window;
|
|
Display *x11_display;
|
|
|
|
# ifdef XIM_DEBUG
|
|
xim_log("xim_destroy_cb()\n");
|
|
#endif
|
|
gui_get_x11_windis(&x11_window, &x11_display);
|
|
|
|
xic = NULL;
|
|
status_area_enabled = FALSE;
|
|
|
|
gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
|
|
|
|
XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL,
|
|
xim_instantiate_cb, NULL);
|
|
}
|
|
# endif
|
|
|
|
void
|
|
xim_init(void)
|
|
{
|
|
Window x11_window;
|
|
Display *x11_display;
|
|
|
|
# ifdef XIM_DEBUG
|
|
xim_log("xim_init()\n");
|
|
# endif
|
|
|
|
gui_get_x11_windis(&x11_window, &x11_display);
|
|
|
|
xic = NULL;
|
|
|
|
if (xim_real_init(x11_window, x11_display))
|
|
return;
|
|
|
|
gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
|
|
|
|
# ifdef USE_X11R6_XIM
|
|
XRegisterIMInstantiateCallback(x11_display, NULL, NULL, NULL,
|
|
xim_instantiate_cb, NULL);
|
|
# endif
|
|
}
|
|
|
|
static int
|
|
xim_real_init(Window x11_window, Display *x11_display)
|
|
{
|
|
int i;
|
|
char *p,
|
|
*s,
|
|
*ns,
|
|
*end,
|
|
tmp[1024];
|
|
# define IMLEN_MAX 40
|
|
char buf[IMLEN_MAX + 7];
|
|
XIM xim = NULL;
|
|
XIMStyles *xim_styles;
|
|
XIMStyle this_input_style = 0;
|
|
Boolean found;
|
|
XPoint over_spot;
|
|
XVaNestedList preedit_list, status_list;
|
|
|
|
input_style = 0;
|
|
status_area_enabled = FALSE;
|
|
|
|
if (xic != NULL)
|
|
return FALSE;
|
|
|
|
if (gui.rsrc_input_method != NULL && *gui.rsrc_input_method != NUL)
|
|
{
|
|
strcpy(tmp, gui.rsrc_input_method);
|
|
for (ns = s = tmp; ns != NULL && *s != NUL;)
|
|
{
|
|
s = (char *)skipwhite((char_u *)s);
|
|
if (*s == NUL)
|
|
break;
|
|
if ((ns = end = strchr(s, ',')) == NULL)
|
|
end = s + strlen(s);
|
|
while (SAFE_isspace(end[-1]))
|
|
end--;
|
|
*end = NUL;
|
|
|
|
if (strlen(s) <= IMLEN_MAX)
|
|
{
|
|
strcpy(buf, "@im=");
|
|
strcat(buf, s);
|
|
if ((p = XSetLocaleModifiers(buf)) != NULL && *p != NUL
|
|
&& (xim = XOpenIM(x11_display, NULL, NULL, NULL))
|
|
!= NULL)
|
|
break;
|
|
}
|
|
|
|
s = ns + 1;
|
|
}
|
|
}
|
|
|
|
if (xim == NULL && (p = XSetLocaleModifiers("")) != NULL && *p != NUL)
|
|
xim = XOpenIM(x11_display, NULL, NULL, NULL);
|
|
|
|
// This is supposed to be useful to obtain characters through
|
|
// XmbLookupString() without really using a XIM.
|
|
if (xim == NULL && (p = XSetLocaleModifiers("@im=none")) != NULL
|
|
&& *p != NUL)
|
|
xim = XOpenIM(x11_display, NULL, NULL, NULL);
|
|
|
|
if (xim == NULL)
|
|
{
|
|
// Only give this message when verbose is set, because too many people
|
|
// got this message when they didn't want to use a XIM.
|
|
if (p_verbose > 0)
|
|
{
|
|
verbose_enter();
|
|
emsg(_(e_failed_to_open_input_method));
|
|
verbose_leave();
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
# ifdef USE_X11R6_XIM
|
|
{
|
|
XIMCallback destroy_cb;
|
|
|
|
destroy_cb.callback = xim_destroy_cb;
|
|
destroy_cb.client_data = NULL;
|
|
if (XSetIMValues(xim, XNDestroyCallback, &destroy_cb, NULL))
|
|
emsg(_(e_warning_could_not_set_destroy_callback_to_im));
|
|
}
|
|
# endif
|
|
|
|
if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL) || !xim_styles)
|
|
{
|
|
emsg(_(e_input_method_doesnt_support_any_style));
|
|
XCloseIM(xim);
|
|
return FALSE;
|
|
}
|
|
|
|
found = False;
|
|
strcpy(tmp, gui.rsrc_preedit_type_name);
|
|
for (s = tmp; s && !found; )
|
|
{
|
|
while (*s && SAFE_isspace(*s))
|
|
s++;
|
|
if (!*s)
|
|
break;
|
|
if ((ns = end = strchr(s, ',')) != 0)
|
|
ns++;
|
|
else
|
|
end = s + strlen(s);
|
|
while (SAFE_isspace(*end))
|
|
end--;
|
|
*end = '\0';
|
|
|
|
if (!strcmp(s, "OverTheSpot"))
|
|
this_input_style = (XIMPreeditPosition | XIMStatusArea);
|
|
else if (!strcmp(s, "OffTheSpot"))
|
|
this_input_style = (XIMPreeditArea | XIMStatusArea);
|
|
else if (!strcmp(s, "Root"))
|
|
this_input_style = (XIMPreeditNothing | XIMStatusNothing);
|
|
|
|
for (i = 0; (unsigned short)i < xim_styles->count_styles; i++)
|
|
{
|
|
if (this_input_style == xim_styles->supported_styles[i])
|
|
{
|
|
found = True;
|
|
break;
|
|
}
|
|
}
|
|
if (!found)
|
|
for (i = 0; (unsigned short)i < xim_styles->count_styles; i++)
|
|
{
|
|
if ((xim_styles->supported_styles[i] & this_input_style)
|
|
== (this_input_style & ~XIMStatusArea))
|
|
{
|
|
this_input_style &= ~XIMStatusArea;
|
|
found = True;
|
|
break;
|
|
}
|
|
}
|
|
|
|
s = ns;
|
|
}
|
|
XFree(xim_styles);
|
|
|
|
if (!found)
|
|
{
|
|
// Only give this message when verbose is set, because too many people
|
|
// got this message when they didn't want to use a XIM.
|
|
if (p_verbose > 0)
|
|
{
|
|
verbose_enter();
|
|
emsg(_(e_input_method_doesnt_support_my_preedit_type));
|
|
verbose_leave();
|
|
}
|
|
XCloseIM(xim);
|
|
return FALSE;
|
|
}
|
|
|
|
over_spot.x = TEXT_X(gui.col);
|
|
over_spot.y = TEXT_Y(gui.row);
|
|
input_style = this_input_style;
|
|
|
|
// A crash was reported when trying to pass gui.norm_font as XNFontSet,
|
|
// thus that has been removed. Hopefully the default works...
|
|
# ifdef FEAT_XFONTSET
|
|
if (gui.fontset != NOFONTSET)
|
|
{
|
|
preedit_list = XVaCreateNestedList(0,
|
|
XNSpotLocation, &over_spot,
|
|
XNForeground, (Pixel)gui.def_norm_pixel,
|
|
XNBackground, (Pixel)gui.def_back_pixel,
|
|
XNFontSet, (XFontSet)gui.fontset,
|
|
NULL);
|
|
status_list = XVaCreateNestedList(0,
|
|
XNForeground, (Pixel)gui.def_norm_pixel,
|
|
XNBackground, (Pixel)gui.def_back_pixel,
|
|
XNFontSet, (XFontSet)gui.fontset,
|
|
NULL);
|
|
}
|
|
else
|
|
# endif
|
|
{
|
|
preedit_list = XVaCreateNestedList(0,
|
|
XNSpotLocation, &over_spot,
|
|
XNForeground, (Pixel)gui.def_norm_pixel,
|
|
XNBackground, (Pixel)gui.def_back_pixel,
|
|
NULL);
|
|
status_list = XVaCreateNestedList(0,
|
|
XNForeground, (Pixel)gui.def_norm_pixel,
|
|
XNBackground, (Pixel)gui.def_back_pixel,
|
|
NULL);
|
|
}
|
|
|
|
xic = XCreateIC(xim,
|
|
XNInputStyle, input_style,
|
|
XNClientWindow, x11_window,
|
|
XNFocusWindow, gui.wid,
|
|
XNPreeditAttributes, preedit_list,
|
|
XNStatusAttributes, status_list,
|
|
NULL);
|
|
XFree(status_list);
|
|
XFree(preedit_list);
|
|
if (xic != NULL)
|
|
{
|
|
if (input_style & XIMStatusArea)
|
|
{
|
|
xim_set_status_area();
|
|
status_area_enabled = TRUE;
|
|
}
|
|
else
|
|
gui_set_shellsize(FALSE, FALSE, RESIZE_BOTH);
|
|
}
|
|
else
|
|
{
|
|
if (!is_not_a_term())
|
|
emsg(_(e_failed_to_create_input_context));
|
|
XCloseIM(xim);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
# endif // FEAT_GUI_X11
|
|
|
|
/*
|
|
* Get IM status. When IM is on, return TRUE. Else return FALSE.
|
|
* FIXME: This doesn't work correctly: Having focus doesn't always mean XIM is
|
|
* active, when not having focus XIM may still be active (e.g., when using a
|
|
* tear-off menu item).
|
|
*/
|
|
int
|
|
im_get_status(void)
|
|
{
|
|
# ifdef FEAT_EVAL
|
|
if (USE_IMSTATUSFUNC)
|
|
return call_imstatusfunc();
|
|
# endif
|
|
return xim_has_focus;
|
|
}
|
|
|
|
# endif // !FEAT_GUI_GTK
|
|
|
|
# if !defined(FEAT_GUI_GTK) || defined(PROTO)
|
|
/*
|
|
* Set up the status area.
|
|
*
|
|
* This should use a separate Widget, but that seems not possible, because
|
|
* preedit_area and status_area should be set to the same window as for the
|
|
* text input. Unfortunately this means the status area pollutes the text
|
|
* window...
|
|
*/
|
|
void
|
|
xim_set_status_area(void)
|
|
{
|
|
XVaNestedList preedit_list = 0, status_list = 0, list = 0;
|
|
XRectangle pre_area, status_area;
|
|
|
|
if (xic == NULL)
|
|
return;
|
|
|
|
if (input_style & XIMStatusArea)
|
|
{
|
|
if (input_style & XIMPreeditArea)
|
|
{
|
|
XRectangle *needed_rect;
|
|
|
|
// to get status_area width
|
|
status_list = XVaCreateNestedList(0, XNAreaNeeded,
|
|
&needed_rect, NULL);
|
|
XGetICValues(xic, XNStatusAttributes, status_list, NULL);
|
|
XFree(status_list);
|
|
|
|
status_area.width = needed_rect->width;
|
|
}
|
|
else
|
|
status_area.width = gui.char_width * Columns;
|
|
|
|
status_area.x = 0;
|
|
status_area.y = gui.char_height * Rows + gui.border_offset;
|
|
if (gui.which_scrollbars[SBAR_BOTTOM])
|
|
status_area.y += gui.scrollbar_height;
|
|
#ifdef FEAT_MENU
|
|
if (gui.menu_is_active)
|
|
status_area.y += gui.menu_height;
|
|
#endif
|
|
status_area.height = gui.char_height;
|
|
status_list = XVaCreateNestedList(0, XNArea, &status_area, NULL);
|
|
}
|
|
else
|
|
{
|
|
status_area.x = 0;
|
|
status_area.y = gui.char_height * Rows + gui.border_offset;
|
|
if (gui.which_scrollbars[SBAR_BOTTOM])
|
|
status_area.y += gui.scrollbar_height;
|
|
#ifdef FEAT_MENU
|
|
if (gui.menu_is_active)
|
|
status_area.y += gui.menu_height;
|
|
#endif
|
|
status_area.width = 0;
|
|
status_area.height = gui.char_height;
|
|
}
|
|
|
|
if (input_style & XIMPreeditArea) // off-the-spot
|
|
{
|
|
pre_area.x = status_area.x + status_area.width;
|
|
pre_area.y = gui.char_height * Rows + gui.border_offset;
|
|
pre_area.width = gui.char_width * Columns - pre_area.x;
|
|
if (gui.which_scrollbars[SBAR_BOTTOM])
|
|
pre_area.y += gui.scrollbar_height;
|
|
#ifdef FEAT_MENU
|
|
if (gui.menu_is_active)
|
|
pre_area.y += gui.menu_height;
|
|
#endif
|
|
pre_area.height = gui.char_height;
|
|
preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL);
|
|
}
|
|
else if (input_style & XIMPreeditPosition) // over-the-spot
|
|
{
|
|
pre_area.x = 0;
|
|
pre_area.y = 0;
|
|
pre_area.height = gui.char_height * Rows;
|
|
pre_area.width = gui.char_width * Columns;
|
|
preedit_list = XVaCreateNestedList(0, XNArea, &pre_area, NULL);
|
|
}
|
|
|
|
if (preedit_list && status_list)
|
|
list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list,
|
|
XNStatusAttributes, status_list, NULL);
|
|
else if (preedit_list)
|
|
list = XVaCreateNestedList(0, XNPreeditAttributes, preedit_list,
|
|
NULL);
|
|
else if (status_list)
|
|
list = XVaCreateNestedList(0, XNStatusAttributes, status_list,
|
|
NULL);
|
|
else
|
|
list = NULL;
|
|
|
|
if (list)
|
|
{
|
|
XSetICValues(xic, XNVaNestedList, list, NULL);
|
|
XFree(list);
|
|
}
|
|
if (status_list)
|
|
XFree(status_list);
|
|
if (preedit_list)
|
|
XFree(preedit_list);
|
|
}
|
|
|
|
int
|
|
xim_get_status_area_height(void)
|
|
{
|
|
if (status_area_enabled)
|
|
return gui.char_height;
|
|
return 0;
|
|
}
|
|
# endif
|
|
|
|
#else // !defined(FEAT_XIM)
|
|
|
|
# if defined(IME_WITHOUT_XIM) || defined(VIMDLL) || defined(PROTO)
|
|
static int im_was_set_active = FALSE;
|
|
|
|
int
|
|
# ifdef VIMDLL
|
|
mbyte_im_get_status(void)
|
|
# else
|
|
im_get_status(void)
|
|
# endif
|
|
{
|
|
# if defined(FEAT_EVAL)
|
|
if (USE_IMSTATUSFUNC)
|
|
return call_imstatusfunc();
|
|
# endif
|
|
return im_was_set_active;
|
|
}
|
|
|
|
void
|
|
# ifdef VIMDLL
|
|
mbyte_im_set_active(int active_arg)
|
|
# else
|
|
im_set_active(int active_arg)
|
|
# endif
|
|
{
|
|
# if defined(FEAT_EVAL)
|
|
int active = !p_imdisable && active_arg;
|
|
|
|
if (USE_IMACTIVATEFUNC && active != im_get_status())
|
|
{
|
|
call_imactivatefunc(active);
|
|
im_was_set_active = active;
|
|
}
|
|
# endif
|
|
}
|
|
|
|
# if defined(FEAT_GUI) && !defined(FEAT_GUI_HAIKU) && !defined(VIMDLL)
|
|
void
|
|
im_set_position(int row UNUSED, int col UNUSED)
|
|
{
|
|
}
|
|
# endif
|
|
# endif
|
|
|
|
#endif // FEAT_XIM
|