]> git.eshelyaron.com Git - emacs.git/commitdiff
implement pre-edit input method
authorYuuki Harano <masm+github@masm11.me>
Wed, 13 Nov 2019 14:36:27 +0000 (23:36 +0900)
committerJeff Walsh <fejfighter@gmail.com>
Sun, 22 Nov 2020 03:46:56 +0000 (14:46 +1100)
* src/termhooks.h (enum event_kind): new pgtk value

* src/pgtkterm.h (struct pgtk_display_info):

* src/pgtkterm.c (x_free_frame_resources, pgtk_delete_terminal)
(pgtk_enqueue_string, pgtk_enqueue_preedit, key_press_event)
(focus_in_event, focus_out_event, pgtk_term_init): call pgtkim

* src/pgtkim.c: new file
(im_context_commit_cb, im_context_retrieve_surrounding_cb)
(make_color_string, im_context_preedit_changed_cb)
(im_context_preedit_end_cb, im_context_preedit_start_cb)
(pgtk_im_focus_in, pgtk_im_focus_out, pgtk_im_filter_keypress)
(pgtk_im_finish, Fpgtk_use_im_context, syms_of_pgtkim):

* src/pgtkfns.c (check_pgtk_display_info):

* src/keyboard.c (kbd_buffer_get_event, make_lispy_event)
(keys_of_keyboard):

* src/emacs.c (main): add syms_of_pgtkim

* lisp/term/pgtk-win.el (pgtk-preedit-text): new defun

* configure.ac (XWIDGETS_OBJ): add pgtkim.o

input method 対応

preedit を overlay で表示するようにした。

preedit 表示後すぐに反映されなかったのを修正

この機能を ON/OFF できるようにした。

default off.

確定と同時に次の preedit が始まった場合に表示位置がおかしかったのを修正。

sample.

preedit をテキストで渡すようにした

configure.ac
lisp/term/pgtk-win.el
src/emacs.c
src/keyboard.c
src/pgtkfns.c
src/pgtkim.c [new file with mode: 0644]
src/pgtkterm.c
src/pgtkterm.h
src/termhooks.h

index 6d3652d97e18014c9f06a52cb1a99ba8e5be9a70..6f5ad44295cd4c602f1f1338df2fca1f69271c43 100644 (file)
@@ -2830,7 +2830,7 @@ LIBS=$OLD_LIBS
 PGTK_OBJ=
 PGTK_LIBS=
 if test "$window_system" = "pgtk"; then
-  PGTK_OBJ="pgtkfns.o pgtkterm.o pgtkselect.o pgtkmenu.o xsettings.o"
+  PGTK_OBJ="pgtkfns.o pgtkterm.o pgtkselect.o pgtkmenu.o pgtkim.o xsettings.o"
   PGTK_LIBS="$GTK_LIBS -ldl"
   AC_DEFINE([HAVE_PGTK], 1, [Define to 1 if you have pure Gtk+-3.])
 fi
