gnome-shell-3.6.3.1/src/st/st-im-text.c

No issues found

  1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
  2 /*
  3  * st-im-text.c: Text widget with input method support
  4  *
  5  * Copyright 2009 Red Hat, Inc.
  6  *
  7  * This started as a copy of ClutterIMText converted to use
  8  * GtkIMContext rather than ClutterIMContext. Original code:
  9  *
 10  * Author: raymond liu <raymond.liu@intel.com>
 11  *
 12  * Copyright (C) 2009, Intel Corporation.
 13  *
 14  * This library is free software; you can redistribute it and/or
 15  * modify it under the terms of the GNU Lesser General Public License
 16  * version 2.1 as published by the Free Software Foundation.
 17  *
 18  * This library is distributed in the hope that it will be useful, but
 19  * WITHOUT ANY WARRANTY; without even the implied warranty of
 20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 21  * Lesser General Public License for more details.
 22  *
 23  * You should have received a copy of the GNU Lesser General Public License
 24  * along with this program. If not, see <http://www.gnu.org/licenses/>.
 25  */
 26 
 27 /**
 28  * SECTION:st-im-text
 29  * @short_description: Text widget with input method support
 30  * @stability: Unstable
 31  * @see_also: #ClutterText
 32  * @include: st-imtext/st-imtext.h
 33  *
 34  * #StIMText derives from ClutterText and hooks up better text input
 35  * via #GtkIMContext. It is meant to be a drop-in replacement for
 36  * ClutterIMText but using GtkIMContext rather than ClutterIMContext.
 37  */
 38 
 39 #ifdef HAVE_CONFIG_H
 40 #include "config.h"
 41 #endif
 42 
 43 #include <clutter/clutter.h>
 44 #include <clutter/x11/clutter-x11.h>
 45 #include <glib.h>
 46 #include <gtk/gtk.h>
 47 #include <gdk/gdkkeysyms.h>
 48 #include <gdk/gdkx.h>
 49 #include <X11/extensions/XKB.h>
 50 
 51 #include "st-im-text.h"
 52 
 53 #define ST_IM_TEXT_GET_PRIVATE(obj)    \
 54         (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ST_TYPE_IM_TEXT, StIMTextPrivate))
 55 
 56 struct _StIMTextPrivate
 57 {
 58   GtkIMContext *im_context;
 59   GdkWindow *window;
 60 
 61   guint need_im_reset : 1;
 62 };
 63 
 64 G_DEFINE_TYPE (StIMText, st_im_text, CLUTTER_TYPE_TEXT)
 65 
 66 static void
 67 st_im_text_dispose (GObject *object)
 68 {
 69   StIMTextPrivate *priv = ST_IM_TEXT (object)->priv;
 70 
 71   G_OBJECT_CLASS (st_im_text_parent_class)->dispose (object);
 72 
 73   g_clear_object (&priv->im_context);
 74 }
 75 
 76 static void
 77 update_im_cursor_location (StIMText *self)
 78 {
 79   StIMTextPrivate *priv = self->priv;
 80   ClutterText *clutter_text = CLUTTER_TEXT (self);
 81   gint position;
 82   gfloat cursor_x, cursor_y, cursor_height;
 83   gfloat actor_x, actor_y;
 84   GdkRectangle area;
 85 
 86   position = clutter_text_get_cursor_position (clutter_text);
 87   clutter_text_position_to_coords (clutter_text, position,
 88                                    &cursor_x, &cursor_y, &cursor_height);
 89 
 90   clutter_actor_get_transformed_position (CLUTTER_ACTOR (self), &actor_x, &actor_y);
 91 
 92   area.x = (int)(0.5 + cursor_x + actor_x);
 93   area.y = (int)(0.5 + cursor_y + actor_y);
 94   area.width = 0;
 95   area.height = (int)(0.5 + cursor_height);
 96 
 97   gtk_im_context_set_cursor_location (priv->im_context, &area);
 98 }
 99 
