vim/src/gui_xim.c
lilydjwg b1457d4cb9
patch 9.1.0078: GTK3: using wrong style for pre-edit area
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>
2024-02-06 10:49:14 +01:00

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