index 1cf526b75877805529a59b0a73ed4e77b294ab34..2205ad662b3f6f54b17f173a947e0af6051cf1e4 100644 (file)
@@ -368,6 +368,39 @@ See the documentation of `create-fontset-from-fontset-spec' for the format.")
                                          &context (window-system pgtk))
   (pgtk-get-selection-internal selection-symbol target-type))
 
+
+(defvar pgtk-preedit-overlay nil)
+
+(defun pgtk-preedit-text (e)
+  (interactive "e")
+  (when pgtk-preedit-overlay
+    (delete-overlay pgtk-preedit-overlay))
+  (setq pgtk-preedit-overlay nil)
+
+  (let ((ovstr "")
+        (idx 0)
+        atts ov str color face-name)
+    (dolist (part (nth 1 e))
+      (setq str (car part))
+      (setq face-name (intern (format "pgtk-im-%d" idx)))
+      (eval
+       `(defface ,face-name nil "face of input method preedit"))
+      (setq atts nil)
+      (when (setq color (cdr-safe (assq 'fg (cdr part))))
+        (setq atts (append atts `(:foreground ,color))))
+      (when (setq color (cdr-safe (assq 'bg (cdr part))))
+        (setq atts (append atts `(:background ,color))))
+      (when (setq color (cdr-safe (assq 'ul (cdr part))))
+        (setq atts (append atts `(:underline ,color))))
+      (face-spec-set face-name `((t . ,atts)))
+      (add-text-properties 0 (length str) `(face ,face-name) str)
+      (setq ovstr (concat ovstr str))
+      (setq idx (1+ idx)))
+
+    (setq ov (make-overlay (point) (point)))
+    (overlay-put ov 'before-string ovstr)
+    (setq pgtk-preedit-overlay ov)))
+
 (provide 'pgtk-win)
 (provide 'term/pgtk-win)
 
index 42d93737b93c5bc95cbb789347874f3d9a63ad2c..ba5c64a15256512b74ed1cc52118168a8ca9e4fa 100644 (file)
@@ -1922,6 +1922,7 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
       syms_of_pgtkfns();
       syms_of_pgtkselect ();
       syms_of_pgtkmenu ();
+      syms_of_pgtkim ();
       syms_of_fontset ();
       syms_of_xsettings ();
       syms_of_xwidget ();
index 49261fcc3e8b66a2624f52a65fcdb63c840e3efc..f6c44a11fb9a12140049292e6ac9541af1e6eb54 100644 (file)
@@ -3939,6 +3939,9 @@ kbd_buffer_get_event (KBOARD **kbp,
          *used_mouse_menu = true;
        FALLTHROUGH;
 #endif
+#ifdef HAVE_PGTK
+      case PGTK_PREEDIT_TEXT_EVENT:
+#endif
 #ifdef HAVE_NTGUI
       case END_SESSION_EVENT:
       case LANGUAGE_CHANGE_EVENT:
@@ -6047,6 +6050,11 @@ make_lispy_event (struct input_event *event)
        return list3 (Qconfig_changed_event,
                      event->arg, event->frame_or_window);
 
+#ifdef HAVE_PGTK
+    case PGTK_PREEDIT_TEXT_EVENT:
+      return list2 (intern("pgtk-preedit-text"), event->arg);
+#endif
+
       /* The 'kind' field of the event is something we don't recognize.  */
     default:
       emacs_abort ();
@@ -12451,6 +12459,8 @@ keys_of_keyboard (void)
                            "ns-put-working-text");
   initial_define_lispy_key (Vspecial_event_map, "ns-unput-working-text",
                            "ns-unput-working-text");
+  initial_define_lispy_key (Vspecial_event_map, "pgtk-preedit-text",
+                           "pgtk-preedit-text");
   /* Here we used to use `ignore-event' which would simple set prefix-arg to
      current-prefix-arg, as is done in `handle-switch-frame'.
      But `handle-switch-frame is not run from the special-map.
index 090ebf5b36393cb327c5576e16d2b3a694b39d91..2525547c3f7c5e316618185e1f736165c66c577b 100644 (file)
@@ -60,7 +60,7 @@ static const char *pgtk_app_name = "Emacs";
 
    ========================================================================== */
 
-static struct pgtk_display_info *
+struct pgtk_display_info *
 check_pgtk_display_info (Lisp_Object object)
 {
   struct pgtk_display_info *dpyinfo = NULL;
diff --git a/src/pgtkim.c b/src/pgtkim.c
new file mode 100644 (file)
index 0000000..d9bba7e
--- /dev/null
@@ -0,0 +1,243 @@
+/* Pure Gtk+-3 communication module.      -*- coding: utf-8 -*-
+
+Copyright (C) 1989, 1993-1994, 2005-2006, 2008-2018 Free Software
+Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+/* This should be the first include, as it may set up #defines affecting
+   interpretation of even the system includes. */
+#include <config.h>
+
+#include "pgtkterm.h"
+
+static void im_context_commit_cb(GtkIMContext *imc, gchar *str, gpointer user_data)
+{
+  struct pgtk_display_info *dpyinfo = user_data;
+  struct frame *f = dpyinfo->im.focused_frame;
+
+  if (dpyinfo->im.context == NULL)
+    return;
+  if (f == NULL)
+    return;
+
+  pgtk_enqueue_string(f, str);
+}
+
+static gboolean im_context_retrieve_surrounding_cb(GtkIMContext *imc, gpointer user_data)
+{
+  gtk_im_context_set_surrounding(imc, "", -1, 0);
+  return TRUE;
+}
+
+static gboolean im_context_delete_surrounding_cb(GtkIMContext *imc, int offset, int n_chars, gpointer user_data)
+{
+  return TRUE;
+}
+
+static Lisp_Object make_color_string(PangoAttrColor *pac)
+{
+  char buf[256];
+  sprintf(buf, "#%02x%02x%02x",
+         pac->color.red >> 8,
+         pac->color.green >> 8,
+         pac->color.blue >> 8);
+  return build_string(buf);
+}
+
+static void im_context_preedit_changed_cb(GtkIMContext *imc, gpointer user_data)
+{
+  struct pgtk_display_info *dpyinfo = user_data;
+  struct frame *f = dpyinfo->im.focused_frame;
+  char *str;
+  PangoAttrList *attrs;
+  int pos;
+
+  if (dpyinfo->im.context == NULL)
+    return;
+  if (f == NULL)
+    return;
+
+  gtk_im_context_get_preedit_string(imc, &str, &attrs, &pos);
+
+
+  /*
+   * (
+   *   (TEXT (ul . COLOR) (bg . COLOR) (fg . COLOR))
+   *   ...
+   * )
+   */
+  Lisp_Object list = Qnil;
+
+  PangoAttrIterator* iter;
+  iter = pango_attr_list_get_iterator(attrs);
+  do {
+    int st, ed;
+    int has_underline = 0;
+    Lisp_Object part = Qnil;
+
+    pango_attr_iterator_range(iter, &st, &ed);
+
+    if (ed > strlen(str))
+      ed = strlen(str);
+    if (st >= ed)
+      continue;
+
+    Lisp_Object text = make_string(str + st, ed - st);
+    part = Fcons(text, part);
+
+    PangoAttrInt *ul = (PangoAttrInt *) pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE);
+    if (ul != NULL) {
+      if (ul->value != PANGO_UNDERLINE_NONE)
+       has_underline = 1;
+    }
+
+    PangoAttrColor *pac;
+    if (has_underline) {
+      pac = (PangoAttrColor *) pango_attr_iterator_get(iter, PANGO_ATTR_UNDERLINE_COLOR);
+      if (pac != NULL)
+       part = Fcons(Fcons(Qul, make_color_string(pac)), part);
+      else
+       part = Fcons(Fcons(Qul, Qt), part);
+    }
+
+    pac = (PangoAttrColor *) pango_attr_iterator_get(iter, PANGO_ATTR_FOREGROUND);
+    if (pac != NULL)
+      part = Fcons(Fcons(Qfg, make_color_string(pac)), part);
+
+    pac = (PangoAttrColor *) pango_attr_iterator_get(iter, PANGO_ATTR_BACKGROUND);
+    if (pac != NULL)
+      part = Fcons(Fcons(Qbg, make_color_string(pac)), part);
+
+    part = Fnreverse(part);
+    list = Fcons(part, list);
+  } while (pango_attr_iterator_next(iter));
+
+  list = Fnreverse(list);
+  pgtk_enqueue_preedit(f, list);
+
+  g_free(str);
+  pango_attr_list_unref(attrs);
+}
+
+static void im_context_preedit_end_cb(GtkIMContext *imc, gpointer user_data)
+{
+  struct pgtk_display_info *dpyinfo = user_data;
+  struct frame *f = dpyinfo->im.focused_frame;
+
+  if (dpyinfo->im.context == NULL)
+    return;
+  if (f == NULL)
+    return;
+
+  pgtk_enqueue_preedit(f, Qnil);
+}
+
+static void im_context_preedit_start_cb(GtkIMContext *imc, gpointer user_data)
+{
+}
+
+void pgtk_im_focus_in(struct frame *f)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  if (dpyinfo->im.context != NULL) {
+    gtk_im_context_reset (dpyinfo->im.context);
+    gtk_im_context_set_client_window (dpyinfo->im.context, gtk_widget_get_window (FRAME_GTK_WIDGET (f)));
+    gtk_im_context_focus_in (dpyinfo->im.context);
+  }
+  dpyinfo->im.focused_frame = f;
+}
+
+void pgtk_im_focus_out(struct frame *f)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  if (dpyinfo->im.focused_frame == f) {
+    if (dpyinfo->im.context != NULL) {
+      gtk_im_context_reset (dpyinfo->im.context);
+      gtk_im_context_focus_out (dpyinfo->im.context);
+      gtk_im_context_set_client_window (dpyinfo->im.context, NULL);
+    }
+    dpyinfo->im.focused_frame = NULL;
+  }
+}
+
+bool pgtk_im_filter_keypress(struct frame *f, GdkEventKey *ev)
+{
+  struct pgtk_display_info *dpyinfo = FRAME_DISPLAY_INFO (f);
+  if (dpyinfo->im.context != NULL) {
+    if (gtk_im_context_filter_keypress (dpyinfo->im.context, ev))
+      return true;
+  }
+  return false;
+}
+
+void pgtk_im_init(struct pgtk_display_info *dpyinfo)
+{
+  dpyinfo->im.context = NULL;
+}
+
+void pgtk_im_finish(struct pgtk_display_info *dpyinfo)
+{
+  if (dpyinfo->im.context != NULL)
+    g_object_unref(dpyinfo->im.context);
+  dpyinfo->im.context = NULL;
+}
+
+DEFUN ("pgtk-use-im-context", Fpgtk_use_im_context, Spgtk_use_im_context,
+       1, 2, 0,
+       doc: /* Set whether use Gtk's im context. */)
+  (Lisp_Object use_p, Lisp_Object terminal)
+{
+  struct pgtk_display_info *dpyinfo = check_pgtk_display_info (terminal);
+
+  if (NILP(use_p)) {
+    if (dpyinfo->im.context != NULL) {
+      gtk_im_context_reset (dpyinfo->im.context);
+      gtk_im_context_focus_out (dpyinfo->im.context);
+      gtk_im_context_set_client_window (dpyinfo->im.context, NULL);
+
+      g_object_unref(dpyinfo->im.context);
+      dpyinfo->im.context = NULL;
+    }
+  } else {
+    if (dpyinfo->im.context == NULL) {
+      dpyinfo->im.context = gtk_im_multicontext_new();
+      g_signal_connect(dpyinfo->im.context, "commit", G_CALLBACK(im_context_commit_cb), dpyinfo);
+      g_signal_connect(dpyinfo->im.context, "retrieve-surrounding", G_CALLBACK(im_context_retrieve_surrounding_cb), dpyinfo);
+      g_signal_connect(dpyinfo->im.context, "delete-surrounding", G_CALLBACK(im_context_delete_surrounding_cb), dpyinfo);
+      g_signal_connect(dpyinfo->im.context, "preedit-changed", G_CALLBACK(im_context_preedit_changed_cb), dpyinfo);
+      g_signal_connect(dpyinfo->im.context, "preedit-end", G_CALLBACK(im_context_preedit_end_cb), dpyinfo);
+      g_signal_connect(dpyinfo->im.context, "preedit-start", G_CALLBACK(im_context_preedit_start_cb), dpyinfo);
+      gtk_im_context_set_use_preedit (dpyinfo->im.context, TRUE);
+
+      if (dpyinfo->im.focused_frame)
+       pgtk_im_focus_in(dpyinfo->im.focused_frame);
+    }
+  }
+
+  return Qnil;
+}
+
+void
+syms_of_pgtkim (void)
+{
+  defsubr (&Spgtk_use_im_context);
+
+  DEFSYM (Qpgtk_refresh_preedit, "pgtk-refresh-preedit");
+  DEFSYM (Qul, "ul");
+  DEFSYM (Qfg, "fg");
+  DEFSYM (Qbg, "bg");
+}
index fab6e4eed98ae3bfb99a66506f47a78948ef5997..67ff37863322fcfb95829cfb4f9bb8ef1f552a33 100644 (file)
@@ -188,6 +188,7 @@ x_free_frame_resources (struct frame *f)
   CLEAR_IF_EQ(last_mouse_frame);
   CLEAR_IF_EQ(last_mouse_motion_frame);
   CLEAR_IF_EQ(last_mouse_glyph_frame);
+  CLEAR_IF_EQ(im.focused_frame);
 
 #undef CLEAR_IF_EQ
 
@@ -4282,6 +4283,8 @@ pgtk_delete_terminal (struct terminal *terminal)
 
   block_input ();
 
+  pgtk_im_finish (dpyinfo);
+
   /* Normally, the display is available...  */
   if (dpyinfo->gdpy)
     {
@@ -4952,6 +4955,45 @@ pgtk_emacs_to_gtk_modifiers (struct pgtk_display_info *dpyinfo, int state)
 #define IsKeypadKey(keysym)       (0xff80 <= (keysym) && (keysym) < 0xffbe)
 #define IsFunctionKey(keysym)     (0xffbe <= (keysym) && (keysym) < 0xffe1)
 
+void
+pgtk_enqueue_string(struct frame *f, gchar *str)
+{
+  gunichar *ustr;
+
+  ustr = g_utf8_to_ucs4 (str, -1, NULL, NULL, NULL);
+  if (ustr == NULL)
+    return;
+  for ( ; *ustr != 0; ustr++) {
+    union buffered_input_event inev;
+    Lisp_Object c = make_fixnum (*ustr);
+    EVENT_INIT (inev.ie);
+    inev.ie.kind = (SINGLE_BYTE_CHAR_P (XFIXNAT (c))
+                   ? ASCII_KEYSTROKE_EVENT
+                   : MULTIBYTE_CHAR_KEYSTROKE_EVENT);
+    inev.ie.arg = Qnil;
+    inev.ie.code = XFIXNAT (c);
+    XSETFRAME (inev.ie.frame_or_window, f);
+    inev.ie.modifiers = 0;
+    inev.ie.timestamp = 0;
+    evq_enqueue (&inev);
+  }
+
+}
+
+void
+pgtk_enqueue_preedit(struct frame *f, Lisp_Object preedit)
+{
+  union buffered_input_event inev;
+  EVENT_INIT (inev.ie);
+  inev.ie.kind = PGTK_PREEDIT_TEXT_EVENT;
+  inev.ie.arg = preedit;
+  inev.ie.code = 0;
+  XSETFRAME (inev.ie.frame_or_window, f);
+  inev.ie.modifiers = 0;
+  inev.ie.timestamp = 0;
+  evq_enqueue (&inev);
+}
+
 static gboolean key_press_event(GtkWidget *widget, GdkEvent *event, gpointer *user_data)
 {
   struct coding_system coding;
@@ -4978,6 +5020,11 @@ static gboolean key_press_event(GtkWidget *widget, GdkEvent *event, gpointer *us
       hlinfo->mouse_face_hidden = true;
     }
 
+  if (f != 0) {
+    if (pgtk_im_filter_keypress (f, &event->key))
+      return TRUE;
+  }
+
   if (f != 0)
     {
       guint keysym, orig_keysym;
@@ -5462,6 +5509,9 @@ focus_in_event(GtkWidget *widget, GdkEvent *event, gpointer *user_data)
                   FRAME_DISPLAY_INFO(frame), frame, &inev);
   if (inev.ie.kind != NO_EVENT)
     evq_enqueue (&inev);
+
+  pgtk_im_focus_in (frame);
+
   return TRUE;
 }
 
@@ -5483,6 +5533,9 @@ focus_out_event(GtkWidget *widget, GdkEvent *event, gpointer *user_data)
                   FRAME_DISPLAY_INFO(frame), frame, &inev);
   if (inev.ie.kind != NO_EVENT)
     evq_enqueue(&inev);
+
+  pgtk_im_focus_out (frame);
+
   return TRUE;
 }
 
@@ -6219,6 +6272,8 @@ pgtk_term_init (Lisp_Object display_name, char *resource_name)
 
   pgtk_selection_init();
 
+  pgtk_im_init (dpyinfo);
+
   unblock_input ();
 
   return dpyinfo;
index a2ba627425cd8e217958dfe311ad83e9ec4f9e3f..694a85bac2e637ce57282905d7867fe56876ea2a 100644 (file)
@@ -226,6 +226,14 @@ struct pgtk_display_info
 
   /* Modifier masks in gdk */
   int meta_mod_mask, alt_mod_mask;
+
+  /* whether to use Gtk's IM context. */
+
+  /* input method */
+  struct {
+    GtkIMContext *context;
+    struct frame *focused_frame;
+  } im;
 };
 
 /* This is a chain of structures for all the PGTK displays currently in use.  */
@@ -579,6 +587,7 @@ extern void syms_of_pgtkterm (void);
 extern void syms_of_pgtkfns (void);
 extern void syms_of_pgtkmenu (void);
 extern void syms_of_pgtkselect (void);
+extern void syms_of_pgtkim (void);
 
 /* Implemented in pgtkselect. */
 extern void nxatoms_of_pgtkselect (void);
@@ -602,4 +611,14 @@ extern void pgtk_frame_rehighlight (struct pgtk_display_info *dpyinfo);
 
 extern void x_change_tab_bar_height (struct frame *, int);
 
+extern struct pgtk_display_info *check_pgtk_display_info (Lisp_Object object);
+
+extern void pgtk_enqueue_string(struct frame *f, gchar *str);
+extern void pgtk_enqueue_preedit(struct frame *f, Lisp_Object image_data);
+extern void pgtk_im_focus_in(struct frame *f);
+extern void pgtk_im_focus_out(struct frame *f);
+extern bool pgtk_im_filter_keypress(struct frame *f, GdkEventKey *ev);
+extern void pgtk_im_init(struct pgtk_display_info *dpyinfo);
+extern void pgtk_im_finish(struct pgtk_display_info *dpyinfo);
+
 #endif /* HAVE_PGTK */
index 4403d518ff003ab68cd454b638ff9e3907a35c0f..c28c3fbbd0212ade6bb502032092b4b80d2c29d2 100644 (file)
@@ -264,6 +264,10 @@ enum event_kind
   , FILE_NOTIFY_EVENT
 #endif
 
+#ifdef HAVE_PGTK
+  /* Pre-edit text was changed. */
+  , PGTK_PREEDIT_TEXT_EVENT
+#endif
 };
 
 /* Bit width of an enum event_kind tag at the start of structs and unions.  */