summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Filelist2
-rw-r--r--src/Make_cyg_ming.mak1
-rw-r--r--src/Make_morph.mak1
-rw-r--r--src/Make_mvc.mak4
-rw-r--r--src/Make_vms.mms6
-rw-r--r--src/Makefile11
-rw-r--r--src/gui_xim.c1777
-rw-r--r--src/mbyte.c1764
-rw-r--r--src/proto.h1
-rw-r--r--src/proto/gui_xim.pro17
-rw-r--r--src/proto/mbyte.pro15
-rw-r--r--src/version.c2
12 files changed, 1822 insertions, 1779 deletions
diff --git a/Filelist b/Filelist
index a0f987716..2dbb2d343 100644
--- a/Filelist
+++ b/Filelist
@@ -419,6 +419,7 @@ SRC_UNIX = \
src/gui_gtk_x11.c \
src/gui_gtk_res.xml \
src/gui_motif.c \
+ src/gui_xim.c \
src/gui_xmdlg.c \
src/gui_xmebw.c \
src/gui_xmebw.h \
@@ -442,6 +443,7 @@ SRC_UNIX = \
src/proto/gui_gtk_x11.pro \
src/proto/gui_gtk_gresources.pro \
src/proto/gui_motif.pro \
+ src/proto/gui_xim.pro \
src/proto/gui_xmdlg.pro \
src/proto/gui_x11.pro \
src/proto/if_xcmdsrv.pro \
diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak
index d0b82f1b8..39f1e847d 100644
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -741,6 +741,7 @@ OBJ = \
$(OUTDIR)/findfile.o \
$(OUTDIR)/fold.o \
$(OUTDIR)/getchar.o \
+ $(OUTDIR)/gui_xim.o \
$(OUTDIR)/hardcopy.o \
$(OUTDIR)/hashtab.o \
$(OUTDIR)/highlight.o \
diff --git a/src/Make_morph.mak b/src/Make_morph.mak
index 4efc1d67a..ce615b2e3 100644
--- a/src/Make_morph.mak
+++ b/src/Make_morph.mak
@@ -61,6 +61,7 @@ SRC = arabic.c \
findfile.c \
fold.c \
getchar.c \
+ gui_xim.c \
hardcopy.c \
hashtab.c \
highlight.c \
diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak
index aec94a981..8559c9dc2 100644
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -761,6 +761,7 @@ OBJ = \
$(OUTDIR)\findfile.obj \
$(OUTDIR)\fold.obj \
$(OUTDIR)\getchar.obj \
+ $(OUTDIR)\gui_xim.obj \
$(OUTDIR)\hardcopy.obj \
$(OUTDIR)\hashtab.obj \
$(OUTDIR)\highlight.obj \
@@ -1591,6 +1592,8 @@ $(OUTDIR)/fold.obj: $(OUTDIR) fold.c $(INCL)
$(OUTDIR)/getchar.obj: $(OUTDIR) getchar.c $(INCL)
+$(OUTDIR)/gui_xim.obj: $(OUTDIR) gui_xim.c $(INCL)
+
$(OUTDIR)/hardcopy.obj: $(OUTDIR) hardcopy.c $(INCL) version.h
$(OUTDIR)/hashtab.obj: $(OUTDIR) hashtab.c $(INCL)
@@ -1908,6 +1911,7 @@ proto.h: \
proto/filepath.pro \
proto/findfile.pro \
proto/getchar.pro \
+ proto/gui_xim.pro \
proto/hardcopy.pro \
proto/hashtab.pro \
proto/highlight.pro \
diff --git a/src/Make_vms.mms b/src/Make_vms.mms
index e5ff85751..1194acdd4 100644
--- a/src/Make_vms.mms
+++ b/src/Make_vms.mms
@@ -334,6 +334,7 @@ SRC = \
findfile.c \
fold.c \
getchar.c \
+ gui_xim.c \
hardcopy.c \
hashtab.c \
highlight.c \
@@ -445,6 +446,7 @@ OBJ = \
findfile.obj \
fold.obj \
getchar.obj \
+ gui_xim.obj \
hardcopy.obj \
hashtab.obj \
highlight.obj \
@@ -818,6 +820,10 @@ getchar.obj : getchar.c vim.h [.auto]config.h feature.h os_unix.h \
ascii.h keymap.h term.h macros.h structs.h regexp.h \
gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
globals.h
+gui_xim.obj : gui_xim.c vim.h [.auto]config.h feature.h os_unix.h \
+ ascii.h keymap.h term.h macros.h structs.h regexp.h \
+ gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
+ globals.h
hardcopy.obj : hardcopy.c vim.h [.auto]config.h feature.h os_unix.h \
ascii.h keymap.h term.h macros.h structs.h regexp.h \
gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
diff --git a/src/Makefile b/src/Makefile
index 9a8e24f83..520ef0b93 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1636,6 +1636,7 @@ BASIC_SRC = \
findfile.c \
fold.c \
getchar.c \
+ gui_xim.c \
hardcopy.c \
hashtab.c \
highlight.c \
@@ -1785,6 +1786,7 @@ OBJ_COMMON = \
objects/findfile.o \
objects/fold.o \
objects/getchar.o \
+ objects/gui_xim.o \
objects/hardcopy.o \
objects/hashtab.o \
objects/highlight.o \
@@ -1950,6 +1952,7 @@ PRO_AUTO = \
findfile.pro \
fold.pro \
getchar.pro \
+ gui_xim.pro \
gui_beval.pro \
hardcopy.pro \
hashtab.pro \
@@ -3301,6 +3304,9 @@ objects/gui_xmebw.o: gui_xmebw.c
objects/gui_x11.o: gui_x11.c
$(CCC) -o $@ gui_x11.c
+objects/gui_xim.o: gui_xim.c
+ $(CCC) -o $@ gui_xim.c
+
objects/gui_photon.o: gui_photon.c
$(CCC) -o $@ gui_photon.c
@@ -4239,6 +4245,11 @@ objects/gui_x11.o: gui_x11.c vim.h protodef.h auto/config.h feature.h os_unix.h
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
proto.h globals.h ../runtime/vim32x32.xpm ../runtime/vim16x16.xpm \
../runtime/vim48x48.xpm
+objects/gui_xim.o: gui_xim.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h ../runtime/vim32x32.xpm ../runtime/vim16x16.xpm \
+ ../runtime/vim48x48.xpm
objects/gui_at_sb.o: gui_at_sb.c vim.h protodef.h auto/config.h feature.h \
os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
diff --git a/src/gui_xim.c b/src/gui_xim.c
new file mode 100644
index 000000000..e3a48b5cb
--- /dev/null
+++ b/src/gui_xim.c
@@ -0,0 +1,1777 @@
+/* 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(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
+#ifdef XIM_DEBUG
+ 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
+
+#ifdef FEAT_GUI
+# 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))
+ static void
+call_imactivatefunc(int active)
+{
+ typval_T argv[2];
+
+ argv[0].v_type = VAR_NUMBER;
+ argv[0].vval.v_number = active ? 1 : 0;
+ argv[1].v_type = VAR_UNKNOWN;
+ (void)call_func_retnr(p_imaf, 1, argv);
+}
+
+ static int
+call_imstatusfunc(void)
+{
+ int is_active;
+
+ // 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_func_retnr(p_imsf, 0, NULL);
+ --msg_silent;
+ return (is_active > 0);
+}
+#endif
+
+#if defined(FEAT_XIM) || defined(PROTO)
+
+# if defined(FEAT_GUI_GTK) || defined(PROTO)
+static int xim_has_preediting INIT(= FALSE); // IM current status
+
+/*
+ * Set preedit_start_col to the current cursor position.
+ */
+ static void
+init_preedit_start_col(void)
+{
+ if (State & 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)
+ {
+ 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)
+ {
+ 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,
+ &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()
+{
+ 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(gui.drawarea);
+ 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(
+ "widget#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()
+{
+ if (preedit_window != NULL)
+ gtk_widget_hide(preedit_window);
+}
+
+ static void
+im_show_preedit()
+{
+ 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 & 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 & NORMAL)
+ return;
+# ifdef FEAT_RIGHTLEFT
+ if ((State & 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 & NORMAL) || (State & 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)
+{
+ 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 & (INSERT | CMDLINE | NORMAL | EXTERNCMD)) != 0)
+ {
+ /*
+ * 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 *)"", LANGMAP, FALSE))
+ {
+ im_set_active(FALSE);
+
+ // ":lmap" mappings exists, toggle use of mappings.
+ State ^= LANGMAP;
+ if (State & LANGMAP)
+ {
+ curbuf->b_p_iminsert = B_IMODE_NONE;
+ State &= ~LANGMAP;
+ }
+ else
+ {
+ curbuf->b_p_iminsert = B_IMODE_LMAP;
+ State |= 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(_("E284: Cannot set IC values"));
+ XFree(attr_list);
+ }
+}
+
+# if defined(FEAT_GUI_X11)
+static char e_xim[] = N_("E285: Failed to create input context");
+# endif
+
+# 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 (isspace(((char_u *)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(_("E286: 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(_("E287: Warning: Could not set destroy callback to IM"));
+ }
+# endif
+
+ if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL) || !xim_styles)
+ {
+ emsg(_("E288: input method doesn't support any style"));
+ XCloseIM(xim);
+ return FALSE;
+ }
+
+ found = False;
+ strcpy(tmp, gui.rsrc_preedit_type_name);
+ for (s = tmp; s && !found; )
+ {
+ while (*s && isspace((unsigned char)*s))
+ s++;
+ if (!*s)
+ break;
+ if ((ns = end = strchr(s, ',')) != 0)
+ ns++;
+ else
+ end = s + strlen(s);
+ while (isspace((unsigned char)*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(_("E289: input method doesn't 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_xim));
+ 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
diff --git a/src/mbyte.c b/src/mbyte.c
index 802df4921..de732c4fc 100644
--- a/src/mbyte.c
+++ b/src/mbyte.c
@@ -111,19 +111,6 @@
# endif
#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
-
#ifdef HAVE_WCHAR_H
# include <wchar.h>
#endif
@@ -179,37 +166,6 @@ static char utf8len_tab_zero[256] =
3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0,
};
-/*
- * XIM often causes trouble. Define XIM_DEBUG to get a log of XIM callbacks
- * in the "xim.log" file.
- */
-// #define XIM_DEBUG
-#ifdef XIM_DEBUG
- 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
-
/*
* Canonical encoding names and their properties.
@@ -4778,1726 +4734,6 @@ iconv_end(void)
# endif // DYNAMIC_ICONV
# endif // USE_ICONV
-
-#ifdef FEAT_GUI
-# 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))
- static void
-call_imactivatefunc(int active)
-{
- typval_T argv[2];
-
- argv[0].v_type = VAR_NUMBER;
- argv[0].vval.v_number = active ? 1 : 0;
- argv[1].v_type = VAR_UNKNOWN;
- (void)call_func_retnr(p_imaf, 1, argv);
-}
-
- static int
-call_imstatusfunc(void)
-{
- int is_active;
-
- // 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_func_retnr(p_imsf, 0, NULL);
- --msg_silent;
- return (is_active > 0);
-}
-#endif
-
-#if defined(FEAT_XIM) || defined(PROTO)
-
-# if defined(FEAT_GUI_GTK) || defined(PROTO)
-static int xim_has_preediting INIT(= FALSE); // IM current status
-
-/*
- * Set preedit_start_col to the current cursor position.
- */
- static void
-init_preedit_start_col(void)
-{
- if (State & 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)
- {
- 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)
- {
- 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,
- &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()
-{
- 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(gui.drawarea);
- 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(
- "widget#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()
-{
- if (preedit_window != NULL)
- gtk_widget_hide(preedit_window);
-}
-
- static void
-im_show_preedit()
-{
- 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 & 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 & NORMAL)
- return;
-# ifdef FEAT_RIGHTLEFT
- if ((State & 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 & NORMAL) || (State & 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)
-{
- 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 & (INSERT | CMDLINE | NORMAL | EXTERNCMD)) != 0)
- {
- /*
- * 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 *)"", LANGMAP, FALSE))
- {
- im_set_active(FALSE);
-
- // ":lmap" mappings exists, toggle use of mappings.
- State ^= LANGMAP;
- if (State & LANGMAP)
- {
- curbuf->b_p_iminsert = B_IMODE_NONE;
- State &= ~LANGMAP;
- }
- else
- {
- curbuf->b_p_iminsert = B_IMODE_LMAP;
- State |= 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(_("E284: Cannot set IC values"));
- XFree(attr_list);
- }
-}
-
-# if defined(FEAT_GUI_X11)
-static char e_xim[] = N_("E285: Failed to create input context");
-# endif
-
-# 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 (isspace(((char_u *)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(_("E286: 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(_("E287: Warning: Could not set destroy callback to IM"));
- }
-# endif
-
- if (XGetIMValues(xim, XNQueryInputStyle, &xim_styles, NULL) || !xim_styles)
- {
- emsg(_("E288: input method doesn't support any style"));
- XCloseIM(xim);
- return FALSE;
- }
-
- found = False;
- strcpy(tmp, gui.rsrc_preedit_type_name);
- for (s = tmp; s && !found; )
- {
- while (*s && isspace((unsigned char)*s))
- s++;
- if (!*s)
- break;
- if ((ns = end = strchr(s, ',')) != 0)
- ns++;
- else
- end = s + strlen(s);
- while (isspace((unsigned char)*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(_("E289: input method doesn't 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_xim));
- 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)
-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
-
#if defined(FEAT_EVAL) || defined(PROTO)
/*
* "getimstatus()" function
diff --git a/src/proto.h b/src/proto.h
index b73350a4f..eec7ad530 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -92,6 +92,7 @@ extern int _stricoll(char *a, char *b);
# include "findfile.pro"
# include "fold.pro"
# include "getchar.pro"
+# include "gui_xim.pro"
# include "hardcopy.pro"
# include "hashtab.pro"
# include "highlight.pro"
diff --git a/src/proto/gui_xim.pro b/src/proto/gui_xim.pro
new file mode 100644
index 000000000..01fb56cda
--- /dev/null
+++ b/src/proto/gui_xim.pro
@@ -0,0 +1,17 @@
+/* gui_xim.c */
+void im_set_active(int active);
+void xim_set_focus(int focus);
+void im_set_position(int row, int col);
+void xim_set_preedit(void);
+int im_get_feedback_attr(int col);
+void xim_init(void);
+void im_shutdown(void);
+int im_xim_isvalid_imactivate(void);
+void xim_reset(void);
+int xim_queue_key_press_event(GdkEventKey *event, int down);
+int im_get_status(void);
+int preedit_get_status(void);
+int im_is_preediting(void);
+void xim_set_status_area(void);
+int xim_get_status_area_height(void);
+/* vim: set ft=c : */
diff --git a/src/proto/mbyte.pro b/src/proto/mbyte.pro
index cbfa90961..7b8c4fe67 100644
--- a/src/proto/mbyte.pro
+++ b/src/proto/mbyte.pro
@@ -74,21 +74,6 @@ int encname2codepage(char_u *name);
void *my_iconv_open(char_u *to, char_u *from);
int iconv_enabled(int verbose);
void iconv_end(void);
-void im_set_active(int active);
-void xim_set_focus(int focus);
-void im_set_position(int row, int col);
-void xim_set_preedit(void);
-int im_get_feedback_attr(int col);
-void xim_init(void);
-void im_shutdown(void);
-int im_xim_isvalid_imactivate(void);
-void xim_reset(void);
-int xim_queue_key_press_event(GdkEventKey *event, int down);
-int im_get_status(void);
-int preedit_get_status(void);
-int im_is_preediting(void);
-void xim_set_status_area(void);
-int xim_get_status_area_height(void);
void f_getimstatus(typval_T *argvars, typval_T *rettv);
int convert_setup(vimconv_T *vcp, char_u *from, char_u *to);
int convert_setup_ext(vimconv_T *vcp, char_u *from, int from_unicode_is_utf8, char_u *to, int to_unicode_is_utf8);
diff --git a/src/version.c b/src/version.c
index 7138ac525..ec8cda77d 100644
--- a/src/version.c
+++ b/src/version.c
@@ -747,6 +747,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 872,
+/**/
871,
/**/
870,