]> Pileus Git - ~andy/gtk/blob - modules/input/gtkimcontextthai.c
printing: Don't hang when getting info for Avahi printer
[~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, see <http://www.gnu.org/licenses/>.
15  *
16  * Author:  Theppitak Karoonboonyanan <thep@linux.thai.net>
17  *
18  */
19
20 #include <string.h>
21
22 #include <gdk/gdkkeysyms.h>
23 #include "gtkimcontextthai.h"
24 #include "thai-charprop.h"
25
26 static void     gtk_im_context_thai_class_init          (GtkIMContextThaiClass *class);
27 static void     gtk_im_context_thai_init                (GtkIMContextThai      *im_context_thai);
28 static gboolean gtk_im_context_thai_filter_keypress     (GtkIMContext          *context,
29                                                          GdkEventKey           *key);
30
31 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
32 static void     forget_previous_chars (GtkIMContextThai *context_thai);
33 static void     remember_previous_char (GtkIMContextThai *context_thai,
34                                         gunichar new_char);
35 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
36
37 static GObjectClass *parent_class;
38
39 GType gtk_type_im_context_thai = 0;
40
41 void
42 gtk_im_context_thai_register_type (GTypeModule *type_module)
43 {
44   const GTypeInfo im_context_thai_info =
45   {
46     sizeof (GtkIMContextThaiClass),
47     (GBaseInitFunc) NULL,
48     (GBaseFinalizeFunc) NULL,
49     (GClassInitFunc) gtk_im_context_thai_class_init,
50     NULL,           /* class_finalize */    
51     NULL,           /* class_data */
52     sizeof (GtkIMContextThai),
53     0,
54     (GInstanceInitFunc) gtk_im_context_thai_init,
55   };
56
57   gtk_type_im_context_thai = 
58     g_type_module_register_type (type_module,
59                                  GTK_TYPE_IM_CONTEXT,
60                                  "GtkIMContextThai",
61                                  &im_context_thai_info, 0);
62 }
63
64 static void
65 gtk_im_context_thai_class_init (GtkIMContextThaiClass *class)
66 {
67   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
68
69   parent_class = g_type_class_peek_parent (class);
70
71   im_context_class->filter_keypress = gtk_im_context_thai_filter_keypress;
72 }
73
74 static void
75 gtk_im_context_thai_init (GtkIMContextThai *context_thai)
76 {
77 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
78   forget_previous_chars (context_thai);
79 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
80   context_thai->isc_mode = ISC_BASICCHECK;
81 }
82
83 GtkIMContext *
84 gtk_im_context_thai_new (void)
85 {
86   GtkIMContextThai *result;
87
88   result = GTK_IM_CONTEXT_THAI (g_object_new (GTK_TYPE_IM_CONTEXT_THAI, NULL));
89
90   return GTK_IM_CONTEXT (result);
91 }
92
93 GtkIMContextThaiISCMode
94 gtk_im_context_thai_get_isc_mode (GtkIMContextThai *context_thai)
95 {
96   return context_thai->isc_mode;
97 }
98
99 GtkIMContextThaiISCMode
100 gtk_im_context_thai_set_isc_mode (GtkIMContextThai *context_thai,
101                                   GtkIMContextThaiISCMode mode)
102 {
103   GtkIMContextThaiISCMode prev_mode = context_thai->isc_mode;
104   context_thai->isc_mode = mode;
105   return prev_mode;
106 }
107
108 static gboolean
109 is_context_lost_key(guint keyval)
110 {
111   return ((keyval & 0xFF00) == 0xFF00) &&
112          (keyval == GDK_KEY_BackSpace ||
113           keyval == GDK_KEY_Tab ||
114           keyval == GDK_KEY_Linefeed ||
115           keyval == GDK_KEY_Clear ||
116           keyval == GDK_KEY_Return ||
117           keyval == GDK_KEY_Pause ||
118           keyval == GDK_KEY_Scroll_Lock ||
119           keyval == GDK_KEY_Sys_Req ||
120           keyval == GDK_KEY_Escape ||
121           keyval == GDK_KEY_Delete ||
122           (GDK_KEY_Home <= keyval && keyval <= GDK_KEY_Begin) || /* IsCursorkey */
123           (GDK_KEY_KP_Space <= keyval && keyval <= GDK_KEY_KP_Delete) || /* IsKeypadKey, non-chars only */
124           (GDK_KEY_Select <= keyval && keyval <= GDK_KEY_Break) || /* IsMiscFunctionKey */
125           (GDK_KEY_F1 <= keyval && keyval <= GDK_KEY_F35)); /* IsFunctionKey */
126 }
127
128 static gboolean
129 is_context_intact_key(guint keyval)
130 {
131   return (((keyval & 0xFF00) == 0xFF00) &&
132            ((GDK_KEY_Shift_L <= keyval && keyval <= GDK_KEY_Hyper_R) || /* IsModifierKey */
133             (keyval == GDK_KEY_Mode_switch) ||
134             (keyval == GDK_KEY_Num_Lock))) ||
135          (((keyval & 0xFE00) == 0xFE00) &&
136           (GDK_KEY_ISO_Lock <= keyval && keyval <= GDK_KEY_ISO_Last_Group_Lock));
137 }
138
139 static gboolean
140 thai_is_accept (gunichar new_char, gunichar prev_char, gint isc_mode)
141 {
142   switch (isc_mode)
143     {
144     case ISC_PASSTHROUGH:
145       return TRUE;
146
147     case ISC_BASICCHECK:
148       return TAC_compose_input (prev_char, new_char) != 'R';
149
150     case ISC_STRICT:
151       {
152         int op = TAC_compose_input (prev_char, new_char);
153         return op != 'R' && op != 'S';
154       }
155
156     default:
157       return FALSE;
158     }
159 }
160
161 #define thai_is_composible(n,p)  (TAC_compose_input ((p), (n)) == 'C')
162
163 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
164 static void
165 forget_previous_chars (GtkIMContextThai *context_thai)
166 {
167   memset (context_thai->char_buff, 0, sizeof (context_thai->char_buff));
168 }
169
170 static void
171 remember_previous_char (GtkIMContextThai *context_thai, gunichar new_char)
172 {
173   memmove (context_thai->char_buff + 1, context_thai->char_buff,
174            (GTK_IM_CONTEXT_THAI_BUFF_SIZE - 1) * sizeof (context_thai->char_buff[0]));
175   context_thai->char_buff[0] = new_char;
176 }
177 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
178
179 static gunichar
180 get_previous_char (GtkIMContextThai *context_thai, gint offset)
181 {
182   gchar *surrounding;
183   gint  cursor_index;
184
185   if (gtk_im_context_get_surrounding ((GtkIMContext *)context_thai,
186                                       &surrounding, &cursor_index))
187     {
188       gunichar prev_char;
189       gchar *p, *q;
190
191       prev_char = 0;
192       p = surrounding + cursor_index;
193       for (q = p; offset < 0 && q > surrounding; ++offset)
194         q = g_utf8_prev_char (q);
195       if (offset == 0)
196         {
197           prev_char = g_utf8_get_char_validated (q, p - q);
198           if (prev_char < 0)
199             prev_char = 0;
200         }
201       g_free (surrounding);
202       return prev_char;
203     }
204 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
205   else
206     {
207       offset = -offset - 1;
208       if (0 <= offset && offset < GTK_IM_CONTEXT_THAI_BUFF_SIZE)
209         return context_thai->char_buff[offset];
210     }
211 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
212
213     return 0;
214 }
215
216 static gboolean
217 gtk_im_context_thai_commit_chars (GtkIMContextThai *context_thai,
218                                   gunichar *s, gsize len)
219 {
220   gchar *utf8;
221
222   utf8 = g_ucs4_to_utf8 (s, len, NULL, NULL, NULL);
223   if (!utf8)
224     return FALSE;
225
226   g_signal_emit_by_name (context_thai, "commit", utf8);
227
228   g_free (utf8);
229   return TRUE;
230 }
231
232 static gboolean
233 accept_input (GtkIMContextThai *context_thai, gunichar new_char)
234 {
235 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
236   remember_previous_char (context_thai, new_char);
237 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
238
239   return gtk_im_context_thai_commit_chars (context_thai, &new_char, 1);
240 }
241
242 static gboolean
243 reorder_input (GtkIMContextThai *context_thai,
244                gunichar prev_char, gunichar new_char)
245 {
246   gunichar buf[2];
247
248   if (!gtk_im_context_delete_surrounding (GTK_IM_CONTEXT (context_thai), -1, 1))
249     return FALSE;
250
251 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
252   forget_previous_chars (context_thai);
253   remember_previous_char (context_thai, new_char);
254   remember_previous_char (context_thai, prev_char);
255 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
256
257   buf[0] = new_char;
258   buf[1] = prev_char;
259   return gtk_im_context_thai_commit_chars (context_thai, buf, 2);
260 }
261
262 static gboolean
263 replace_input (GtkIMContextThai *context_thai, gunichar new_char)
264 {
265   if (!gtk_im_context_delete_surrounding (GTK_IM_CONTEXT (context_thai), -1, 1))
266     return FALSE;
267
268 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
269   forget_previous_chars (context_thai);
270   remember_previous_char (context_thai, new_char);
271 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
272
273   return gtk_im_context_thai_commit_chars (context_thai, &new_char, 1);
274 }
275
276 static gboolean
277 gtk_im_context_thai_filter_keypress (GtkIMContext *context,
278                                      GdkEventKey  *event)
279 {
280   GtkIMContextThai *context_thai = GTK_IM_CONTEXT_THAI (context);
281   gunichar prev_char, new_char;
282   gboolean is_reject;
283   GtkIMContextThaiISCMode isc_mode;
284
285   if (event->type != GDK_KEY_PRESS)
286     return FALSE;
287
288   if (event->state & (GDK_MODIFIER_MASK
289                       & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_MOD2_MASK)) ||
290       is_context_lost_key (event->keyval))
291     {
292 #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
293       forget_previous_chars (context_thai);
294 #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
295       return FALSE;
296     }
297   if (event->keyval == 0 || is_context_intact_key (event->keyval))
298     {
299       return FALSE;
300     }
301
302   prev_char = get_previous_char (context_thai, -1);
303   if (!prev_char)
304     prev_char = ' ';
305   new_char = gdk_keyval_to_unicode (event->keyval);
306   is_reject = TRUE;
307   isc_mode = gtk_im_context_thai_get_isc_mode (context_thai);
308   if (thai_is_accept (new_char, prev_char, isc_mode))
309     {
310       accept_input (context_thai, new_char);
311       is_reject = FALSE;
312     }
313   else
314     {
315       gunichar context_char;
316
317       /* rejected, trying to correct */
318       context_char = get_previous_char (context_thai, -2);
319       if (context_char)
320         {
321           if (thai_is_composible (new_char, context_char))
322             {
323               if (thai_is_composible (prev_char, new_char))
324                 is_reject = !reorder_input (context_thai, prev_char, new_char);
325               else if (thai_is_composible (prev_char, context_char))
326                 is_reject = !replace_input (context_thai, new_char);
327               else if ((TAC_char_class (prev_char) == FV1
328                         || TAC_char_class (prev_char) == AM)
329                        && TAC_char_class (new_char) == TONE)
330                 is_reject = !reorder_input (context_thai, prev_char, new_char);
331             }
332           else if (thai_is_accept (new_char, context_char, isc_mode))
333             is_reject = !replace_input (context_thai, new_char);
334         }
335     }
336   if (is_reject)
337     {
338       /* reject character */
339       gdk_beep ();
340     }
341   return TRUE;
342 }
343