100 static void
101 st_im_text_commit_cb (GtkIMContext *context,
102                       const gchar  *str,
103                       StIMText     *imtext)
104 {
105   ClutterText *clutter_text = CLUTTER_TEXT (imtext);
106 
107   if (clutter_text_get_editable (clutter_text))
108     {
109       clutter_text_delete_selection (clutter_text);
110       clutter_text_insert_text (clutter_text, str,
111                                 clutter_text_get_cursor_position (clutter_text));
112     }
113 }
114 
115 static void
116 st_im_text_preedit_changed_cb (GtkIMContext *context,
117                                StIMText     *imtext)
118 {
119   ClutterText *clutter_text = CLUTTER_TEXT (imtext);
120   gchar *preedit_str = NULL;
121   PangoAttrList *preedit_attrs = NULL;
122   gint cursor_pos = 0;
123 
124   gtk_im_context_get_preedit_string (context,
125                                      &preedit_str,
126                                      &preedit_attrs,
127                                      &cursor_pos);
128 
129   clutter_text_set_preedit_string (clutter_text,
130                                    preedit_str,
131                                    preedit_attrs,
132                                    cursor_pos);
133 
134   g_free (preedit_str);
135   pango_attr_list_unref (preedit_attrs);
136 }
137 
138 static gboolean
139 st_im_text_retrieve_surrounding_cb (GtkIMContext *context,
140                                     StIMText     *imtext)
141 {
142   ClutterText *clutter_text = CLUTTER_TEXT (imtext);
143   ClutterTextBuffer *buffer;
144   const gchar *text;
145   gint cursor_pos;
146 
147   buffer = clutter_text_get_buffer (clutter_text);
148   text = clutter_text_buffer_get_text (buffer);
149 
150   cursor_pos = clutter_text_get_cursor_position (clutter_text);
151   if (cursor_pos < 0)
152     cursor_pos = clutter_text_buffer_get_length (buffer);
153 
154   gtk_im_context_set_surrounding (context, text,
155                                   /* length and cursor_index are in bytes */
156                                   clutter_text_buffer_get_bytes (buffer),
157                                   g_utf8_offset_to_pointer (text, cursor_pos) - text);
158 
159   return TRUE;
160 }
161 
162 static gboolean
163 st_im_text_delete_surrounding_cb (GtkIMContext *context,
164                                   gint          offset,
165                                   gint          n_chars,
166                                   StIMText     *imtext)
167 {
168   ClutterText *clutter_text = CLUTTER_TEXT (imtext);
169 
170   if (clutter_text_get_editable (clutter_text))
171     {
172       gint cursor_pos = clutter_text_get_cursor_position (clutter_text);
173       clutter_text_delete_text (clutter_text,
174                                 cursor_pos + offset,
175                                 cursor_pos + offset + n_chars);
176     }
177 
178   return TRUE;
179 }
180 
181 static void
182 reset_im_context (StIMText *self)
183 {
184   StIMTextPrivate *priv = self->priv;
185 
186   if (priv->need_im_reset)
187     {
188       gtk_im_context_reset (priv->im_context);
189       priv->need_im_reset = FALSE;
190     }
191 }
192 
193 static void
194 st_im_text_paint (ClutterActor *actor)
195 {
196   StIMText *self = ST_IM_TEXT (actor);
197   ClutterText *clutter_text = CLUTTER_TEXT (actor);
198 
199   /* This updates the cursor position as a side-effect */
200   if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->paint)
201     CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->paint (actor);
202 
203   if (clutter_text_get_editable (clutter_text))
204     update_im_cursor_location (self);
205 }
206 
207 static gboolean
208 st_im_text_get_paint_volume (ClutterActor       *self,
209                              ClutterPaintVolume *volume)
210 {
211   return clutter_paint_volume_set_from_allocation (volume, self);
212 }
213 
214 /* Returns a new reference to window */
215 static GdkWindow *
216 window_for_actor (ClutterActor *actor)
217 {
218   GdkDisplay *display = gdk_display_get_default ();
219   ClutterActor *stage;
220   Window xwindow;
221   GdkWindow *window;
222 
223   stage = clutter_actor_get_stage (actor);
224   xwindow = clutter_x11_get_stage_window ((ClutterStage *)stage);
225 
226   window = gdk_x11_window_lookup_for_display (display, xwindow);
227   if (window)
228     g_object_ref (window);
229   else
230     window = gdk_x11_window_foreign_new_for_display (display, xwindow);
231 
232   return window;
233 }
234 
235 static void
236 st_im_text_realize (ClutterActor *actor)
237 {
238   StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv;
239 
240   priv->window = window_for_actor (actor);
241   gtk_im_context_set_client_window (priv->im_context, priv->window);
242 }
243 
244 static void
245 st_im_text_unrealize (ClutterActor *actor)
246 {
247   StIMText *self = ST_IM_TEXT (actor);
248   StIMTextPrivate *priv = self->priv;
249 
250   reset_im_context (self);
251   gtk_im_context_set_client_window (priv->im_context, NULL);
252   g_object_unref (priv->window);
253   priv->window = NULL;
254 }
255 
256 static gboolean
257 key_is_modifier (guint16 keyval)
258 {
259   /* See gdkkeys-x11.c:_gdk_keymap_key_is_modifier() for how this
260    * really should be implemented */
261 
262   switch (keyval)
263     {
264     case GDK_KEY_Shift_L:
265     case GDK_KEY_Shift_R:
266     case GDK_KEY_Control_L:
267     case GDK_KEY_Control_R:
268     case GDK_KEY_Caps_Lock:
269     case GDK_KEY_Shift_Lock:
270     case GDK_KEY_Meta_L:
271     case GDK_KEY_Meta_R:
272     case GDK_KEY_Alt_L:
273     case GDK_KEY_Alt_R:
274     case GDK_KEY_Super_L:
275     case GDK_KEY_Super_R:
276     case GDK_KEY_Hyper_L:
277     case GDK_KEY_Hyper_R:
278     case GDK_KEY_ISO_Lock:
279     case GDK_KEY_ISO_Level2_Latch:
280     case GDK_KEY_ISO_Level3_Shift:
281     case GDK_KEY_ISO_Level3_Latch:
282     case GDK_KEY_ISO_Level3_Lock:
283     case GDK_KEY_ISO_Level5_Shift:
284     case GDK_KEY_ISO_Level5_Latch:
285     case GDK_KEY_ISO_Level5_Lock:
286     case GDK_KEY_ISO_Group_Shift:
287     case GDK_KEY_ISO_Group_Latch:
288     case GDK_KEY_ISO_Group_Lock:
289       return TRUE;
290     default:
291       return FALSE;
292     }
293 }
294 
295 static GdkEventKey *
296 key_event_to_gdk (ClutterKeyEvent *event_clutter)
297 {
298   GdkDisplay *display = gdk_display_get_default ();
299   GdkKeymap *keymap = gdk_keymap_get_for_display (display);
300   GdkEventKey *event_gdk;
301   event_gdk = (GdkEventKey *)gdk_event_new ((event_clutter->type == CLUTTER_KEY_PRESS) ?
302                                             GDK_KEY_PRESS : GDK_KEY_RELEASE);
303 
304   event_gdk->window = window_for_actor ((ClutterActor *)event_clutter->stage);
305   event_gdk->send_event = FALSE;
306   event_gdk->time = event_clutter->time;
307   /* This depends on ClutterModifierType and GdkModifierType being
308    * identical, which they are currently. (They both match the X
309    * modifier state in the low 16-bits and have the same extensions.) */
310   event_gdk->state = event_clutter->modifier_state;
311   event_gdk->keyval = event_clutter->keyval;
312   event_gdk->hardware_keycode = event_clutter->hardware_keycode;
313   /* For non-proper non-XKB support, we'd need a huge cut-and-paste
314    * from gdkkeys-x11.c; this is a macro that just shifts a few bits
315    * out of state, so won't make the situation worse if the server
316    * doesn't support XKB; we'll just end up with group == 0 */
317   event_gdk->group = XkbGroupForCoreState (event_gdk->state);
318 
319   gdk_keymap_translate_keyboard_state (keymap, event_gdk->hardware_keycode,
320                                        event_gdk->state, event_gdk->group,
321                                        &event_gdk->keyval, NULL, NULL, NULL);
322 
323   if (event_clutter->unicode_value)
324     {
325       /* This is not particularly close to what GDK does - event_gdk->string
326        * is supposed to be in the locale encoding, and have control keys
327        * as control characters, etc. See gdkevents-x11.c:translate_key_event().
328        * Hopefully no input method is using event.string.
329        */
330       char buf[6];
331 
332       event_gdk->length = g_unichar_to_utf8 (event_clutter->unicode_value, buf);
333       event_gdk->string = g_strndup (buf, event_gdk->length);
334     }
335 
336   event_gdk->is_modifier = key_is_modifier (event_gdk->keyval);
337 
338   return event_gdk;
339 }
340 
341 static gboolean
342 st_im_text_button_press_event (ClutterActor       *actor,
343                                ClutterButtonEvent *event)
344 {
345   /* The button press indicates the user moving the cursor, or selecting
346    * etc, so we should abort any current preedit operation. ClutterText
347    * treats all buttons identically, so so do we.
348    */
349   reset_im_context (ST_IM_TEXT (actor));
350 
351   if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->button_press_event)
352     return CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->button_press_event (actor, event);
353   else
354     return FALSE;
355 }
356 
357 static gboolean
358 st_im_text_captured_event (ClutterActor *actor,
359                            ClutterEvent *event)
360 {
361   StIMText *self = ST_IM_TEXT (actor);
362   StIMTextPrivate *priv = self->priv;
363   ClutterText *clutter_text = CLUTTER_TEXT (actor);
364   ClutterEventType type = clutter_event_type (event);
365   gboolean result = FALSE;
366   int old_position;
367 
368   if (type != CLUTTER_KEY_PRESS && type != CLUTTER_KEY_RELEASE)
369     return FALSE;
370 
371   if (clutter_text_get_editable (clutter_text))
372     {
373       GdkEventKey *event_gdk = key_event_to_gdk ((ClutterKeyEvent *)event);
374 
375       if (gtk_im_context_filter_keypress (priv->im_context, event_gdk))
376         {
377           priv->need_im_reset = TRUE;
378           result = TRUE;
379         }
380 
381       gdk_event_free ((GdkEvent *)event_gdk);
382     }
383 
384   old_position = clutter_text_get_cursor_position (clutter_text);
385 
386   if (!result &&
387       CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->captured_event)
388     result = CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->captured_event (actor, event);
389 
390   if (type == CLUTTER_KEY_PRESS &&
391       clutter_text_get_cursor_position (clutter_text) != old_position)
392     reset_im_context (self);
393 
394   return result;
395 }
396 
397 static void
398 st_im_text_key_focus_in (ClutterActor *actor)
399 {
400   StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv;
401   ClutterText *clutter_text = CLUTTER_TEXT (actor);
402 
403   if (clutter_text_get_editable (clutter_text))
404     {
405       priv->need_im_reset = TRUE;
406       gtk_im_context_focus_in (priv->im_context);
407     }
408 
409   if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_in)
410     CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_in (actor);
411 }
412 
413 static void
414 st_im_text_key_focus_out (ClutterActor *actor)
415 {
416   StIMTextPrivate *priv = ST_IM_TEXT (actor)->priv;
417   ClutterText *clutter_text = CLUTTER_TEXT (actor);
418 
419   if (clutter_text_get_editable (clutter_text))
420     {
421       priv->need_im_reset = TRUE;
422       gtk_im_context_focus_out (priv->im_context);
423     }
424 
425   if (CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_out)
426     CLUTTER_ACTOR_CLASS (st_im_text_parent_class)->key_focus_out (actor);
427 }
428 
429 static void
430 st_im_text_class_init (StIMTextClass *klass)
431 {
432   GObjectClass *object_class = G_OBJECT_CLASS (klass);
433   ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
434 
435   g_type_class_add_private (klass, sizeof (StIMTextPrivate));
436 
437   object_class->dispose = st_im_text_dispose;
438 
439   actor_class->paint = st_im_text_paint;
440   actor_class->get_paint_volume = st_im_text_get_paint_volume;
441   actor_class->realize = st_im_text_realize;
442   actor_class->unrealize = st_im_text_unrealize;
443 
444   actor_class->button_press_event = st_im_text_button_press_event;
445   actor_class->captured_event = st_im_text_captured_event;
446   actor_class->key_focus_in = st_im_text_key_focus_in;
447   actor_class->key_focus_out = st_im_text_key_focus_out;
448 }
449 
450 static void
451 st_im_text_init (StIMText *self)
452 {
453   StIMTextPrivate *priv;
454 
455   self->priv = priv = ST_IM_TEXT_GET_PRIVATE (self);
456 
457   priv->im_context = gtk_im_multicontext_new ();
458   g_signal_connect (priv->im_context, "commit",
459                     G_CALLBACK (st_im_text_commit_cb), self);
460   g_signal_connect (priv->im_context, "preedit-changed",
461                     G_CALLBACK (st_im_text_preedit_changed_cb), self);
462   g_signal_connect (priv->im_context, "retrieve-surrounding",
463                     G_CALLBACK (st_im_text_retrieve_surrounding_cb), self);
464   g_signal_connect (priv->im_context, "delete-surrounding",
465                     G_CALLBACK (st_im_text_delete_surrounding_cb), self);
466 }
467 
468 /**
469  * st_im_text_new:
470  * @text: text to set  to
471  *
472  * Create a new #StIMText with the specified text
473  *
474  * Returns: a new #ClutterActor
475  */
476 ClutterActor *
477 st_im_text_new (const gchar *text)
478 {
479   return g_object_new (ST_TYPE_IM_TEXT,
480                        "text", text,
481                        NULL);
482 }