]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextthai.c
640c013c5fedbe2ea0e20a3be2bd0a44985d863c
[~andy/gtk] / modules / input / gtkimcontextthai.c
1 /* GTK - The GIMP Toolkit
2  *
3  * This library is free software; you can redistribute it and/or
4  * modify it under the terms of the GNU Lesser General Public
5  * License as published by the Free Software Foundation; either
6  * version 2 of the License, or (at your option) any later version.
7  *
8  * This library is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  * Lesser General Public License for more details.
12  *
13  * You should have received a copy of the GNU Lesser General Public
14  * License along with this library; if not, write to the
15  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
16  * Boston, MA 02111-1307, USA.
17  *
18  * Author:  Theppitak Karoonboonyanan <thep@linux.thai.net>
19  *
20  */
21
22 #include <string.h>
23
24 #include <gdk/gdkkeysyms.h>
25 #include "gtkimcontextthai.h"
26 #include "thai-charprop.h"
27
28 static void     gtk_im_context_thai_class_init          (GtkIMContextThaiClass *class);
29 static void     gtk_im_context_thai_init                (GtkIMContextThai      *im_context_thai);
30 static gboolean gtk_im_context_thai_filter_keypress     (GtkIMContext          *context,
31                                                          GdkEventKey           *key);
32
33 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
34 static void     forget_previous_chars (GtkIMContextThai *context_thai);
35 static void     remember_previous_char (GtkIMContextThai *context_thai,
36                                         gunichar new_char);
37 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
38
39 static GObjectClass *parent_class;
40
41 GType gtk_type_im_context_thai = 0;
42
43 void
44 gtk_im_context_thai_register_type (GTypeModule *type_module)
45 {
46   static const GTypeInfo im_context_thai_info =
47   {
48     sizeof (GtkIMContextThaiClass),
49     (GBaseInitFunc) NULL,
50     (GBaseFinalizeFunc) NULL,
51     (GClassInitFunc) gtk_im_context_thai_class_init,
52     NULL,           /* class_finalize */    
53     NULL,           /* class_data */
54     sizeof (GtkIMContextThai),
55     0,
56     (GInstanceInitFunc) gtk_im_context_thai_init,
57   };
58
59   gtk_type_im_context_thai = 
60     g_type_module_register_type (type_module,
61                                  GTK_TYPE_IM_CONTEXT,
62                                  "GtkIMContextThai",
63                                  &im_context_thai_info, 0);
64 }
65
66 static void
67 gtk_im_context_thai_class_init (GtkIMContextThaiClass *class)
68 {
69   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
70
71   parent_class = g_type_class_peek_parent (class);
72
73   im_context_class->filter_keypress = gtk_im_context_thai_filter_keypress;
74 }
75
76 static void
77 gtk_im_context_thai_init (GtkIMContextThai *context_thai)
78 {
79 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
80   forget_previous_chars (context_thai);
81 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
82   context_thai->isc_mode = ISC_BASICCHECK;
83 }
84
85 GtkIMContext *
86 gtk_im_context_thai_new (void)
87 {
88   GtkIMContextThai *result;
89
90   result = GTK_IM_CONTEXT_THAI (g_object_new (GTK_TYPE_IM_CONTEXT_THAI, NULL));
91
92   return GTK_IM_CONTEXT (result);
93 }
94
95 GtkIMContextThaiISCMode
96 gtk_im_context_thai_get_isc_mode (GtkIMContextThai *context_thai)
97 {
98   return context_thai->isc_mode;
99 }
100
101 GtkIMContextThaiISCMode
102 gtk_im_context_thai_set_isc_mode (GtkIMContextThai *context_thai,
103                                   GtkIMContextThaiISCMode mode)
104 {
105   GtkIMContextThaiISCMode prev_mode = context_thai->isc_mode;
106   context_thai->isc_mode = mode;
107   return prev_mode;
108 }
109
110 static gboolean
111 is_context_lost_key(guint keyval)
112 {
113   return ((keyval & 0xFF00) == 0xFF00) &&
114          (keyval == GDK_BackSpace ||
115           keyval == GDK_Tab ||
116           keyval == GDK_Linefeed ||
117           keyval == GDK_Clear ||
118           keyval == GDK_Return ||
119           keyval == GDK_Pause ||
120           keyval == GDK_Scroll_Lock ||
121           keyval == GDK_Sys_Req ||
122           keyval == GDK_Escape ||
123           keyval == GDK_Delete ||
124           (GDK_Home <= keyval && keyval <= GDK_Begin) || /* IsCursorkey */
125           (GDK_KP_Space <= keyval && keyval <= GDK_KP_Delete) || /* IsKeypadKey, non-chars only */
126           (GDK_Select <= keyval && keyval <= GDK_Break) || /* IsMiscFunctionKey */
127           (GDK_F1 <= keyval && keyval <= GDK_F35)); /* IsFunctionKey */
128 }
129
130 static gboolean
131 is_context_intact_key(guint keyval)
132 {
133   return (((keyval & 0xFF00) == 0xFF00) &&
134            ((GDK_Shift_L <= keyval && keyval <= GDK_Hyper_R) || /* IsModifierKey */
135             (keyval == GDK_Mode_switch) ||
136             (keyval == GDK_Num_Lock))) ||
137          (((keyval & 0xFE00) == 0xFE00) &&
138           (GDK_ISO_Lock <= keyval && keyval <= GDK_ISO_Last_Group_Lock));
139 }
140
141 static gboolean
142 thai_is_accept (gunichar new_char, gunichar prev_char, gint isc_mode)
143 {
144   switch (isc_mode)
145     {
146     case ISC_PASSTHROUGH:
147       return TRUE;
148
149     case ISC_BASICCHECK:
150       return TAC_compose_input (prev_char, new_char) != 'R';
151
152     case ISC_STRICT:
153       {
154         int op = TAC_compose_input (prev_char, new_char);
155         return op != 'R' && op != 'S';
156       }
157
158     default:
159       return FALSE;
160     }
161 }
162
163 #define thai_is_composible(n,p)  (TAC_compose_input ((p), (n)) == 'C')
164
165 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
166 static void
167 forget_previous_chars (GtkIMContextThai *context_thai)
168 {
169   memset (context_thai->char_buff, 0, sizeof (context_thai->char_buff));
170 }
171
172 static void
173 remember_previous_char (GtkIMContextThai *context_thai, gunichar new_char)
174 {
175   memmove (context_thai->char_buff + 1, context_thai->char_buff,
176            (GTK_IM_CONTEXT_THAI_BUFF_SIZE - 1) * sizeof (context_thai->char_buff[0]));
177   context_thai->char_buff[0] = new_char;
178 }
179 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
180
181 static gunichar
182 get_previous_char (GtkIMContextThai *context_thai, gint offset)
183 {
184   gchar *surrounding;
185   gint  cursor_index;
186
187   if (gtk_im_context_get_surrounding ((GtkIMContext *)context_thai,
188                                       &surrounding, &cursor_index))
189     {
190       gunichar prev_char;
191       gchar *p, *q;
192
193       prev_char = 0;
194       p = surrounding + cursor_index;
195       for (q = p; offset < 0 && q > surrounding; ++offset)
196         q = g_utf8_prev_char (q);
197       if (offset == 0)
198         {
199           prev_char = g_utf8_get_char_validated (q, p - q);
200           if (prev_char < 0)
201             prev_char = 0;
202         }
203       g_free (surrounding);
204       return prev_char;
205     }
206 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
207   else
208     {
209       offset = -offset - 1;
210       if (0 <= offset && offset < GTK_IM_CONTEXT_THAI_BUFF_SIZE)
211         return context_thai->char_buff[offset];
212     }
213 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
214
215     return 0;
216 }
217
218 static gboolean
219 gtk_im_context_thai_commit_chars (GtkIMContextThai *context_thai,
220                                   gunichar *s, gsize len)
221 {
222   gchar *utf8;
223
224   utf8 = g_ucs4_to_utf8 (s, len, NULL, NULL, NULL);
225   if (!utf8)
226     return FALSE;
227
228   g_signal_emit_by_name (context_thai, "commit", utf8);
229
230   g_free (utf8);
231   return TRUE;
232 }
233
234 static gboolean
235 accept_input (GtkIMContextThai *context_thai, gunichar new_char)
236 {
237 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
238   remember_previous_char (context_thai, new_char);
239 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
240
241   return gtk_im_context_thai_commit_chars (context_thai, &new_char, 1);
242 }
243
244 static gboolean
245 reorder_input (GtkIMContextThai *context_thai,
246                gunichar prev_char, gunichar new_char)
247 {
248   gunichar buf[2];
249
250   if (!gtk_im_context_delete_surrounding (GTK_IM_CONTEXT (context_thai), -1, 1))
251     return FALSE;
252
253 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
254   forget_previous_chars (context_thai);
255   remember_previous_char (context_thai, new_char);
256   remember_previous_char (context_thai, prev_char);
257 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
258
259   buf[0] = new_char;
260   buf[1] = prev_char;
261   return gtk_im_context_thai_commit_chars (context_thai, buf, 2);
262 }
263
264 static gboolean
265 replace_input (GtkIMContextThai *context_thai, gunichar new_char)
266 {
267   if (!gtk_im_context_delete_surrounding (GTK_IM_CONTEXT (context_thai), -1, 1))
268     return FALSE;
269
270 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
271   forget_previous_chars (context_thai);
272   remember_previous_char (context_thai, new_char);
273 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
274
275   return gtk_im_context_thai_commit_chars (context_thai, &new_char, 1);
276 }
277
278 static gboolean
279 gtk_im_context_thai_filter_keypress (GtkIMContext *context,
280                                      GdkEventKey  *event)
281 {
282   GtkIMContextThai *context_thai = GTK_IM_CONTEXT_THAI (context);
283   gunichar prev_char, new_char;
284   gboolean is_reject;
285   GtkIMContextThaiISCMode isc_mode;
286
287   if (event->type != GDK_KEY_PRESS)
288     return FALSE;
289
290   if (event->state & (GDK_MODIFIER_MASK
291                       & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_MOD2_MASK)) ||
292       is_context_lost_key (event->keyval))
293     {
294 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
295       forget_previous_chars (context_thai);
296 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
297       return FALSE;
298     }
299   if (event->keyval == 0 || is_context_intact_key (event->keyval))
300     {
301       return FALSE;
302     }
303
304   prev_char = get_previous_char (context_thai, -1);
305   if (!prev_char)
306     prev_char = ' ';
307   new_char = gdk_keyval_to_unicode (event->keyval);
308   is_reject = TRUE;
309   isc_mode = gtk_im_context_thai_get_isc_mode (context_thai);
310   if (thai_is_accept (new_char, prev_char, isc_mode))
311     {
312       accept_input (context_thai, new_char);
313       is_reject = FALSE;
314     }
315   else
316     {
317       gunichar context_char;
318
319       /* rejected, trying to correct */
320       context_char = get_previous_char (context_thai, -2);
321       if (context_char)
322         {
323           if (thai_is_composible (new_char, context_char))
324             {
325               if (thai_is_composible (prev_char, new_char))
326                 is_reject = !reorder_input (context_thai, prev_char, new_char);
327               else if (thai_is_composible (prev_char, context_char))
328                 is_reject = !replace_input (context_thai, new_char);
329               else if ((TAC_char_class (prev_char) == FV1
330                         || TAC_char_class (prev_char) == AM)
331                        && TAC_char_class (new_char) == TONE)
332                 is_reject = !reorder_input (context_thai, prev_char, new_char);
333             }
334           else if (thai_is_accept (new_char, context_char, isc_mode))
335             is_reject = !replace_input (context_thai, new_char);
336         }
337     }
338   if (is_reject)
339     {
340       /* reject character */
341       gdk_beep ();
342     }
343   return TRUE;
344 }
345