1 /*
2 * gtkimcontexttim.c - Table-based Input Method for GTK+ 2 class.
3 *
4 Copyright (C) 2002-2003 Yao Zhang
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; version 2 of the License.
9
10 This program 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
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 * yaoz@users.sourceforge.net
20 */
21 #include <ctype.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include <gdk/gdkkeysyms.h>
27
28 #include "gtkimcontexttim.h"
29 #include "timtable.h"
30
31
32 static GHashTable *TABLES;
33
34 #undef DEBUG
35
36 /**
37 * Locate one UTF-8 character sequence at the current location in string.
38 *
39 * The search starts from current location "cp". First, "cp" has to be in
40 * string, that it, cp >= s and cp <= s+strlen(s). Note that "cp" could be
41 * at the trailing '\0'.
42 *
43 * Then search backward until a valid UTF-8 character leading byte
44 * is obtained. But the search doesn't go beyond 6 bytes (maximum length
45 * of a UTF-8 sequence). If we DO have a leading byte, we also check if
46 * the following bytes are valid or not.
47 *
48 * It returns the length of the valid UTF-8 sequence for the current
49 * character and "cp" will be repositioned to point to the leading byte.
50 * If "cp" is not within a valid UTF-8 character sequence, the function
51 * returns 0 and "cp" is not changed.
52 *
53 * To use this function, "&cp" is passed as the second parameter "acp" (address
54 * of char pointer). If "acp" is NULL, then "cp" is set to "s".
55 */
strutf8seq(const guchar * s,const guchar ** acp)56 static int strutf8seq(const guchar *s, const guchar **acp)
57 {
58 int length;
59 int count;
60 const guchar *cp, *p;
61
62 if (s == NULL) {
63 return 0;
64 }
65
66 if (acp == NULL) {
67 cp = s;
68 } else {
69 cp = *acp;
70 }
71
72 if (cp < s) {
73 return 0;
74 }
75
76 length = strlen((char *)s);
77 if ((cp - s) > length) {
78 return 0;
79 }
80
81 count = 0;
82 p = cp;
83 while (p >= s) {
84 if (((*p)>>6) == 0x02) {
85 /* Following byte, back off. */
86 if ((cp - p) < 5) {
87 p--;
88 continue;
89 }
90 } else if (((*p)>>7) == 0x0) {
91 count = 1;
92 } else if (((*p)>>5) == 0x06) {
93 count = 2;
94 } else if (((*p)>>4) == 0x0e) {
95 count = 3;
96 } else if (((*p)>>3) == 0x1e) {
97 count = 4;
98 } else if (((*p)>>2) == 0x3e) {
99 count = 5;
100 } else if (((*p)>>1) == 0x7e) {
101 count = 6;
102 }
103 break;
104 }
105 if (count > 1) {
106 int i;
107
108 for (i = 1; i < count; i++) {
109 if (((*(p+i))>>6) != 0x02) {
110 return 0;
111 }
112 }
113 if (acp != NULL) {
114 *acp = p;
115 }
116 }
117
118 return count;
119 }
120
get_table(GtkIMContextTIM * this)121 static TIMTable *get_table(GtkIMContextTIM *this)
122 {
123 return (TIMTable *)g_hash_table_lookup(TABLES, this->id);
124 }
125
126 /**
127 * Move candidate window based on cursor position and size.
128 * Note that cursor position here is in root window coordinate.
129 */
candidate_window_move(GtkIMContextTIM * this,int cursor_x,int cursor_y,int cursor_xs,int cursor_ys)130 static void candidate_window_move(GtkIMContextTIM *this,
131 int cursor_x, int cursor_y,
132 int cursor_xs, int cursor_ys)
133 {
134 int x, y;
135
136 gtk_window_get_size(GTK_WINDOW(this->candidate_window),
137 &this->xs, &this->ys);
138
139 x = cursor_x + cursor_xs/2 - this->xs/4;
140 /* Reduce the horizontal position change. */
141 if (((x-this->x) > (-this->xs/4))
142 && ((x-this->x) < (+this->xs/4))) {
143 x = this->x;
144 }
145 if (x < 8) {
146 x = 8;
147 } else if (x+this->xs > gdk_screen_width()-8) {
148 x = gdk_screen_width()-8 - this->xs;
149 }
150
151 y = cursor_y + cursor_ys + 8;
152 if (y < 8) {
153 y = 8;
154 } else if (y+this->ys > gdk_screen_height()-8) {
155 y = gdk_screen_height()-8 - this->ys;
156 }
157 if ((y < cursor_y+cursor_ys) && (y+this->ys > cursor_y)) {
158 y = cursor_y-8 - this->ys;
159 }
160
161 this->x = x;
162 this->y = y;
163
164 if (this->showing) {
165 gtk_window_move(GTK_WINDOW(this->candidate_window), x, y);
166 }
167 }
168
169 enum tim_actions {
170 TIM_KEYIN,
171 TIM_SWITCH,
172 TIM_NEXT,
173 TIM_PREVIOUS,
174 TIM_NEXT_PAGE,
175 TIM_PREVIOUS_PAGE,
176 TIM_SELECT,
177 TIM_SELECT1,
178 TIM_SELECT2,
179 TIM_SELECT3,
180 TIM_SELECT4,
181 TIM_SELECT5,
182 TIM_SELECT6,
183 TIM_SELECT7,
184 TIM_SELECT8,
185 TIM_SELECT9,
186 TIM_SELECT0,
187 TIM_COMMIT,
188 TIM_BACKSPACE,
189 TIM_END
190 };
191
tim_action(guint keyval,guint state)192 static int tim_action(guint keyval, guint state)
193 {
194 int n;
195
196 n = TIM_KEYIN;
197
198 if ((state&(GDK_SHIFT_MASK)) != 0) {
199 switch (keyval) {
200 case GDK_space:
201 n = TIM_SWITCH;
202 break;
203 default:
204 break;
205 }
206 }
207
208 if (n != TIM_KEYIN) {
209 return n;
210 }
211
212 switch (keyval) {
213 case GDK_Escape:
214 n = TIM_SWITCH;
215 break;
216 case GDK_comma:
217 n = TIM_PREVIOUS;
218 break;
219 case GDK_period:
220 n = TIM_NEXT;
221 break;
222 case GDK_less:
223 n = TIM_PREVIOUS_PAGE;
224 break;
225 case GDK_greater:
226 n = TIM_NEXT_PAGE;
227 break;
228 case GDK_space:
229 n = TIM_SELECT;
230 break;
231 case GDK_1:
232 n = TIM_SELECT1;
233 break;
234 case GDK_2:
235 n = TIM_SELECT2;
236 break;
237 case GDK_3:
238 n = TIM_SELECT3;
239 break;
240 case GDK_4:
241 n = TIM_SELECT4;
242 break;
243 case GDK_5:
244 n = TIM_SELECT5;
245 break;
246 case GDK_6:
247 n = TIM_SELECT6;
248 break;
249 case GDK_7:
250 n = TIM_SELECT7;
251 break;
252 case GDK_8:
253 n = TIM_SELECT8;
254 break;
255 case GDK_9:
256 n = TIM_SELECT9;
257 break;
258 case GDK_0:
259 n = TIM_SELECT0;
260 break;
261 case GDK_Return:
262 n = TIM_COMMIT;
263 break;
264 case GDK_BackSpace:
265 n = TIM_BACKSPACE;
266 break;
267 default:
268 break;
269 }
270
271 return n;
272 }
273
handle_keyin(GtkIMContextTIM * tim,GdkEventKey * event)274 static int handle_keyin(GtkIMContextTIM *tim, GdkEventKey *event)
275 {
276 int c;
277
278 c = '\0';
279 if ((event->keyval >= GDK_space)
280 && (event->keyval <= GDK_asciitilde)) {
281 c = event->keyval;
282 } else if (event->keyval == GDK_Return) {
283 c = '\n';
284 }
285
286 if (c != '\0') {
287 g_string_append_c(tim->raw, c);
288 g_string_set_size(tim->candidates, 0);
289 tim->which = NULL;
290 g_signal_emit_by_name(tim, "preedit_changed");
291
292 return TRUE;
293 }
294
295 return FALSE;
296 }
297
handle_switch(GtkIMContextTIM * tim,GdkEventKey * event)298 static int handle_switch(GtkIMContextTIM *tim, GdkEventKey *event)
299 {
300 register int i;
301 register GString *s;
302
303 s = g_string_new(tim->converted->str);
304 i = strutf8seq((guchar *)tim->candidates->str, &tim->which);
305 if (i > 0) {
306 g_string_append_len(s, (gchar *)tim->which, i);
307 } else {
308 g_string_append(s, tim->raw->str);
309 }
310 g_signal_emit_by_name(tim, "commit", s->str);
311 g_string_free(s, TRUE);
312 g_string_set_size(tim->converted, 0);
313 g_string_set_size(tim->raw, 0);
314 g_string_set_size(tim->candidates, 0);
315 tim->which = NULL;
316 g_signal_emit_by_name(tim, "preedit_changed");
317 tim->ascii_mode = TRUE;
318
319 return TRUE;
320 }
321
next_n(GtkIMContextTIM * tim,int n)322 static void next_n(GtkIMContextTIM *tim, int n)
323 {
324 if ((tim->candidates->len > 0)
325 && (strchr(tim->candidates->str, US) != NULL)) {
326 while (n--) {
327 register int i;
328
329 i = strutf8seq((guchar *)tim->candidates->str, &tim->which);
330 if (i > 0) {
331 const guchar *p;
332
333 p = (guchar *)strchr((char *)tim->which, US);
334 tim->which = (guchar *)tim->candidates->str;
335 if (p != NULL) {
336 p++;
337 i = strutf8seq((guchar *)tim->candidates->str, &p);
338 if (i > 0) {
339 if (*p != '\0') {
340 tim->which = p;
341 }
342 }
343 }
344 }
345 }
346 }
347 }
348
previous_n(GtkIMContextTIM * tim,int n)349 static void previous_n(GtkIMContextTIM *tim, int n)
350 {
351 if ((tim->candidates->len > 0)
352 && (strchr(tim->candidates->str, US) != NULL)) {
353 while (n--) {
354 register int i;
355
356 i = strutf8seq((guchar *)tim->candidates->str, &tim->which);
357 if (i > 0) {
358 const guchar *p;
359
360 p = (guchar *)strrchr(tim->candidates->str, US);
361 if (p != NULL) {
362 const guchar *q;
363
364 q = tim->which - 2;
365 if (q < (guchar *)tim->candidates->str) {
366 p++;
367 i = strutf8seq((guchar *)tim->candidates->str, &p);
368 if (i > 0) {
369 if (*p != '\0') {
370 tim->which = p;
371 }
372 }
373 } else {
374 tim->which = (guchar *)tim->candidates->str;
375 while (q >= (guchar *)tim->candidates->str) {
376 if (*q == US) {
377 q++;
378 i = strutf8seq((guchar *)tim->candidates->str, &q);
379 if (i > 0) {
380 if (*q != '\0') {
381 tim->which = q;
382 }
383 }
384 break;
385 }
386 q--;
387 }
388 }
389 }
390 }
391 }
392 }
393 }
394
handle_next(GtkIMContextTIM * tim,GdkEventKey * event)395 static int handle_next(GtkIMContextTIM *tim, GdkEventKey *event)
396 {
397 if ((tim->candidates->len > 0)
398 && (strchr(tim->candidates->str, US) != NULL)) {
399 next_n(tim, 1);
400 } else {
401 handle_keyin(tim, event);
402 }
403
404 return TRUE;
405 }
406
handle_previous(GtkIMContextTIM * tim,GdkEventKey * event)407 static int handle_previous(GtkIMContextTIM *tim, GdkEventKey *event)
408 {
409 if ((tim->candidates->len > 0)
410 && (strchr(tim->candidates->str, US) != NULL)) {
411 previous_n(tim, 1);
412 } else {
413 handle_keyin(tim, event);
414 }
415
416 return TRUE;
417 }
418
handle_next_page(GtkIMContextTIM * tim,GdkEventKey * event)419 static int handle_next_page(GtkIMContextTIM *tim, GdkEventKey *event)
420 {
421 if ((tim->candidates->len > 0)
422 && (strchr(tim->candidates->str, US) != NULL)) {
423 next_n(tim, 10);
424 } else {
425 handle_keyin(tim, event);
426 }
427
428 return TRUE;
429 }
430
handle_previous_page(GtkIMContextTIM * tim,GdkEventKey * event)431 static int handle_previous_page(GtkIMContextTIM *tim, GdkEventKey *event)
432 {
433 if ((tim->candidates->len > 0)
434 && (strchr(tim->candidates->str, US) != NULL)) {
435 previous_n(tim, 10);
436 } else {
437 handle_keyin(tim, event);
438 }
439
440 return TRUE;
441 }
442
handle_select(GtkIMContextTIM * tim,GdkEventKey * event)443 static int handle_select(GtkIMContextTIM *tim, GdkEventKey *event)
444 {
445 register int i;
446
447 i = strutf8seq((guchar *)tim->candidates->str, &tim->which);
448 if (i > 0) {
449 guchar *p;
450
451 p = (guchar *)strchr((char *)tim->which, US);
452 if (p != NULL) {
453 i = p - tim->which;
454 } else {
455 i = strlen((char *)tim->which);
456 }
457 g_string_append_len(tim->converted, (gchar *)tim->which, i);
458 } else if (tim->raw->len > 0) {
459 g_string_append(tim->converted, tim->raw->str);
460 } else {
461 if ((event->keyval >= GDK_space)
462 && (event->keyval <= GDK_asciitilde)) {
463 g_string_append_c(tim->converted, event->keyval);
464 } else if (event->keyval == GDK_Return) {
465 g_string_append_c(tim->converted, '\n');
466 }
467 }
468 g_string_set_size(tim->raw, 0);
469 g_string_set_size(tim->candidates, 0);
470 tim->which = NULL;
471 g_signal_emit_by_name(tim, "preedit_changed");
472
473 return TRUE;
474 }
475
handle_select_n(GtkIMContextTIM * tim,GdkEventKey * event,int n)476 static int handle_select_n(GtkIMContextTIM *tim, GdkEventKey *event, int n)
477 {
478 guchar *p;
479
480 p = NULL;
481
482 if ((tim->candidates->len > 0)
483 && (strchr(tim->candidates->str, US) != NULL)) {
484 register int i;
485 GString *s;
486
487 s = g_string_new((gchar *)tim->which);
488 if ((gchar *)tim->which != tim->candidates->str) {
489 g_string_append_c(s, US);
490 g_string_append_len(s, tim->candidates->str,
491 (gchar *)tim->which-1 - tim->candidates->str);
492 }
493
494 p = (guchar *)s->str;
495 for (i = 0; i < n; i++) {
496 p = (guchar *)strchr((char *)p, US);
497 if (p != NULL) {
498 p++;
499 } else {
500 break;
501 }
502 }
503
504 if (p != NULL) {
505 guchar *q;
506
507 q = (guchar *)strchr((char *)p, US);
508 if (q != NULL) {
509 i = q - p;
510 } else {
511 i = strlen((char *)p);
512 }
513 g_string_append_len(tim->converted, (gchar *)p, i);
514 g_string_set_size(tim->raw, 0);
515 g_string_set_size(tim->candidates, 0);
516 tim->which = NULL;
517 g_signal_emit_by_name(tim, "preedit_changed");
518 }
519
520 g_string_free(s, TRUE);
521 }
522
523 if (p == NULL) {
524 handle_keyin(tim, event);
525 }
526
527 return TRUE;
528 }
529
handle_select1(GtkIMContextTIM * tim,GdkEventKey * event)530 static int handle_select1(GtkIMContextTIM *tim, GdkEventKey *event)
531 {
532 return handle_select_n(tim, event, 1);
533 }
534
handle_select2(GtkIMContextTIM * tim,GdkEventKey * event)535 static int handle_select2(GtkIMContextTIM *tim, GdkEventKey *event)
536 {
537 return handle_select_n(tim, event, 2);
538 }
539
handle_select3(GtkIMContextTIM * tim,GdkEventKey * event)540 static int handle_select3(GtkIMContextTIM *tim, GdkEventKey *event)
541 {
542 return handle_select_n(tim, event, 3);
543 }
544
handle_select4(GtkIMContextTIM * tim,GdkEventKey * event)545 static int handle_select4(GtkIMContextTIM *tim, GdkEventKey *event)
546 {
547 return handle_select_n(tim, event, 4);
548 }
549
handle_select5(GtkIMContextTIM * tim,GdkEventKey * event)550 static int handle_select5(GtkIMContextTIM *tim, GdkEventKey *event)
551 {
552 return handle_select_n(tim, event, 5);
553 }
554
handle_select6(GtkIMContextTIM * tim,GdkEventKey * event)555 static int handle_select6(GtkIMContextTIM *tim, GdkEventKey *event)
556 {
557 return handle_select_n(tim, event, 6);
558 }
559
handle_select7(GtkIMContextTIM * tim,GdkEventKey * event)560 static int handle_select7(GtkIMContextTIM *tim, GdkEventKey *event)
561 {
562 return handle_select_n(tim, event, 7);
563 }
564
handle_select8(GtkIMContextTIM * tim,GdkEventKey * event)565 static int handle_select8(GtkIMContextTIM *tim, GdkEventKey *event)
566 {
567 return handle_select_n(tim, event, 8);
568 }
569
handle_select9(GtkIMContextTIM * tim,GdkEventKey * event)570 static int handle_select9(GtkIMContextTIM *tim, GdkEventKey *event)
571 {
572 return handle_select_n(tim, event, 9);
573 }
574
handle_select0(GtkIMContextTIM * tim,GdkEventKey * event)575 static int handle_select0(GtkIMContextTIM *tim, GdkEventKey *event)
576 {
577 return handle_select_n(tim, event, 10);
578 }
579
handle_commit(GtkIMContextTIM * tim,GdkEventKey * event)580 static int handle_commit(GtkIMContextTIM *tim, GdkEventKey *event)
581 {
582 register int i;
583 GString *s;
584
585 s = g_string_new(tim->converted->str);
586 i = strutf8seq((guchar *)tim->candidates->str, &tim->which);
587 if (i > 0) {
588 guchar *p;
589
590 p = (guchar *)strchr((char *)tim->which, US);
591 if (p != NULL) {
592 i = p - tim->which;
593 } else {
594 i = strlen((char *)tim->which);
595 }
596 g_string_append_len(s, (gchar *)tim->which, i);
597 } else {
598 g_string_append(s, tim->raw->str);
599 }
600 if (s->len <= 0) {
601 if ((event->keyval >= GDK_space)
602 && (event->keyval <= GDK_asciitilde)) {
603 g_string_append_c(s, event->keyval);
604 } else if (event->keyval == GDK_Return) {
605 g_string_append_c(s, '\n');
606 }
607 }
608 g_signal_emit_by_name(tim, "commit", s->str);
609 g_string_free(s, TRUE);
610 g_string_set_size(tim->converted, 0);
611 g_string_set_size(tim->raw, 0);
612 g_string_set_size(tim->candidates, 0);
613 tim->which = NULL;
614 g_signal_emit_by_name(tim, "preedit_changed");
615
616 return TRUE;
617 }
618
handle_backspace(GtkIMContextTIM * tim,GdkEventKey * event)619 static int handle_backspace(GtkIMContextTIM *tim, GdkEventKey *event)
620 {
621 register int i;
622 TIMTable *table;
623 GString *s;
624
625 table = get_table(tim);
626 if (table == NULL) {
627 return FALSE;
628 }
629
630 if (tim->raw->len > 0) {
631 g_string_truncate(tim->raw, tim->raw->len - 1);
632 s = TIMTABLE_GET_CLASS(table)->lookup(table, tim->raw);
633 if (s != NULL) {
634 g_string_assign(tim->candidates, s->str);
635 tim->which = (guchar *)tim->candidates->str;
636 } else {
637 g_string_set_size(tim->candidates, 0);
638 tim->which = NULL;
639 }
640 } else if (tim->converted->len > 0) {
641 const guchar *p;
642
643 p = (guchar *)(tim->converted->str + tim->converted->len - 1);
644 i = strutf8seq((guchar *)tim->converted->str, &p);
645 if (i > 0) {
646 g_string_truncate(tim->converted,
647 tim->converted->len - i);
648 }
649 } else {
650 return FALSE;
651 }
652 g_signal_emit_by_name(tim, "preedit_changed");
653
654 return TRUE;
655 }
656
657 static struct {
658 enum tim_actions action;
659 int (*handler)(GtkIMContextTIM *tim, GdkEventKey *event);
660 } HANDLERS[] = {
661 {TIM_KEYIN, handle_keyin},
662 {TIM_SWITCH, handle_switch},
663 {TIM_NEXT, handle_next},
664 {TIM_PREVIOUS, handle_previous},
665 {TIM_NEXT_PAGE, handle_next_page},
666 {TIM_PREVIOUS_PAGE, handle_previous_page},
667 {TIM_SELECT, handle_select},
668 {TIM_SELECT1, handle_select1},
669 {TIM_SELECT2, handle_select2},
670 {TIM_SELECT3, handle_select3},
671 {TIM_SELECT4, handle_select4},
672 {TIM_SELECT5, handle_select5},
673 {TIM_SELECT6, handle_select6},
674 {TIM_SELECT7, handle_select7},
675 {TIM_SELECT8, handle_select8},
676 {TIM_SELECT9, handle_select9},
677 {TIM_SELECT0, handle_select0},
678 {TIM_COMMIT, handle_commit},
679 {TIM_BACKSPACE, handle_backspace},
680 {TIM_END, NULL}
681 };
682
process_candidates(GString * s)683 static void process_candidates(GString *s)
684 {
685 register int i;
686 register gchar *p;
687 gchar buffer[32];
688
689 strcpy(buffer, "<sup><b><i> </i></b></sup>");
690 g_string_insert(s, 0, buffer);
691 for (i = 1; i <= 10; i++) {
692 p = strchr(s->str, US);
693 if (p != NULL) {
694 *p = ' ';
695 buffer[11] = (i%10) + '0';
696 g_string_insert(s, p+1 - s->str, buffer);
697
698 } else {
699 break;
700 }
701 }
702
703 while ((p = strchr(s->str, US)) != NULL) {
704 *p = ' ';
705 }
706
707 strcpy(buffer, "<big>");
708 g_string_prepend(s, buffer);
709 strcpy(buffer, "</big>");
710 g_string_append(s, buffer);
711 }
712
713
candidate_window_expose_callback(GtkWidget * widget,GdkEventExpose * event,gpointer data)714 static gboolean candidate_window_expose_callback(GtkWidget *widget,
715 GdkEventExpose *event,
716 gpointer data)
717 {
718 GtkRequisition requisition;
719
720 gtk_widget_size_request(widget, &requisition);
721 gtk_paint_flat_box(widget->style, widget->window,
722 GTK_STATE_NORMAL, GTK_SHADOW_NONE,
723 NULL, widget, "tooltip",
724 0, 0, requisition.width, requisition.height);
725
726 return FALSE;
727 }
728
gtkimcontexttim_init(GTypeInstance * this,gpointer class)729 static void gtkimcontexttim_init(GTypeInstance *this, gpointer class)
730 {
731 if (TABLES == NULL) {
732 TABLES = g_hash_table_new_full((GHashFunc)g_str_hash,
733 (GEqualFunc)g_str_equal,
734 (GDestroyNotify)g_free,
735 NULL);
736 }
737
738 GTKIMCONTEXTTIM(this)->debug = 0;
739 if (getenv("WJ_DEBUG") != NULL) {
740 GTKIMCONTEXTTIM(this)->debug = 1;
741 }
742 if (GTKIMCONTEXTTIM(this)->debug) {
743 printf("gtkimcontexttim_init\n");
744 }
745
746 GTKIMCONTEXTTIM(this)->carryover_state = 0;
747 GTKIMCONTEXTTIM(this)->client_window = NULL;
748 GTKIMCONTEXTTIM(this)->ascii_mode = FALSE;
749 GTKIMCONTEXTTIM(this)->converted = g_string_new("");
750 GTKIMCONTEXTTIM(this)->raw = g_string_new("");
751 GTKIMCONTEXTTIM(this)->candidates = g_string_new("");
752 GTKIMCONTEXTTIM(this)->which = NULL;
753 GTKIMCONTEXTTIM(this)->candidate_window = gtk_window_new(GTK_WINDOW_POPUP);
754 gtk_widget_set_app_paintable(GTKIMCONTEXTTIM(this)->candidate_window, TRUE);
755 gtk_container_set_border_width(GTK_CONTAINER(GTKIMCONTEXTTIM(this)->candidate_window), 1);
756 g_signal_connect(GTKIMCONTEXTTIM(this)->candidate_window,
757 "expose_event",
758 (GCallback)candidate_window_expose_callback,
759 this);
760 GTKIMCONTEXTTIM(this)->candidate_label = gtk_label_new("");
761 gtk_misc_set_alignment(GTK_MISC(GTKIMCONTEXTTIM(this)->candidate_label),
762 0, 0.5);
763 gtk_container_add(GTK_CONTAINER(GTKIMCONTEXTTIM(this)->candidate_window),
764 GTKIMCONTEXTTIM(this)->candidate_label);
765 /*
766 g_signal_connect(GTKIMCONTEXTTIM(this)->candidate_window,
767 "destroy",
768 (GCallback)gtk_widget_destroyed,
769 >KIMCONTEXTTIM(this)->candidate_window);
770 */
771 GTKIMCONTEXTTIM(this)->showing = 0;
772 GTKIMCONTEXTTIM(this)->x = 0;
773 GTKIMCONTEXTTIM(this)->y = 0;
774 GTKIMCONTEXTTIM(this)->xs = 384;
775 GTKIMCONTEXTTIM(this)->ys = -1;
776 gtk_widget_set_size_request(GTKIMCONTEXTTIM(this)->candidate_window,
777 GTKIMCONTEXTTIM(this)->xs,
778 GTKIMCONTEXTTIM(this)->ys);
779 gtk_window_move(GTK_WINDOW(GTKIMCONTEXTTIM(this)->candidate_window),
780 64, 64);
781 GTKIMCONTEXTTIM(this)->auto_commit = -1;
782 GTKIMCONTEXTTIM(this)->id = NULL;
783 }
784
gtkimcontexttim_finalize(GObject * this)785 static void gtkimcontexttim_finalize(GObject *this)
786 {
787 GtkIMContext *super_class;
788
789 if (GTKIMCONTEXTTIM(this)->debug) {
790 printf("gtkimcontexttim_finalize\n");
791 }
792
793 if (TABLES) {
794 TIMTable *table;
795
796 table = get_table(GTKIMCONTEXTTIM(this));
797 if (table != NULL) {
798 if (G_OBJECT(table)->ref_count == 1) {
799 g_hash_table_remove(TABLES, GTKIMCONTEXTTIM(this)->id);
800 }
801 g_object_unref(table);
802 }
803 if (g_hash_table_size(TABLES) <= 0) {
804 if (GTKIMCONTEXTTIM(this)->debug) {
805 printf("Destroying TABLES.\n");
806 }
807 g_hash_table_destroy(TABLES);
808 TABLES = NULL;
809 }
810 }
811
812 /* GtkIMContextTIM specific finalization. */
813 if (GTKIMCONTEXTTIM(this)->client_window != NULL) {
814 g_object_unref(GTKIMCONTEXTTIM(this)->client_window);
815 }
816 g_string_free(GTKIMCONTEXTTIM(this)->converted, TRUE);
817 g_string_free(GTKIMCONTEXTTIM(this)->raw, TRUE);
818 g_string_free(GTKIMCONTEXTTIM(this)->candidates, TRUE);
819 /*
820 g_signal_emit_by_name(GTKIMCONTEXTTIM(this)->candidate_window,
821 "destroy");
822 */
823 gtk_widget_destroy(GTKIMCONTEXTTIM(this)->candidate_window);
824 if (GTKIMCONTEXTTIM(this)->id != NULL) {
825 g_free(GTKIMCONTEXTTIM(this)->id);
826 }
827
828 /* Super class finalizatoin. */
829 super_class = g_type_class_peek(g_type_parent(GTKIMCONTEXTTIM_TYPE));
830 G_OBJECT_CLASS(super_class)->finalize(this);
831 }
832
gtkimcontexttim_set(GtkIMContextTIM * this,const gchar * id)833 static void gtkimcontexttim_set(GtkIMContextTIM *this, const gchar *id)
834 {
835 if (GTKIMCONTEXTTIM(this)->debug) {
836 printf("gtkimcontexttim_set\n");
837 }
838
839 if (this->id != NULL) {
840 g_free(this->id);
841 this->id = NULL;
842 }
843 if (id != NULL) {
844 TIMTable *table;
845
846 this->id = g_strdup(id);
847
848 table = get_table(this);
849 if (table == NULL) {
850 table = g_object_new(TIMTABLE_TYPE, NULL);
851 TIMTABLE_GET_CLASS(table)->load(table, "my.tim");
852 TIMTABLE_GET_CLASS(table)->load(table, this->id);
853 g_hash_table_insert(TABLES, g_strdup(this->id), table);
854 } else {
855 g_object_ref(table);
856 }
857 }
858 }
859
gtkimcontexttim_set_client_window(GtkIMContext * this,GdkWindow * client_window)860 static void gtkimcontexttim_set_client_window(GtkIMContext *this,
861 GdkWindow *client_window)
862 {
863 GtkIMContextTIM *this_tim = GTKIMCONTEXTTIM(this);
864
865 if (GTKIMCONTEXTTIM(this)->debug) {
866 printf("gtkimcontexttim_set_client_window\n");
867 }
868
869 if (this_tim->client_window != NULL) {
870 g_object_unref(this_tim->client_window);
871 this_tim->client_window = NULL;
872 }
873 if (client_window != NULL) {
874 this_tim->client_window = g_object_ref(client_window);
875 }
876 }
877
carryover_state(GtkIMContextTIM * this,GdkEventKey * event)878 static void carryover_state(GtkIMContextTIM *this, GdkEventKey *event)
879 {
880 guint state;
881
882 state = event->state;
883 event->state |= this->carryover_state;
884
885 switch (event->keyval) {
886 case GDK_Shift_L:
887 case GDK_Shift_R:
888 this->carryover_state |= state | GDK_SHIFT_MASK;
889 break;
890 case GDK_Control_L:
891 case GDK_Control_R:
892 this->carryover_state |= state | GDK_CONTROL_MASK;
893 break;
894 case GDK_Caps_Lock:
895 case GDK_Shift_Lock:
896 this->carryover_state |= state | GDK_LOCK_MASK;
897 break;
898 case GDK_Meta_L:
899 case GDK_Meta_R:
900 break;
901 case GDK_Alt_L:
902 case GDK_Alt_R:
903 this->carryover_state |= state | GDK_MOD1_MASK;
904 break;
905 case GDK_Super_L: /* Windows */
906 case GDK_Super_R:
907 this->carryover_state |= state | GDK_MOD4_MASK;
908 break;
909 case GDK_Hyper_L:
910 case GDK_Hyper_R:
911 break;
912 default:
913 this->carryover_state = 0;
914 break;
915 }
916 }
917
gtkimcontexttim_filter_keypress(GtkIMContext * this,GdkEventKey * event)918 static gboolean gtkimcontexttim_filter_keypress(GtkIMContext *this,
919 GdkEventKey *event)
920 {
921 register int handled;
922 register GString *s;
923 GtkIMContextTIM *this_tim = GTKIMCONTEXTTIM(this);
924
925 if (GTKIMCONTEXTTIM(this)->debug) {
926 printf("gtkimcontexttim_filter_keypress: state - 0x%X, keyval - 0x%X\n",
927 event->state, event->keyval);
928 }
929
930 if (event->type != GDK_KEY_PRESS) {
931 return FALSE;
932 }
933
934 /**
935 * Allow special key state can be carried over to the next keypress
936 * so that, for example, Ctrl+1 can be typed as press Ctrl key and
937 * release it, then press 1 key.
938 */
939 carryover_state(this_tim, event);
940
941 if (this_tim->ascii_mode) {
942 if (((event->state&(GDK_SHIFT_MASK)) != 0)
943 && (event->keyval == GDK_space)) {
944 this_tim->ascii_mode = FALSE;
945 } else {
946 guint32 u;
947 guchar utf8[16];
948
949 u = gdk_keyval_to_unicode(event->keyval);
950 if (u == 0) {
951 return FALSE;
952 }
953 utf8[g_unichar_to_utf8(u, (gchar *)utf8)] = '\0';
954 g_signal_emit_by_name(this_tim, "commit", utf8);
955 }
956 return TRUE;
957 }
958
959 handled = FALSE;
960 /**
961 * First thing's first: try to look it up
962 * if NOT with special control keys like
963 * Ctrl, Alt, etc. Those special control
964 * keys make the key combination processed
965 * as command.
966 */
967 if (!(event->state&~(GDK_SHIFT_MASK|GDK_LOCK_MASK))
968 && (event->keyval >= GDK_space)
969 && (event->keyval <= GDK_asciitilde)) {
970 TIMTable *table;
971
972 table = get_table(this_tim);
973 if (table != NULL) {
974 GString *k;
975
976 k = g_string_new(this_tim->raw->str);
977 g_string_append_c(k, event->keyval);
978 s = TIMTABLE_GET_CLASS(table)->lookup(table, k);
979 if (s != NULL) {
980 g_string_append_c(this_tim->raw, event->keyval);
981 g_string_assign(this_tim->candidates, s->str);
982 this_tim->which = (guchar *)this_tim->candidates->str;
983 g_signal_emit_by_name(this_tim, "preedit_changed");
984 handled = TRUE;
985 /* AutoCommit */
986 if (this_tim->auto_commit < 0) {
987 GString *key, *value;
988
989 key = g_string_new("AutoCommit");
990 value = TIMTABLE_GET_CLASS(table)->lookup(table, key);
991 GTKIMCONTEXTTIM(this)->auto_commit = 0;
992 if (value != NULL) {
993 GTKIMCONTEXTTIM(this)->auto_commit = atoi(value->str);
994 }
995 g_string_free(key, TRUE);
996 }
997 if (this_tim->auto_commit && strchr(s->str, US) == NULL) {
998 g_signal_emit_by_name(this_tim, "commit", s->str);
999 g_string_set_size(this_tim->converted, 0);
1000 g_string_set_size(this_tim->raw, 0);
1001 g_string_set_size(this_tim->candidates, 0);
1002 this_tim->which = NULL;
1003 g_signal_emit_by_name(this_tim, "preedit_changed");
1004 }
1005 }
1006 g_string_free(k, TRUE);
1007 }
1008 }
1009
1010 /* No lookup result. Handle it as command. */
1011 if (!handled) {
1012 handled = HANDLERS[tim_action(event->keyval, event->state)].
1013 handler(this_tim, event);
1014 }
1015
1016 this_tim->showing = 0;
1017 if (strchr(this_tim->candidates->str, US) != NULL) {
1018 this_tim->showing = 1;
1019 }
1020 if (this_tim->showing) {
1021 s = g_string_new((gchar *)this_tim->which);
1022 if ((gchar *)this_tim->which != this_tim->candidates->str) {
1023 g_string_append_c(s, US);
1024 g_string_append_len(s, this_tim->candidates->str,
1025 (gchar *)this_tim->which-1 - this_tim->candidates->str);
1026 }
1027 process_candidates(s);
1028 gtk_label_set_markup(GTK_LABEL(this_tim->candidate_label), s->str);
1029 g_string_free(s, TRUE);
1030 gtk_widget_show_all(this_tim->candidate_window);
1031 } else {
1032 gtk_widget_hide(this_tim->candidate_window);
1033 }
1034
1035 return handled;
1036 }
1037
gtkimcontexttim_focus_in(GtkIMContext * this)1038 static void gtkimcontexttim_focus_in(GtkIMContext *this)
1039 {
1040 GtkIMContextTIM *this_tim = GTKIMCONTEXTTIM(this);
1041
1042 if (GTKIMCONTEXTTIM(this)->debug) {
1043 printf("gtkimcontexttim_focus_in\n");
1044 }
1045
1046 if (this_tim->showing) {
1047 gtk_widget_show_all(this_tim->candidate_window);
1048 }
1049
1050 return;
1051 }
1052
gtkimcontexttim_focus_out(GtkIMContext * this)1053 static void gtkimcontexttim_focus_out(GtkIMContext *this)
1054 {
1055 GtkIMContextTIM *this_tim = GTKIMCONTEXTTIM(this);
1056
1057 if (GTKIMCONTEXTTIM(this)->debug) {
1058 printf("gtkimcontexttim_focus_out\n");
1059 }
1060
1061 if (this_tim->showing) {
1062 gtk_widget_hide(this_tim->candidate_window);
1063 }
1064
1065 return;
1066 }
1067
gtkimcontexttim_set_cursor_location(GtkIMContext * this,GdkRectangle * area)1068 static void gtkimcontexttim_set_cursor_location(GtkIMContext *this,
1069 GdkRectangle *area)
1070 {
1071 gint x, y;
1072 GtkIMContextTIM *this_tim = GTKIMCONTEXTTIM(this);
1073
1074 if (GTKIMCONTEXTTIM(this)->debug) {
1075 printf("gtkimcontexttim_set_cursor_location: %d, %d, %d, %d\n",
1076 area->x, area->y, area->width, area->height);
1077 }
1078
1079 if (this_tim->client_window != NULL) {
1080 gdk_window_get_origin(this_tim->client_window, &x, &y);
1081 candidate_window_move(this_tim,
1082 x + area->x, y + area->y,
1083 area->width, area->height);
1084 }
1085
1086 return;
1087 }
1088
gtkimcontexttim_reset(GtkIMContext * this)1089 static void gtkimcontexttim_reset(GtkIMContext *this)
1090 {
1091 if (GTKIMCONTEXTTIM(this)->debug) {
1092 printf("gtkimcontexttim_reset\n");
1093 }
1094
1095 return;
1096 }
1097
gtkimcontexttim_get_preedit_string(GtkIMContext * this,gchar ** str,PangoAttrList ** attrs,gint * cursor_pos)1098 static void gtkimcontexttim_get_preedit_string(GtkIMContext *this,
1099 gchar **str,
1100 PangoAttrList **attrs,
1101 gint *cursor_pos)
1102 {
1103 register int i;
1104 GtkIMContextTIM *this_tim = GTKIMCONTEXTTIM(this);
1105
1106 if (GTKIMCONTEXTTIM(this)->debug) {
1107 printf("gtkimcontexttim_get_preedit_string\n");
1108 }
1109
1110 i = strutf8seq((guchar *)this_tim->candidates->str, &this_tim->which);
1111 if (i > 0) {
1112 guchar *p;
1113
1114 i = -1;
1115 p = (guchar *)strchr((char *)this_tim->which, US);
1116 if (p == NULL) {
1117 p = (guchar *)strchr(this_tim->candidates->str, US);
1118 if (p == NULL) {
1119 i = strlen((char *)this_tim->which);
1120 }
1121 }
1122 }
1123
1124 if (str) {
1125 GString *s;
1126
1127 s = g_string_new(this_tim->converted->str);
1128 if (i > 0) {
1129 g_string_append_len(s, (char *)this_tim->which, i);
1130 } else {
1131 g_string_append(s, this_tim->raw->str);
1132 }
1133 *str = g_string_free(s, FALSE);
1134 }
1135
1136 if (attrs) {
1137 PangoAttribute *attr;
1138
1139 *attrs = pango_attr_list_new();
1140
1141 /* underline */
1142 attr = pango_attr_underline_new(PANGO_UNDERLINE_SINGLE);
1143 attr->start_index = 0;
1144 attr->end_index = this_tim->converted->len;
1145 if (i > 0) {
1146 attr->end_index += i;
1147 } else {
1148 attr->end_index += this_tim->raw->len;;
1149 }
1150 pango_attr_list_insert(*attrs, attr);
1151 /* italic */
1152 attr = pango_attr_style_new(PANGO_STYLE_ITALIC);
1153 attr->start_index = this_tim->converted->len;
1154 attr->end_index = this_tim->converted->len;
1155 if (i > 0) {
1156 attr->end_index += i;
1157 } else {
1158 attr->end_index += this_tim->raw->len;;
1159 }
1160 pango_attr_list_insert(*attrs, attr);
1161 }
1162
1163 if (cursor_pos) {
1164 *cursor_pos = this_tim->converted->len;
1165 if (i > 0) {
1166 *cursor_pos += i;
1167 } else {
1168 *cursor_pos += this_tim->raw->len;;
1169 }
1170 }
1171 }
1172
gtkimcontexttim_class_init(gpointer class,gpointer class_data)1173 static void gtkimcontexttim_class_init(gpointer class, gpointer class_data)
1174 {
1175 #ifdef DEBUG
1176 printf("gtkimcontexttim_class_init\n");
1177 #endif
1178 /* GtkIMContextTIMClass specific methods initialization. */
1179 GTKIMCONTEXTTIM_CLASS(class)->set = gtkimcontexttim_set;
1180
1181 /* Override super class methods. */
1182 GTK_IM_CONTEXT_CLASS(class)->set_client_window = gtkimcontexttim_set_client_window;
1183 GTK_IM_CONTEXT_CLASS(class)->filter_keypress = gtkimcontexttim_filter_keypress;
1184 GTK_IM_CONTEXT_CLASS(class)->reset = gtkimcontexttim_reset;
1185 GTK_IM_CONTEXT_CLASS(class)->get_preedit_string = gtkimcontexttim_get_preedit_string;
1186 GTK_IM_CONTEXT_CLASS(class)->focus_in = gtkimcontexttim_focus_in;
1187 GTK_IM_CONTEXT_CLASS(class)->focus_out = gtkimcontexttim_focus_out;
1188 GTK_IM_CONTEXT_CLASS(class)->set_cursor_location = gtkimcontexttim_set_cursor_location;
1189
1190 /* Override GObjectClass finalize(). */
1191 G_OBJECT_CLASS(class)->finalize = gtkimcontexttim_finalize;
1192 }
1193
1194 static GType gtkimcontexttim_type = 0;
1195
gtkimcontexttim_get_type(void)1196 GType gtkimcontexttim_get_type(void)
1197 {
1198 #ifdef DEBUG
1199 printf("gtkimcontexttim_get_type\n");
1200 #endif
1201
1202 if (!gtkimcontexttim_type) {
1203 static const GTypeInfo gtkimcontexttim_info = {
1204 sizeof(GtkIMContextTIMClass),
1205 NULL, /* base_init */
1206 NULL, /* base_finalize */
1207 gtkimcontexttim_class_init,
1208 NULL, /* class_finalize */
1209 NULL, /* class_data */
1210 sizeof(GtkIMContextTIM),
1211 0, /* n_preallocs */
1212 gtkimcontexttim_init
1213 };
1214
1215 gtkimcontexttim_type = g_type_register_static(
1216 GTK_TYPE_IM_CONTEXT,
1217 "GtkIMContextTIM",
1218 >kimcontexttim_info,
1219 0);
1220 }
1221
1222 return gtkimcontexttim_type;
1223 }
1224
gtkimcontexttim_register_type(GTypeModule * type_module)1225 void gtkimcontexttim_register_type(GTypeModule *type_module)
1226 {
1227 #ifdef DEBUG
1228 printf("gtkimcontexttim_register_type\n");
1229 #endif
1230
1231 if (!gtkimcontexttim_type) {
1232 static const GTypeInfo gtkimcontexttim_info = {
1233 sizeof(GtkIMContextTIMClass),
1234 NULL, /* base_init */
1235 NULL, /* base_finalize */
1236 gtkimcontexttim_class_init,
1237 NULL, /* class_finalize */
1238 NULL, /* class_data */
1239 sizeof(GtkIMContextTIM),
1240 0, /* n_preallocs */
1241 gtkimcontexttim_init
1242 };
1243
1244 gtkimcontexttim_type = g_type_module_register_type(
1245 type_module,
1246 GTK_TYPE_IM_CONTEXT,
1247 "GtkIMContextTIM",
1248 >kimcontexttim_info,
1249 0);
1250 }
1251 }
1252