1 /*
2  * gtkimmodulequartz
3  * Copyright (C) 2011 Hiroyuki Yamamoto
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Lesser General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Lesser General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this library; if not, write to the
17  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18  * Boston, MA 02111-1307, USA.
19  *
20  * $Id:$
21  */
22 
23 #include "config.h"
24 #include <string.h>
25 
26 #include <gtk/gtk.h>
27 #include "gtk/gtkintl.h"
28 #include "gtk/gtkimmodule.h"
29 
30 #include "gdk/quartz/gdkquartz.h"
31 #include "gdk/quartz/GdkQuartzView.h"
32 
33 #define GTK_IM_CONTEXT_TYPE_QUARTZ (type_quartz)
34 #define GTK_IM_CONTEXT_QUARTZ(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GTK_IM_CONTEXT_TYPE_QUARTZ, GtkIMContextQuartz))
35 #define GTK_IM_CONTEXT_QUARTZ_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), GTK_IM_CONTEXT_TYPE_QUARTZ, GtkIMContextQuartzClass))
36 
37 typedef struct _GtkIMContextQuartz
38 {
39   GtkIMContext parent;
40   GtkIMContext *slave;
41   GdkWindow *client_window;
42   gchar *preedit_str;
43   unsigned int cursor_index;
44   unsigned int selected_len;
45   GdkRectangle *cursor_rect;
46   gboolean focused;
47 } GtkIMContextQuartz;
48 
49 typedef struct _GtkIMContextQuartzClass
50 {
51   GtkIMContextClass parent_class;
52 } GtkIMContextQuartzClass;
53 
54 GType type_quartz = 0;
55 static GObjectClass *parent_class;
56 
57 static const GtkIMContextInfo imquartz_info =
58 {
59   "quartz",
60   "Mac OS X Quartz",
61   GETTEXT_PACKAGE,
62   GTK_LOCALEDIR,
63   "ja:ko:zh:*",
64 };
65 
66 static const GtkIMContextInfo *info_list[] =
67 {
68   &imquartz_info,
69 };
70 
71 #ifndef INCLUDE_IM_quartz
72 #define MODULE_ENTRY(type,function) G_MODULE_EXPORT type im_module_ ## function
73 #else
74 #define MODULE_ENTRY(type, function) type _gtk_immodule_quartz_ ## function
75 #endif
76 
77 static void
quartz_get_preedit_string(GtkIMContext * context,gchar ** str,PangoAttrList ** attrs,gint * cursor_pos)78 quartz_get_preedit_string (GtkIMContext *context,
79                            gchar **str,
80                            PangoAttrList **attrs,
81                            gint *cursor_pos)
82 {
83   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
84 
85   GTK_NOTE (MISC, g_print ("quartz_get_preedit_string\n"));
86 
87   if (str)
88     *str = qc->preedit_str ? g_strdup (qc->preedit_str) : g_strdup ("");
89 
90   if (attrs)
91     {
92       *attrs = pango_attr_list_new ();
93       int len = g_utf8_strlen (*str, -1);
94       gchar *ch = *str;
95       if (len > 0)
96         {
97           PangoAttribute *attr;
98           int i = 0;
99           for (;;)
100             {
101               gchar *s = ch;
102               ch = g_utf8_next_char (ch);
103 
104               if (i >= qc->cursor_index &&
105 		  i < qc->cursor_index + qc->selected_len)
106                 attr = pango_attr_underline_new (PANGO_UNDERLINE_DOUBLE);
107               else
108                 attr = pango_attr_underline_new (PANGO_UNDERLINE_SINGLE);
109 
110               attr->start_index = s - *str;
111               if (!*ch)
112                 attr->end_index = attr->start_index + strlen (s);
113               else
114                 attr->end_index = ch - *str;
115 
116               pango_attr_list_change (*attrs, attr);
117 
118               if (!*ch)
119                 break;
120               i++;
121             }
122         }
123     }
124   if (cursor_pos)
125     *cursor_pos = qc->cursor_index;
126 }
127 
128 static gboolean
output_result(GtkIMContext * context,GdkWindow * win)129 output_result (GtkIMContext *context,
130                GdkWindow *win)
131 {
132   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
133   gboolean retval = FALSE;
134   gchar *fixed_str, *marked_str;
135 
136   fixed_str = g_object_get_data (G_OBJECT (win), TIC_INSERT_TEXT);
137   marked_str = g_object_get_data (G_OBJECT (win), TIC_MARKED_TEXT);
138   if (fixed_str)
139     {
140       GTK_NOTE (MISC, g_print ("tic-insert-text: %s\n", fixed_str));
141       g_free (qc->preedit_str);
142       qc->preedit_str = NULL;
143       g_object_set_data (G_OBJECT (win), TIC_INSERT_TEXT, NULL);
144       g_signal_emit_by_name (context, "commit", fixed_str);
145       g_signal_emit_by_name (context, "preedit_changed");
146 
147       unsigned int filtered =
148 	   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (win),
149 						GIC_FILTER_KEY));
150       GTK_NOTE (MISC, g_print ("filtered, %d\n", filtered));
151       if (filtered)
152         retval = TRUE;
153       else
154         retval = FALSE;
155     }
156   if (marked_str)
157     {
158       GTK_NOTE (MISC, g_print ("tic-marked-text: %s\n", marked_str));
159       qc->cursor_index =
160 	   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (win),
161 						TIC_SELECTED_POS));
162       qc->selected_len =
163 	   GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (win),
164 						TIC_SELECTED_LEN));
165       g_free (qc->preedit_str);
166       qc->preedit_str = g_strdup (marked_str);
167       g_object_set_data (G_OBJECT (win), TIC_MARKED_TEXT, NULL);
168       g_signal_emit_by_name (context, "preedit_changed");
169       retval = TRUE;
170     }
171   if (!fixed_str && !marked_str)
172     {
173       if (qc->preedit_str && strlen (qc->preedit_str) > 0)
174         retval = TRUE;
175     }
176 
177   g_free (fixed_str);
178   g_free (marked_str);
179 
180   return retval;
181 }
182 
183 static gboolean
quartz_filter_keypress(GtkIMContext * context,GdkEventKey * event)184 quartz_filter_keypress (GtkIMContext *context,
185                         GdkEventKey *event)
186 {
187   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
188   gboolean retval;
189   NSView *nsview;
190   GdkWindow *win;
191 
192   GTK_NOTE (MISC, g_print ("quartz_filter_keypress\n"));
193 
194   if (!qc->client_window)
195     return FALSE;
196 
197   if (!gdk_quartz_window_is_quartz (qc->client_window))
198     return gtk_im_context_filter_keypress (qc->slave, event);
199 
200   nsview = gdk_quartz_window_get_nsview (qc->client_window);
201   win = (GdkWindow *)[ (GdkQuartzView *)nsview gdkWindow];
202   GTK_NOTE (MISC, g_print ("client_window: %p, win: %p, nsview: %p\n",
203 			   qc->client_window, win, nsview));
204 
205   NSEvent *nsevent = gdk_quartz_event_get_nsevent ((GdkEvent *)event);
206 
207   if (!nsevent)
208     {
209       if (event->hardware_keycode == 0 && event->keyval == 0xffffff)
210         /* update text input changes by mouse events */
211         return output_result (context, win);
212       else
213         return gtk_im_context_filter_keypress (qc->slave, event);
214     }
215 
216   if (event->type == GDK_KEY_RELEASE)
217     return FALSE;
218 
219   if (event->hardware_keycode == 55)	/* Command */
220     return FALSE;
221 
222   NSEventType etype = [nsevent type];
223   if (etype == NSKeyDown)
224     {
225        g_object_set_data (G_OBJECT (win), TIC_IN_KEY_DOWN,
226                                           GUINT_TO_POINTER (TRUE));
227        [nsview keyDown: nsevent];
228     }
229   /* JIS_Eisu || JIS_Kana */
230   if (event->hardware_keycode == 102 || event->hardware_keycode == 104)
231     return FALSE;
232 
233   retval = output_result(context, win);
234   g_object_set_data (G_OBJECT (win), TIC_IN_KEY_DOWN,
235                                      GUINT_TO_POINTER (FALSE));
236   GTK_NOTE (MISC, g_print ("quartz_filter_keypress done\n"));
237 
238   return retval;
239 }
240 
241 static void
discard_preedit(GtkIMContext * context)242 discard_preedit (GtkIMContext *context)
243 {
244   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
245 
246   if (!qc->client_window)
247     return;
248 
249   if (!gdk_quartz_window_is_quartz (qc->client_window))
250     return;
251 
252   NSView *nsview = gdk_quartz_window_get_nsview (qc->client_window);
253   if (!nsview)
254     return;
255 
256   /* reset any partial input for this NSView */
257   [(GdkQuartzView *)nsview unmarkText];
258   NSInputManager *currentInputManager = [NSInputManager currentInputManager];
259   [currentInputManager markedTextAbandoned:nsview];
260 
261   if (qc->preedit_str && strlen (qc->preedit_str) > 0)
262     {
263       g_signal_emit_by_name (context, "commit", qc->preedit_str);
264 
265       g_free (qc->preedit_str);
266       qc->preedit_str = NULL;
267       g_signal_emit_by_name (context, "preedit_changed");
268     }
269 }
270 
271 static void
quartz_reset(GtkIMContext * context)272 quartz_reset (GtkIMContext *context)
273 {
274   GTK_NOTE (MISC, g_print ("quartz_reset\n"));
275   discard_preedit (context);
276 }
277 
278 static void
quartz_set_client_window(GtkIMContext * context,GdkWindow * window)279 quartz_set_client_window (GtkIMContext *context, GdkWindow *window)
280 {
281   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
282 
283   GTK_NOTE (MISC, g_print ("quartz_set_client_window: %p\n", window));
284 
285   qc->client_window = window;
286 }
287 
288 static void
quartz_focus_in(GtkIMContext * context)289 quartz_focus_in (GtkIMContext *context)
290 {
291   GTK_NOTE (MISC, g_print ("quartz_focus_in\n"));
292 
293   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
294   qc->focused = TRUE;
295 }
296 
297 static void
quartz_focus_out(GtkIMContext * context)298 quartz_focus_out (GtkIMContext *context)
299 {
300   GTK_NOTE (MISC, g_print ("quartz_focus_out\n"));
301 
302   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
303   qc->focused = FALSE;
304 
305   /* Commit any partially built strings or it'll mess up other GTK+ widgets in the window */
306   discard_preedit (context);
307 }
308 
309 static void
quartz_set_cursor_location(GtkIMContext * context,GdkRectangle * area)310 quartz_set_cursor_location (GtkIMContext *context, GdkRectangle *area)
311 {
312   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (context);
313   gint x, y;
314   NSView *nsview;
315   GdkWindow *win;
316 
317   GTK_NOTE (MISC, g_print ("quartz_set_cursor_location\n"));
318 
319   if (!qc->client_window)
320     return;
321 
322   if (!gdk_quartz_window_is_quartz (qc->client_window))
323     return;
324 
325   if (!qc->focused)
326     return;
327 
328   qc->cursor_rect->x = area->x;
329   qc->cursor_rect->y = area->y;
330   qc->cursor_rect->width = area->width;
331   qc->cursor_rect->height = area->height;
332 
333   gdk_window_get_origin (qc->client_window, &x, &y);
334 
335   qc->cursor_rect->x = area->x + x;
336   qc->cursor_rect->y = area->y + y;
337 
338   nsview = gdk_quartz_window_get_nsview (qc->client_window);
339 
340   win = (GdkWindow *)[ (GdkQuartzView*)nsview gdkWindow];
341   g_object_set_data (G_OBJECT (win), GIC_CURSOR_RECT, qc->cursor_rect);
342 }
343 
344 static void
quartz_set_use_preedit(GtkIMContext * context,gboolean use_preedit)345 quartz_set_use_preedit (GtkIMContext *context, gboolean use_preedit)
346 {
347   GTK_NOTE (MISC, g_print ("quartz_set_use_preedit: %d\n", use_preedit));
348 }
349 
350 static void
commit_cb(GtkIMContext * context,const gchar * str,GtkIMContextQuartz * qc)351 commit_cb (GtkIMContext *context, const gchar *str, GtkIMContextQuartz *qc)
352 {
353   g_signal_emit_by_name (qc, "commit", str);
354 }
355 
356 static void
imquartz_finalize(GObject * obj)357 imquartz_finalize (GObject *obj)
358 {
359   GTK_NOTE (MISC, g_print ("imquartz_finalize\n"));
360 
361   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (obj);
362   g_free (qc->preedit_str);
363   qc->preedit_str = NULL;
364   g_free (qc->cursor_rect);
365   qc->cursor_rect = NULL;
366 
367   g_signal_handlers_disconnect_by_func (qc->slave, (gpointer)commit_cb, qc);
368   g_object_unref (qc->slave);
369 
370   parent_class->finalize (obj);
371 }
372 
373 static void
gtk_im_context_quartz_class_init(GtkIMContextClass * klass)374 gtk_im_context_quartz_class_init (GtkIMContextClass *klass)
375 {
376   GTK_NOTE (MISC, g_print ("gtk_im_context_quartz_class_init\n"));
377 
378   GObjectClass *object_class = G_OBJECT_CLASS (klass);
379   parent_class = g_type_class_peek_parent (klass);
380 
381   klass->get_preedit_string = quartz_get_preedit_string;
382   klass->filter_keypress = quartz_filter_keypress;
383   klass->reset = quartz_reset;
384   klass->set_client_window = quartz_set_client_window;
385   klass->focus_in = quartz_focus_in;
386   klass->focus_out = quartz_focus_out;
387   klass->set_cursor_location = quartz_set_cursor_location;
388   klass->set_use_preedit = quartz_set_use_preedit;
389 
390   object_class->finalize = imquartz_finalize;
391 }
392 
393 static void
gtk_im_context_quartz_init(GtkIMContext * im_context)394 gtk_im_context_quartz_init (GtkIMContext *im_context)
395 {
396   GTK_NOTE (MISC, g_print ("gtk_im_context_quartz_init\n"));
397 
398   GtkIMContextQuartz *qc = GTK_IM_CONTEXT_QUARTZ (im_context);
399   qc->preedit_str = g_strdup ("");
400   qc->cursor_index = 0;
401   qc->selected_len = 0;
402   qc->cursor_rect = g_malloc (sizeof (GdkRectangle));
403   qc->focused = FALSE;
404 
405   qc->slave = g_object_new (GTK_TYPE_IM_CONTEXT_SIMPLE, NULL);
406   g_signal_connect (G_OBJECT (qc->slave), "commit", G_CALLBACK (commit_cb), qc);
407 }
408 
409 static void
gtk_im_context_quartz_register_type(GTypeModule * module)410 gtk_im_context_quartz_register_type (GTypeModule *module)
411 {
412   const GTypeInfo object_info =
413   {
414     sizeof (GtkIMContextQuartzClass),
415     (GBaseInitFunc) NULL,
416     (GBaseFinalizeFunc) NULL,
417     (GClassInitFunc) gtk_im_context_quartz_class_init,
418     NULL,           /* class_finalize */
419     NULL,           /* class_data */
420     sizeof (GtkIMContextQuartz),
421     0,
422     (GInstanceInitFunc) gtk_im_context_quartz_init,
423   };
424 
425   type_quartz =
426     g_type_module_register_type (module,
427                                  GTK_TYPE_IM_CONTEXT,
428                                  "GtkIMContextQuartz",
429                                  &object_info, 0);
430 }
431 
MODULE_ENTRY(void,init)432 MODULE_ENTRY (void, init) (GTypeModule * module)
433 {
434   gtk_im_context_quartz_register_type (module);
435 }
436 
MODULE_ENTRY(void,exit)437 MODULE_ENTRY (void, exit) (void)
438 {
439 }
440 
MODULE_ENTRY(void,list)441 MODULE_ENTRY (void, list) (const GtkIMContextInfo *** contexts, int *n_contexts)
442 {
443   *contexts = info_list;
444   *n_contexts = G_N_ELEMENTS (info_list);
445 }
446 
MODULE_ENTRY(GtkIMContext *,create)447 MODULE_ENTRY (GtkIMContext *, create) (const gchar * context_id)
448 {
449   g_return_val_if_fail (context_id, NULL);
450 
451   if (!strcmp (context_id, "quartz"))
452     {
453       GTK_NOTE (MISC, g_print ("immodule_quartz create\n"));
454       return g_object_new (type_quartz, NULL);
455     }
456   else
457     return NULL;
458 }
459