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                     &GTKIMCONTEXTTIM(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                 &gtkimcontexttim_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                 &gtkimcontexttim_info,
1249                 0);
1250     }
1251 }
1252