diff src/mbyte.c @ 12293:1ff5e5dfa9b0 v8.0.1026

patch 8.0.1026: GTK on-the-spot input has problems commit https://github.com/vim/vim/commit/5c6dbcb03fa552d7b0e61c8fcf425147eb6bf7d5 Author: Bram Moolenaar <Bram@vim.org> Date: Wed Aug 30 22:00:20 2017 +0200 patch 8.0.1026: GTK on-the-spot input has problems Problem: GTK on-the-spot input has problems. (Gerd Wachsmuth) Solution: Support over-the-spot. (Yukihiro Nakadaira, Ketn Takata, closes #1215)
author Christian Brabandt <cb@256bit.org>
date Wed, 30 Aug 2017 22:15:04 +0200
parents d2373927d76d
children d3175a3bd8cd
line wrap: on
line diff
--- a/src/mbyte.c
+++ b/src/mbyte.c
@@ -4788,6 +4788,11 @@ static unsigned long im_commit_handler_i
 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)
 {
@@ -4825,6 +4830,9 @@ im_set_position(int row, int col)
 	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();
     }
 }
 
@@ -4855,12 +4863,107 @@ im_add_to_input(char_u *str, int len)
 	gui_mch_mousehide(TRUE);
 }
 
+     static void
+im_preedit_window_set_position(void)
+{
+    int x, y, w, h, sw, sh;
+
+    if (preedit_window == NULL)
+	return;
+
+    sw = gdk_screen_get_width(gtk_widget_get_screen(preedit_window));
+    sh = gdk_screen_get_height(gtk_widget_get_screen(preedit_window));
+#if GTK_CHECK_VERSION(3,0,0)
+    gdk_window_get_origin(gtk_widget_get_window(gui.drawarea), &x, &y);
+#else
+    gdk_window_get_origin(gui.drawarea->window, &x, &y);
+#endif
+    gtk_window_get_size(GTK_WINDOW(preedit_window), &w, &h);
+    x = x + FILL_X(gui.col);
+    y = y + FILL_Y(gui.row);
+    if (x + w > sw)
+	x = sw - w;
+    if (y + h > sh)
+	y = sh - h;
+    gtk_window_move(GTK_WINDOW(preedit_window), x, y);
+}
+
+    static void
+im_preedit_window_open()
+{
+    char *preedit_string;
+    char buf[8];
+    PangoAttrList *attr_list;
+    PangoLayout *layout;
+    GdkColor color;
+    gint w, h;
+
+    if (preedit_window == NULL)
+    {
+	preedit_window = gtk_window_new(GTK_WINDOW_POPUP);
+	preedit_label = gtk_label_new("");
+	gtk_container_add(GTK_CONTAINER(preedit_window), preedit_label);
+    }
+
+    gtk_widget_modify_font(preedit_label, gui.norm_font);
+
+    vim_snprintf(buf, sizeof(buf), "#%06X", gui.norm_pixel);
+    gdk_color_parse(buf, &color);
+    gtk_widget_modify_fg(preedit_label, GTK_STATE_NORMAL, &color);
+
+    vim_snprintf(buf, sizeof(buf), "#%06X", gui.back_pixel);
+    gdk_color_parse(buf, &color);
+    gtk_widget_modify_bg(preedit_window, GTK_STATE_NORMAL, &color);
+
+    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)
     {
 	im_preedit_cursor = 0;
@@ -4933,40 +5036,43 @@ im_commit_cb(GtkIMContext *context UNUSE
     xim_log("im_commit_cb(): %s\n", str);
 #endif
 
-    /* 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)
+    if (p_imst == IM_ON_THE_SPOT)
     {
-	init_preedit_start_col();
-	commit_with_preedit = FALSE;
+	/* 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;
     }
 
-    /* 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
@@ -4990,14 +5096,17 @@ im_commit_cb(GtkIMContext *context UNUSE
     if (add_to_input)
 	im_add_to_input((char_u *)str, slen);
 
-    /* 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 (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();
@@ -5031,7 +5140,8 @@ im_preedit_end_cb(GtkIMContext *context 
     im_delete_preedit();
 
     /* Indicate that preediting has finished */
-    preedit_start_col = MAXCOL;
+    if (p_imst == IM_ON_THE_SPOT)
+	preedit_start_col = MAXCOL;
     xim_has_preediting = FALSE;
 
 #if 0
@@ -5092,9 +5202,14 @@ im_preedit_changed_cb(GtkIMContext *cont
     char_u  *p;
     int	    i;
 
-    gtk_im_context_get_preedit_string(context,
-				      &preedit_string, NULL,
-				      &cursor_index);
+    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);
@@ -5102,66 +5217,82 @@ im_preedit_changed_cb(GtkIMContext *cont
 
     g_return_if_fail(preedit_string != NULL); /* just in case */
 
-    /* If preedit_start_col is MAXCOL set it to the current cursor position. */
-    if (preedit_start_col == MAXCOL && preedit_string[0] != '\0')
+    if (p_imst == IM_OVER_THE_SPOT)
     {
-	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;
+	if (preedit_string[0] == NUL)
+	{
+	    xim_has_preediting = FALSE;
+	    im_delete_preedit();
+	}
+	else
+	{
+	    xim_has_preediting = TRUE;
+	    im_show_preedit();
+	}
     }
-
-    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)
+    else
     {
-	int is_composing;
-
-	is_composing = ((*p & 0x80) != 0 && utf_iscomposing(utf_ptr2char(p)));
+	/* 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();
+
 	/*
-	 * 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.
+	 * 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 (!is_composing || p_deco)
-	{
-	    if (i < cursor_index)
-		++im_preedit_cursor;
-	    else
-		++im_preedit_trailing;
-	}
-	if (!is_composing && i >= cursor_index)
+	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)
 	{
-	    /* This is essentially the same as im_preedit_trailing, except
-	     * composing characters are not counted even if p_deco is set. */
-	    ++num_move_back;
+	    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 (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);
+
+	if (p > str)
+	{
+	    im_add_to_input(str, (int)(p - str));
+	    im_correct_cursor(num_move_back);
+	}
     }
 
     g_free(preedit_string);
@@ -5310,7 +5441,8 @@ im_shutdown(void)
     }
     im_is_active = FALSE;
     im_commit_handler_id = 0;
-    preedit_start_col = MAXCOL;
+    if (p_imst == IM_ON_THE_SPOT)
+	preedit_start_col = MAXCOL;
     xim_has_preediting = FALSE;
 }
 
@@ -5465,7 +5597,8 @@ xim_reset(void)
 	}
     }
 
-    preedit_start_col = MAXCOL;
+    if (p_imst == IM_ON_THE_SPOT)
+	preedit_start_col = MAXCOL;
     xim_has_preediting = FALSE;
 }
 
@@ -5570,19 +5703,22 @@ xim_queue_key_press_event(GdkEventKey *e
 	{
 	    int imresult = gtk_im_context_filter_keypress(xic, event);
 
-	    /* 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)
+	    if (p_imst == IM_ON_THE_SPOT)
 	    {
-		im_synthesize_keypress(GDK_Return, 0U);
-		return FALSE;
+		/* 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.,