Support surrounding-text retrieval.
authorDaiki Ueno <daiki.ueno@gmail.com>
Tue, 5 Apr 2011 14:11:49 +0000 (10:11 -0400)
committerPeng Huang <shawn.p.huang@gmail.com>
Tue, 5 Apr 2011 14:11:49 +0000 (10:11 -0400)
Unlike the GtkIMContext API, IBus automatically retrieves
surrounding-text when certain events occurred to the
client ("before filter_keypress", for example).  This makes the
engine API simpler but causes periodical emission of D-Bus
signals for updating surrounding-text information, which is
unwanted for typical engines.

For this reason, the surrounding-text support is currently
disabled by default.  To enable it, pass
--enable-surrounding-text to configure.  Also, even
surrounding-text support is compiled in, IBus does not start
polling until an engine requests surrounding-text using
ibus_engine_get_surrounding_text().  To make the function work
for the first time, clients should retrieve
initial surrounding-text when the engine is enabled (see ibus_im_context_focus_in() and
_ibus_context_enabled_cb() in client/gtk2/ibusimcontext.c).

BUG=Issue#778
TEST=manual

Review URL: http://codereview.appspot.com/4276082
Patch from Daiki Ueno <daiki.ueno@gmail.com>.

13 files changed:
bus/engineproxy.c
bus/engineproxy.h
bus/inputcontext.c
client/gtk2/ibusimcontext.c
configure.ac
ibus/engine.py
ibus/interface/iengine.py
ibus/interface/iinputcontext.py
src/ibusengine.c
src/ibusengine.h
src/ibusinputcontext.c
src/ibusinputcontext.h
src/ibusmarshalers.list

index d37e9e9..33ebf2a 100644 (file)
@@ -47,6 +47,11 @@ struct _BusEngineProxy {
     /* a key mapping for the engine that converts keycode into keysym. the mapping is used only when use_sys_layout is FALSE. */
     IBusKeymap     *keymap;
     /* private member */
+
+    /* cached surrounding text (see also IBusEnginePrivate and
+       IBusInputContextPrivate) */
+    IBusText *surrounding_text;
+    guint     surrounding_cursor_pos;
 };
 
 struct _BusEngineProxyClass {
@@ -58,6 +63,7 @@ enum {
     COMMIT_TEXT,
     FORWARD_KEY_EVENT,
     DELETE_SURROUNDING_TEXT,
+    REQUIRE_SURROUNDING_TEXT,
     UPDATE_PREEDIT_TEXT,
     SHOW_PREEDIT_TEXT,
     HIDE_PREEDIT_TEXT,
@@ -83,6 +89,8 @@ enum {
 
 static guint    engine_signals[LAST_SIGNAL] = { 0 };
 
+static IBusText *text_empty = NULL;
+
 /* functions prototype */
 static void     bus_engine_proxy_set_property   (BusEngineProxy      *engine,
                                                  guint                prop_id,
@@ -171,6 +179,16 @@ bus_engine_proxy_class_init (BusEngineProxyClass *class)
             G_TYPE_INT,
             G_TYPE_UINT);
 
+    engine_signals[REQUIRE_SURROUNDING_TEXT] =
+        g_signal_new (I_("require-surrounding-text"),
+            G_TYPE_FROM_CLASS (class),
+            G_SIGNAL_RUN_LAST,
+            0,
+            NULL, NULL,
+            bus_marshal_VOID__VOID,
+            G_TYPE_NONE,
+            0);
+
     engine_signals[UPDATE_PREEDIT_TEXT] =
         g_signal_new (I_("update-preedit-text"),
             G_TYPE_FROM_CLASS (class),
@@ -330,11 +348,16 @@ bus_engine_proxy_class_init (BusEngineProxyClass *class)
             G_TYPE_NONE,
             1,
             IBUS_TYPE_PROPERTY);
+
+    text_empty = ibus_text_new_from_static_string ("");
+    g_object_ref_sink (text_empty);
 }
 
 static void
 bus_engine_proxy_init (BusEngineProxy *engine)
 {
+    engine->surrounding_text = g_object_ref_sink (text_empty);
+    engine->surrounding_cursor_pos = 0;
 }
 
 static void
@@ -393,6 +416,11 @@ bus_engine_proxy_real_destroy (IBusProxy *proxy)
         engine->keymap = NULL;
     }
 
+    if (engine->surrounding_text) {
+        g_object_unref (engine->surrounding_text);
+        engine->surrounding_text = NULL;
+    }
+
     IBUS_PROXY_CLASS (bus_engine_proxy_parent_class)->destroy ((IBusProxy *)engine);
 }
 
@@ -431,6 +459,7 @@ bus_engine_proxy_g_signal (GDBusProxy  *proxy,
         { "PageDownLookupTable",    PAGE_DOWN_LOOKUP_TABLE },
         { "CursorUpLookupTable",    CURSOR_UP_LOOKUP_TABLE },
         { "CursorDownLookupTable",  CURSOR_DOWN_LOOKUP_TABLE },
+        { "RequireSurroundingText", REQUIRE_SURROUNDING_TEXT },
     };
 
     gint i;
@@ -985,6 +1014,33 @@ void bus_engine_proxy_property_hide (BusEngineProxy *engine,
                        NULL);
 }
 
