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