+void bus_engine_proxy_set_surrounding_text (BusEngineProxy *engine,
+                                            IBusText       *text,
+                                            guint           cursor_pos)
+{
+    g_assert (BUS_IS_ENGINE_PROXY (engine));
+    g_assert (text != NULL);
+
+    if (!engine->surrounding_text ||
+        g_strcmp0 (text->text, engine->surrounding_text->text) != 0 ||
+        cursor_pos != engine->surrounding_cursor_pos) {
+        GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text);
+        if (engine->surrounding_text)
+            g_object_unref (engine->surrounding_text);
+        engine->surrounding_text = (IBusText *) g_object_ref_sink (text);
+        engine->surrounding_cursor_pos = cursor_pos;
+
+        g_dbus_proxy_call ((GDBusProxy *)engine,
+                           "SetSurroundingText",
+                           g_variant_new ("(vu)", variant, cursor_pos),
+                           G_DBUS_CALL_FLAGS_NONE,
+                           -1,
+                           NULL,
+                           NULL,
+                           NULL);
+    }
+}
+
 /* a macro to generate a function to call a nullary D-Bus method. */
 #define DEFINE_FUNCTION(Name, name)                         \
     void                                                    \
index 2a82fc6..0680917 100644 (file)
@@ -212,5 +212,16 @@ void             bus_engine_proxy_property_hide     (BusEngineProxy *engine,
  */
 gboolean         bus_engine_proxy_is_enabled        (BusEngineProxy *engine);
 
+/**
+ * bus_engine_proxy_set_surrounding_text:
+ *
+ * Call "SetSurroundingText" method of an engine asynchronously.
+ */
+void             bus_engine_proxy_set_surrounding_text
+                                                    (BusEngineProxy *engine,
+                                                     IBusText       *text,
+                                                     guint           cursor_pos);
+
+
 G_END_DECLS
 #endif
index f36a73f..fa14fe3 100644 (file)
@@ -259,6 +259,11 @@ static const gchar introspection_xml[] =
     "    <method name='GetEngine'>"
     "      <arg direction='out' type='v' name='desc' />"
     "    </method>"
+    "    <method name='SetSurroundingText'>"
+    "      <arg direction='in' type='v' name='text' />"
+    "      <arg direction='in' type='u' name='cursor_pos' />"
+    "    </method>"
+
     /* signals */
     "    <signal name='CommitText'>"
     "      <arg type='v' name='text' />"
@@ -1011,6 +1016,32 @@ _ic_get_engine (BusInputContext       *context,
  * Handle a D-Bus method call whose destination and interface name are both "org.freedesktop.IBus.InputContext"
  */
 static void
+_ic_set_surrounding_text (BusInputContext       *context,
+                          GVariant              *parameters,
+                          GDBusMethodInvocation *invocation)
+{
+    GVariant *variant = NULL;
+    IBusText *text;
+    guint cursor_pos = 0;
+
+    g_variant_get (parameters, "(vu)", &variant, &cursor_pos);
+    text = IBUS_TEXT (ibus_serializable_deserialize (variant));
+    g_variant_unref (variant);
+
+    if ((context->capabilities & IBUS_CAP_SURROUNDING_TEXT) &&
+         context->has_focus && context->enabled && context->engine) {
+        bus_engine_proxy_set_surrounding_text (context->engine,
+                                               text,
+                                               cursor_pos);
+    }
+
+    if (g_object_is_floating (text))
+        g_object_unref (text);
+
+    g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+static void
 bus_input_context_service_method_call (IBusService            *service,
                                        GDBusConnection        *connection,
                                        const gchar            *sender,
@@ -1049,6 +1080,7 @@ bus_input_context_service_method_call (IBusService            *service,
         { "IsEnabled",         _ic_is_enabled },
         { "SetEngine",         _ic_set_engine },
         { "GetEngine",         _ic_get_engine },
+        { "SetSurroundingText", _ic_set_surrounding_text},
     };
 
     gint i;
@@ -1787,6 +1819,29 @@ _engine_delete_surrounding_text_cb (BusEngineProxy    *engine,
 }
 
 /**
+ * _engine_require_surrounding_text_cb:
+ *
+ * A function to be called when "require-surrounding-text" glib signal is sent to the engine object.
+ */
+static void
+_engine_require_surrounding_text_cb (BusEngineProxy    *engine,
+                                     BusInputContext   *context)
+{
+    g_assert (BUS_IS_ENGINE_PROXY (engine));
+    g_assert (BUS_IS_INPUT_CONTEXT (context));
+
+    g_assert (context->engine == engine);
+
+    if (!context->enabled)
+        return;
+
+    bus_input_context_emit_signal (context,
+                                   "RequireSurroundingText",
+                                   NULL,
+                                   NULL);
+}
+
+/**
  * _engine_update_preedit_text_cb:
  *
  * A function to be called when "update-preedit-text" glib signal is sent to the engine object.
@@ -2060,6 +2115,7 @@ const static struct {
     { "commit-text",              G_CALLBACK (_engine_commit_text_cb) },
     { "forward-key-event",        G_CALLBACK (_engine_forward_key_event_cb) },
     { "delete-surrounding-text",  G_CALLBACK (_engine_delete_surrounding_text_cb) },
+    { "require-surrounding-text", G_CALLBACK (_engine_require_surrounding_text_cb) },
     { "update-preedit-text",      G_CALLBACK (_engine_update_preedit_text_cb) },
     { "show-preedit-text",        G_CALLBACK (_engine_show_preedit_text_cb) },
     { "hide-preedit-text",        G_CALLBACK (_engine_hide_preedit_text_cb) },
index 0cb9c31..dcd356f 100644 (file)
@@ -24,6 +24,7 @@
 #  include <config.h>
 #endif
 
+#include <string.h>
 #include <gtk/gtk.h>
 #include <gdk/gdkkeysyms.h>
 #include <ibus.h>
@@ -114,6 +115,12 @@ static void     ibus_im_context_set_cursor_location
 static void     ibus_im_context_set_use_preedit
                                             (GtkIMContext           *context,
                                              gboolean               use_preedit);
+static void     ibus_im_context_set_surrounding
+                                            (GtkIMContext  *slave,
+                                             const gchar   *text,
+                                             gint           len,
+                                             gint           cursor_index);
+
 
 /* static methods*/
 static void     _create_input_context       (IBusIMContext      *context);
@@ -132,14 +139,16 @@ static void     _slave_preedit_start_cb     (GtkIMContext       *slave,
                                              IBusIMContext       *context);
 static void     _slave_preedit_end_cb       (GtkIMContext       *slave,
                                              IBusIMContext       *context);
-static void     _slave_retrieve_surrounding_cb
+static gboolean _slave_retrieve_surrounding_cb
                                             (GtkIMContext       *slave,
-                                             IBusIMContext       *context);
-static void     _slave_delete_surrounding_cb
+                                             IBusIMContext      *context);
+static gboolean _slave_delete_surrounding_cb
                                             (GtkIMContext       *slave,
-                                             gint               offset_from_cursor,
-                                             guint              nchars,
-                                             IBusIMContext       *context);
+                                             gint                offset_from_cursor,
+                                             guint               nchars,
+                                             IBusIMContext      *context);
+static void     _request_surrounding_text   (IBusIMContext      *context,
+                                             gboolean            force);
 static void     _create_fake_input_context  (void);
 
 
@@ -251,6 +260,28 @@ _process_key_event_done (GObject      *object,
 }
 
 
+/* emit "retrieve-surrounding" glib signal of GtkIMContext, if
+ * context->caps has IBUS_CAP_SURROUNDING_TEXT and the current IBus
+ * engine needs surrounding-text.
+ *
+ * if "force" is TRUE, emit the signal regardless of whether the
+ * engine needs surrounding-text.
+ */
+static void
+_request_surrounding_text (IBusIMContext *context, gboolean force)
+{
+    if (context->enable &&
+        (context->caps & IBUS_CAP_SURROUNDING_TEXT) != 0 &&
+        (force ||
+         ibus_input_context_needs_surrounding_text (context->ibuscontext))) {
+        gboolean return_value;
+        IDEBUG ("requesting surrounding text");
+        g_signal_emit (context, _signal_retrieve_surrounding_id, 0,
+                       &return_value);
+    }
+}
+
+
 static gint
 _key_snooper_cb (GtkWidget   *widget,
                  GdkEventKey *event,
@@ -337,6 +368,8 @@ _key_snooper_cb (GtkWidget   *widget,
 
     } while (0);
 
+    _request_surrounding_text (ibusimcontext, FALSE);
+
     if (ibusimcontext != NULL) {
         ibusimcontext->time = event->time;
     }
@@ -414,6 +447,7 @@ ibus_im_context_class_init (IBusIMContextClass *class)
     im_context_class->set_client_window = ibus_im_context_set_client_window;
     im_context_class->set_cursor_location = ibus_im_context_set_cursor_location;
     im_context_class->set_use_preedit = ibus_im_context_set_use_preedit;
+    im_context_class->set_surrounding = ibus_im_context_set_surrounding;
     gobject_class->finalize = ibus_im_context_finalize;
 
     _signal_commit_id =
@@ -545,7 +579,11 @@ ibus_im_context_init (GObject *obj)
     ibusimcontext->ibuscontext = NULL;
     ibusimcontext->has_focus = FALSE;
     ibusimcontext->time = GDK_CURRENT_TIME;
+#ifdef ENABLE_SURROUNDING
     ibusimcontext->caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS | IBUS_CAP_SURROUNDING_TEXT;
+#else
+    ibusimcontext->caps = IBUS_CAP_PREEDIT_TEXT | IBUS_CAP_FOCUS;
+#endif
 
 
     // Create slave im context
@@ -642,6 +680,8 @@ ibus_im_context_filter_keypress (GtkIMContext *context,
         if (ibusimcontext->client_window == NULL && event->window != NULL)
             gtk_im_context_set_client_window ((GtkIMContext *)ibusimcontext, event->window);
 
+        _request_surrounding_text (ibusimcontext, FALSE);
+
         if (ibusimcontext != NULL) {
             ibusimcontext->time = event->time;
         }
@@ -721,6 +761,10 @@ ibus_im_context_focus_in (GtkIMContext *context)
                      g_object_ref (context),
                      (GDestroyNotify) g_object_unref);
 
+    /* retrieve the initial surrounding-text (regardless of whether
+     * the current IBus engine needs surrounding-text) */
+    _request_surrounding_text (ibusimcontext, TRUE);
+
     g_object_add_weak_pointer ((GObject *) context,
                                (gpointer *) &_focus_im_context);
     _focus_im_context = context;
@@ -899,12 +943,44 @@ ibus_im_context_set_use_preedit (GtkIMContext *context, gboolean use_preedit)
         else {
             ibusimcontext->caps &= ~IBUS_CAP_PREEDIT_TEXT;
         }
-        ibus_input_context_set_capabilities (ibusimcontext->ibuscontext, ibusimcontext->caps);
     }
     gtk_im_context_set_use_preedit (ibusimcontext->slave, use_preedit);
 }
 
 static void
+ibus_im_context_set_surrounding (GtkIMContext  *context,
+                                 const gchar   *text,
+                                 gint           len,
+                                 gint           cursor_index)
+{
+    g_return_if_fail (context != NULL);
+    g_return_if_fail (IBUS_IS_IM_CONTEXT (context));
+    g_return_if_fail (text != NULL);
+    g_return_if_fail (strlen (text) >= len);
+    g_return_if_fail (0 <= cursor_index && cursor_index <= len);
+
+    IBusIMContext *ibusimcontext = IBUS_IM_CONTEXT (context);
+
+    if (ibusimcontext->enable && ibusimcontext->ibuscontext) {
+        IBusText *ibustext;
+        guint cursor_pos;
+        gchar *p;
+
+        p = g_strndup (text, len);
+        cursor_pos = g_utf8_strlen (p, cursor_index);
+        ibustext = ibus_text_new_from_string (p);
+        g_free (p);
+        ibus_input_context_set_surrounding_text (ibusimcontext->ibuscontext,
+                                                 ibustext,
+                                                 cursor_pos);
+    }
+    gtk_im_context_set_surrounding (ibusimcontext->slave,
+                                    text,
+                                    len,
+                                    cursor_index);
+}
+
+static void
 _bus_connected_cb (IBusBus          *bus,
                    IBusIMContext    *ibusimcontext)
 {
@@ -923,6 +999,8 @@ _ibus_context_commit_text_cb (IBusInputContext *ibuscontext,
     IDEBUG ("%s", __FUNCTION__);
 
     g_signal_emit (ibusimcontext, _signal_commit_id, 0, text->text);
+
+    _request_surrounding_text (ibusimcontext, FALSE);
 }
 
 static gboolean
@@ -1217,6 +1295,8 @@ _ibus_context_show_preedit_text_cb (IBusInputContext   *ibuscontext,
     ibusimcontext->preedit_visible = TRUE;
     g_signal_emit (ibusimcontext, _signal_preedit_start_id, 0);
     g_signal_emit (ibusimcontext, _signal_preedit_changed_id, 0);
+
+    _request_surrounding_text (ibusimcontext, FALSE);
 }
 
 static void
@@ -1240,6 +1320,10 @@ _ibus_context_enabled_cb (IBusInputContext *ibuscontext,
     IDEBUG ("%s", __FUNCTION__);
 
     ibusimcontext->enable = TRUE;
+
+    /* retrieve the initial surrounding-text (regardless of whether
+     * the current IBus engine needs surrounding-text) */
+    _request_surrounding_text (ibusimcontext, TRUE);
 }
 
 static void
@@ -1417,17 +1501,21 @@ _slave_preedit_end_cb (GtkIMContext  *slave,
     g_signal_emit (ibusimcontext, _signal_preedit_end_id, 0);
 }
 
-static void
+static gboolean
 _slave_retrieve_surrounding_cb (GtkIMContext  *slave,
                                 IBusIMContext *ibusimcontext)
 {
+    gboolean return_value;
+
     if (ibusimcontext->enable && ibusimcontext->ibuscontext) {
-        return;
+        return FALSE;
     }
-    g_signal_emit (ibusimcontext, _signal_retrieve_surrounding_id, 0);
+    g_signal_emit (ibusimcontext, _signal_retrieve_surrounding_id, 0,
+                   &return_value);
+    return return_value;
 }
 
-static void
+static gboolean
 _slave_delete_surrounding_cb (GtkIMContext  *slave,
                               gint           offset_from_cursor,
                               guint          nchars,
@@ -1436,9 +1524,10 @@ _slave_delete_surrounding_cb (GtkIMContext  *slave,
     gboolean return_value;
 
     if (ibusimcontext->enable && ibusimcontext->ibuscontext) {
-        return;
+        return FALSE;
     }
     g_signal_emit (ibusimcontext, _signal_delete_surrounding_id, 0, offset_from_cursor, nchars, &return_value);
+    return return_value;
 }
 
 #ifdef OS_CHROMEOS
index cc9d1a2..5544dfa 100644 (file)
@@ -371,6 +371,19 @@ AC_ARG_WITH(panel-icon-keyboard,
 )
 AC_SUBST(IBUS_ICON_KEYBOARD)
 
+# option for enable surrounding-text
+AC_ARG_ENABLE(surrounding-text,
+    AS_HELP_STRING([--enable-surrounding-text],
+        [Enable surrounding-text support]),
+    [enable_surrounding_text=$enableval],
+    [enable_surrounding_text=no]
+)
+if test x"$enable_surrounding_text" = x"yes"; then
+    AC_DEFINE(ENABLE_SURROUNDING, TRUE, [Enable surrounding-text support])
+else
+    enable_surrounding_text="no (disabled, use --enable-surrounding-text to enable)"
+fi
+
 # check iso-codes
 PKG_CHECK_MODULES(ISOCODES, [
     iso-codes
@@ -442,5 +455,6 @@ Build options:
   Enable key snooper        $enable_key_snooper
   No snooper regexes        "$NO_SNOOPER_APPS"
   Panel icon                "$IBUS_ICON_KEYBOARD"
+  Enable surrounding-text   $enable_surrounding_text
 ])
 
index b1df2fe..ec42fa4 100644 (file)
@@ -46,6 +46,9 @@ class EngineBase(object.Object):
     def set_cursor_location(self, x, y, w, h):
         pass
 
+    def set_surrounding_text(self, text, cursor_index):
+        pass
+
     def set_capabilities(self, cap):
         pass
 
@@ -163,6 +166,9 @@ class EngineProxy(interface.IEngine):
     def SetCursorLocation(self, x, y, w, h):
         return self.__engine.set_cursor_location(x, y, w, h)
 
+    def SetSurroundingText(self, text, cursor_index):
+        return self.__engine.set_surrounding_text(text, cursor_index)
+
     def SetCapabilities(self, caps):
         return self.__engine.set_capabilities(caps)
 
index 2386c0f..5db2012 100644 (file)
@@ -50,6 +50,9 @@ class IEngine(dbus.service.Object):
     @method(in_signature="iiii")
     def SetCursorLocation(self, x, y, w, h): pass
 
+    @method(in_signature="vu")
+    def SetSurroundingText(self, text, cursor_index): pass
+
     @method(in_signature="u")
     def SetCapabilities(self, cap): pass
 
index 89f6dbd..2db1c9b 100644 (file)
@@ -49,6 +49,9 @@ class IInputContext(dbus.service.Object):
     @method(in_signature="iiii")
     def SetCursorLocation(self, x, y, w, h): pass
 
+    @method(in_signature="vu")
+    def SetSurroundingText(self, text, cursor_index): pass
+
     @method()
     def FocusIn(self): pass
 
index 519d7ca..73b6473 100644 (file)
@@ -21,6 +21,7 @@
  */
 #include "ibusengine.h"
 #include <stdarg.h>
+#include <string.h>
 #include "ibusmarshalers.h"
 #include "ibusinternal.h"
 #include "ibusshare.h"
@@ -45,6 +46,7 @@ enum {
     PROPERTY_SHOW,
     PROPERTY_HIDE,
     CANDIDATE_CLICKED,
+    SET_SURROUNDING_TEXT,
     LAST_SIGNAL,
 };
 
@@ -58,10 +60,17 @@ enum {
 struct _IBusEnginePrivate {
     gchar *engine_name;
     GDBusConnection *connection;
+
+    /* cached surrounding text (see also IBusInputContextPrivate and
+       BusEngineProxy) */
+    IBusText *surrounding_text;
+    guint surrounding_cursor_pos;
 };
 
 static guint            engine_signals[LAST_SIGNAL] = { 0 };
 
+static IBusText *text_empty = NULL;
+
 /* functions prototype */
 static void      ibus_engine_destroy         (IBusEngine         *engine);
 static void      ibus_engine_set_property    (IBusEngine         *engine,
@@ -135,6 +144,10 @@ static void      ibus_engine_property_show   (IBusEngine         *engine,
                                               const gchar        *prop_name);
 static void      ibus_engine_property_hide   (IBusEngine         *engine,
                                               const gchar        *prop_name);
+static void      ibus_engine_set_surrounding_text
+                                            (IBusEngine         *engine,
+                                             IBusText           *text,
+                                             guint               cursor_pos);
 static void      ibus_engine_emit_signal     (IBusEngine         *engine,
                                               const gchar        *signal_name,
                                               GVariant           *parameters);
@@ -185,6 +198,10 @@ static const gchar introspection_xml[] =
     "    <method name='PageDown' />"
     "    <method name='CursorUp' />"
     "    <method name='CursorDown' />"
+    "    <method name='SetSurroundingText'>"
+    "      <arg direction='in'  type='v' name='text' />"
+    "      <arg direction='in'  type='u' name='cursor_pos' />"
+    "    </method>"
     /* FIXME signals */
     "    <signal name='CommitText'>"
     "      <arg type='v' name='text' />"
@@ -250,6 +267,7 @@ ibus_engine_class_init (IBusEngineClass *class)
     class->property_hide        = ibus_engine_property_hide;
     class->set_cursor_location  = ibus_engine_set_cursor_location;
     class->set_capabilities     = ibus_engine_set_capabilities;
+    class->set_surrounding_text = ibus_engine_set_surrounding_text;
 
     /* install properties */
     /**
@@ -616,12 +634,39 @@ ibus_engine_class_init (IBusEngineClass *class)
             G_TYPE_STRING);
 
     g_type_class_add_private (class, sizeof (IBusEnginePrivate));
+
+    /**
+     * IBusEngine::set-surrounding-text:
+     * @engine: An IBusEngine.
+     *
+     * Emitted when a surrounding text is set.
+     * Implement the member function set_surrounding_text() in extended class to receive this signal.
+     *
+     * <note><para>Argument @user_data is ignored in this function.</para></note>
+     */
+    engine_signals[SET_SURROUNDING_TEXT] =
+        g_signal_new (I_("set-surrounding-text"),
+            G_TYPE_FROM_CLASS (gobject_class),
+            G_SIGNAL_RUN_LAST,
+            G_STRUCT_OFFSET (IBusEngineClass, set_surrounding_text),
+            NULL, NULL,
+            _ibus_marshal_VOID__OBJECT_UINT,
+            G_TYPE_NONE,
+            2,
+            G_TYPE_OBJECT,
+            G_TYPE_UINT);
+
+    text_empty = ibus_text_new_from_static_string ("");
+    g_object_ref_sink (text_empty);
 }
 
 static void
 ibus_engine_init (IBusEngine *engine)
 {
     engine->priv = IBUS_ENGINE_GET_PRIVATE (engine);
+
+    engine->priv->surrounding_text = g_object_ref_sink (text_empty);
+    engine->priv->surrounding_cursor_pos = 0;
 }
 
 static void
@@ -630,6 +675,11 @@ ibus_engine_destroy (IBusEngine *engine)
     g_free (engine->priv->engine_name);
     engine->priv->engine_name = NULL;
 
+    if (engine->priv->surrounding_text) {
+        g_object_unref (engine->priv->surrounding_text);
+        engine->priv->surrounding_text = NULL;
+    }
+
     IBUS_OBJECT_CLASS(ibus_engine_parent_class)->destroy (IBUS_OBJECT (engine));
 }
 
@@ -801,6 +851,26 @@ ibus_engine_service_method_call (IBusService           *service,
         return;
     }
 
+    if (g_strcmp0 (method_name, "SetSurroundingText") == 0) {
+        GVariant *variant = NULL;
+        IBusText *text;
+        guint cursor_pos;
+
+        g_variant_get (parameters, "(vu)", &variant, &cursor_pos);
+        text = IBUS_TEXT (ibus_serializable_deserialize (variant));
+        g_variant_unref (variant);
+
+        g_signal_emit (engine, engine_signals[SET_SURROUNDING_TEXT],
+                       0,
+                       text,
+                       cursor_pos);
+        if (g_object_is_floating (text)) {
+            g_object_unref (text);
+        }
+        g_dbus_method_invocation_return_value (invocation, NULL);
+        return;
+    }
+
     /* should not be reached */
     g_return_if_reached ();
 }
@@ -955,6 +1025,26 @@ ibus_engine_property_hide (IBusEngine *engine, const gchar *prop_name)
 }
 
 static void
+ibus_engine_set_surrounding_text (IBusEngine *engine,
+                                  IBusText   *text,
+                                  guint       cursor_pos)
+{
+    g_assert (IBUS_IS_ENGINE (engine));
+
+    IBusEnginePrivate *priv;
+
+    priv = IBUS_ENGINE_GET_PRIVATE (engine);
+
+    if (priv->surrounding_text) {
+        g_object_unref (priv->surrounding_text);
+    }
+
+    priv->surrounding_text = (IBusText *) g_object_ref_sink (text ? text : text_empty);
+    priv->surrounding_cursor_pos = cursor_pos;
+    // g_debug ("set-surrounding-text ('%s', %d)", text->text, cursor_pos);
+}
+
+static void
 ibus_engine_emit_signal (IBusEngine  *engine,
                          const gchar *signal_name,
                          GVariant    *parameters)
@@ -1138,14 +1228,77 @@ void ibus_engine_delete_surrounding_text (IBusEngine      *engine,
                                           gint             offset_from_cursor,
                                           guint            nchars)
 {
+    IBusEnginePrivate *priv;
+
     g_return_if_fail (IBUS_IS_ENGINE (engine));
 
+    priv = IBUS_ENGINE_GET_PRIVATE (engine);
+
+    /* Update surrounding-text cache.  This is necessary since some
+       engines call ibus_engine_get_surrounding_text() immediately
+       after ibus_engine_delete_surrounding_text(). */
+    if (priv->surrounding_text) {
+        IBusText *text;
+        glong cursor_pos, len;
+
+        cursor_pos = priv->surrounding_cursor_pos + offset_from_cursor;
+        len = ibus_text_get_length (priv->surrounding_text);
+        if (cursor_pos >= 0 && len - cursor_pos >= nchars) {
+            gunichar *ucs;
+
+            ucs = g_utf8_to_ucs4_fast (priv->surrounding_text->text,
+                                       -1,
+                                       NULL);
+            memmove (&ucs[cursor_pos],
+                     &ucs[cursor_pos + nchars],
+                     sizeof(gunichar) * (len - cursor_pos - nchars));
+            ucs[len - nchars] = 0;
+            text = ibus_text_new_from_ucs4 (ucs);
+            g_free (ucs);
+            priv->surrounding_cursor_pos = cursor_pos;
+        } else {
+            text = text_empty;
+            priv->surrounding_cursor_pos = 0;
+        }
+
+        g_object_unref (priv->surrounding_text);
+        priv->surrounding_text = g_object_ref_sink (text);
+    }
+
     ibus_engine_emit_signal (engine,
                              "DeleteSurroundingText",
                              g_variant_new ("(iu)", offset_from_cursor, nchars));
 }
 
 void
+ibus_engine_get_surrounding_text (IBusEngine   *engine,
+                                  IBusText    **text,
+                                  guint        *cursor_pos)
+{
+    IBusEnginePrivate *priv;
+
+    g_return_if_fail (IBUS_IS_ENGINE (engine));
+    g_return_if_fail (text != NULL);
+    g_return_if_fail (cursor_pos != NULL);
+
+    priv = IBUS_ENGINE_GET_PRIVATE (engine);
+
+    *text = g_object_ref (priv->surrounding_text);
+    *cursor_pos = priv->surrounding_cursor_pos;
+
+    /* tell the client that this engine will utilize surrounding-text
+     * feature, which causes periodical update.  Note that the client
+     * should request the initial surrounding-text when the engine is
+     * enabled (see ibus_im_context_focus_in() and
+     * _ibus_context_enabled_cb() in client/gtk2/ibusimcontext.c). */
+    ibus_engine_emit_signal (engine,
+                             "RequireSurroundingText",
+                             NULL);
+
+    // g_debug ("get-surrounding-text ('%s', %d)", (*text)->text, *cursor_pos);
+}
+
+void
 ibus_engine_register_properties (IBusEngine   *engine,
                                  IBusPropList *prop_list)
 {
index 46d0a04..a5f5aea 100644 (file)
@@ -136,10 +136,14 @@ struct _IBusEngineClass {
                                      guint           index,
                                      guint           button,
                                      guint           state);
+    void        (* set_surrounding_text)
+                                    (IBusEngine     *engine,
+                                     IBusText       *text,
+                                     guint           cursor_index);
 
     /*< private >*/
     /* padding */
-    gpointer pdummy[8];
+    gpointer pdummy[7];
 };
 
 GType        ibus_engine_get_type       (void);
@@ -394,6 +398,21 @@ void ibus_engine_delete_surrounding_text(IBusEngine         *engine,
                                          guint               nchars);
 
 /**
+ * ibus_engine_get_surrounding_text:
+ * @engine: An IBusEngine.
+ * @text: Location to store surrounding text.
+ * @cursor_pos: Cursor position in characters in @text.
+ *
+ * Get surrounding text.
+ *
+ * @see_also #IBusEngine::set-surrounding-text
+ */
+void ibus_engine_get_surrounding_text(IBusEngine         *engine,
+                                      IBusText          **text,
+                                      guint              *cursor_pos);
+
+
+/**
  * ibus_engine_get_name:
  * @engine: An IBusEngine.
  * @returns: Name of IBusEngine.
index a345986..439d1b7 100644 (file)
@@ -28,6 +28,9 @@
 #include "ibuslookuptable.h"
 #include "ibusproplist.h"
 
+#define IBUS_INPUT_CONTEXT_GET_PRIVATE(o)  \
+   (G_TYPE_INSTANCE_GET_PRIVATE ((o), IBUS_TYPE_INPUT_CONTEXT, IBusInputContextPrivate))
+
 enum {
     ENABLED,
     DISABLED,
@@ -52,9 +55,25 @@ enum {
     LAST_SIGNAL,
 };
 
+/* IBusInputContextPrivate */
+struct _IBusInputContextPrivate {
+    /* TRUE if the current engine needs surrounding text; FALSE otherwise */
+    gboolean  needs_surrounding_text;
+
+    /* cached surrounding text (see also IBusEnginePrivate and
+       BusEngineProxy) */
+    IBusText *surrounding_text;
+    guint     surrounding_cursor_pos;
+};
+
+typedef struct _IBusInputContextPrivate IBusInputContextPrivate;
+
 static guint            context_signals[LAST_SIGNAL] = { 0 };
 
+static IBusText *text_empty = NULL;
+
 /* functions prototype */
+static void     ibus_input_context_real_destroy (IBusProxy              *context);
 static void     ibus_input_context_g_signal     (GDBusProxy             *proxy,
                                                  const gchar            *sender_name,
                                                  const gchar            *signal_name,
@@ -65,8 +84,13 @@ G_DEFINE_TYPE (IBusInputContext, ibus_input_context, IBUS_TYPE_PROXY)
 static void
 ibus_input_context_class_init (IBusInputContextClass *class)
 {
+    IBusProxyClass *ibus_proxy_class = IBUS_PROXY_CLASS (class);
     GDBusProxyClass *g_dbus_proxy_class = G_DBUS_PROXY_CLASS (class);
 
+    g_type_class_add_private (class, sizeof (IBusInputContextPrivate));
+
+    ibus_proxy_class->destroy = ibus_input_context_real_destroy;
+
     g_dbus_proxy_class->g_signal = ibus_input_context_g_signal;
 
     /* install signals */
@@ -429,11 +453,33 @@ ibus_input_context_class_init (IBusInputContextClass *class)
             G_TYPE_NONE,
             1,
             IBUS_TYPE_PROPERTY);
+
+    text_empty = ibus_text_new_from_static_string ("");
+    g_object_ref_sink (text_empty);
 }
 
 static void
 ibus_input_context_init (IBusInputContext *context)
 {
+    IBusInputContextPrivate *priv;
+
+    priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (context);
+    priv->surrounding_text = g_object_ref_sink (text_empty);
+    priv->surrounding_cursor_pos = 0;
+}
+
+static void
+ibus_input_context_real_destroy (IBusProxy *context)
+{
+    IBusInputContextPrivate *priv;
+    priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (IBUS_INPUT_CONTEXT (context));
+
+    if (priv->surrounding_text) {
+        g_object_unref (priv->surrounding_text);
+        priv->surrounding_text = NULL;
+    }
+
+    IBUS_PROXY_CLASS(ibus_input_context_parent_class)->destroy (context);
 }
 
 static void
@@ -451,8 +497,6 @@ ibus_input_context_g_signal (GDBusProxy  *proxy,
         const gchar *signal_name;
         guint signal_id;
     } signals [] = {
-        { "Enabled",                ENABLED                  },
-        { "Disabled",               DISABLED                 },
         { "ShowPreeditText",        SHOW_PREEDIT_TEXT        },
         { "HidePreeditText",        HIDE_PREEDIT_TEXT        },
         { "ShowAuxiliaryText",      SHOW_AUXILIARY_TEXT      },
@@ -605,6 +649,26 @@ ibus_input_context_g_signal (GDBusProxy  *proxy,
         return;
     }
 
+    IBusInputContextPrivate *priv;
+    priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (IBUS_INPUT_CONTEXT (context));
+
+    if (g_strcmp0 (signal_name, "Enabled") == 0) {
+        priv->needs_surrounding_text = FALSE;
+        g_signal_emit (context, context_signals[ENABLED], 0);
+        return;
+    }
+
+    if (g_strcmp0 (signal_name, "Disabled") == 0) {
+        priv->needs_surrounding_text = FALSE;
+        g_signal_emit (context, context_signals[DISABLED], 0);
+        return;
+    }
+
+    if (g_strcmp0 (signal_name, "RequireSurroundingText") == 0) {
+        priv->needs_surrounding_text = TRUE;
+        return;
+    }
+
     G_DBUS_PROXY_CLASS (ibus_input_context_parent_class)->g_signal (
                                 proxy, sender_name, signal_name, parameters);
 }
@@ -943,6 +1007,46 @@ ibus_input_context_is_enabled_async_finish (IBusInputContext   *context,
     return TRUE;
 }
 
+void
+ibus_input_context_set_surrounding_text (IBusInputContext   *context,
+                                         IBusText           *text,
+                                         guint32             cursor_pos)
+{
+    g_assert (IBUS_IS_INPUT_CONTEXT (context));
+    g_assert (IBUS_IS_TEXT (text));
+
+    IBusInputContextPrivate *priv;
+    priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (context);
+
+    if (priv->surrounding_text == NULL ||
+        g_strcmp0 (text->text, priv->surrounding_text->text) != 0 ||
+        cursor_pos != priv->surrounding_cursor_pos) {
+        GVariant *variant = ibus_serializable_serialize ((IBusSerializable *)text);
+        if (priv->surrounding_text)
+            g_object_unref (priv->surrounding_text);
+        priv->surrounding_text = (IBusText *) g_object_ref_sink (text);
+        priv->surrounding_cursor_pos = cursor_pos;
+
+        g_dbus_proxy_call ((GDBusProxy *) context,
+                         "SetSurroundingText",              /* method_name */
+                         g_variant_new ("(vu)", variant, cursor_pos), /* parameters */
+                         G_DBUS_CALL_FLAGS_NONE,            /* flags */
+                         -1,                                /* timeout */
+                         NULL,                              /* cancellable */
+                         NULL,                              /* callback */
+                         NULL                               /* user_data */
+                         );
+    }
+}
+
+gboolean
+ibus_input_context_needs_surrounding_text (IBusInputContext *context)
+{
+    IBusInputContextPrivate *priv;
+    priv = IBUS_INPUT_CONTEXT_GET_PRIVATE (IBUS_INPUT_CONTEXT (context));
+    return priv->needs_surrounding_text;
+}
+
 gboolean
 ibus_input_context_is_enabled (IBusInputContext *context)
 {
index d2bcf5b..67a95d6 100644 (file)
@@ -41,6 +41,7 @@
 
 #include "ibusproxy.h"
 #include "ibusenginedesc.h"
+#include "ibustext.h"
 
 /*
  * Type macros.
@@ -460,6 +461,27 @@ IBusEngineDesc *
 void         ibus_input_context_set_engine  (IBusInputContext   *context,
                                              const gchar        *name);
 
+/**
+ * ibus_input_context_set_surrounding_text:
+ * @context: An #IBusInputContext.
+ * @text: An #IBusText surrounding the current cursor on the application.
+ * @cursor_po: Current cursor position in characters in @text.
+*/
+void         ibus_input_context_set_surrounding_text
+                                            (IBusInputContext   *context,
+                                             IBusText           *text,
+                                             guint32             cursor_pos);
+
+/**
+ * ibus_input_context_needs_surrounding_text:
+ * @context: An #IBusInputContext.
+ * @returns: %TRUE if surrounding-text is needed by the current engine;
+ * %FALSE otherwise.
+ *
+ * Check whether the current engine requires surrounding-text.
+ */
+gboolean     ibus_input_context_needs_surrounding_text
+                                            (IBusInputContext   *context);
 
 G_END_DECLS
 #endif
index 5184278..5dc7fc2 100644 (file)
@@ -13,6 +13,7 @@ VOID:INT,INT,INT,INT
 VOID:UINT,UINT
 VOID:INT,UINT
 VOID:UINT,UINT,UINT
+VOID:OBJECT,UINT
 VOID:OBJECT,UINT,BOOL
 VOID:OBJECT,UINT,BOOL,UINT
 VOID:OBJECT,BOOL