345 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			345 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* GTK - The GIMP Toolkit
 | |
|  *
 | |
|  * This library is free software; you can redistribute it and/or
 | |
|  * modify it under the terms of the GNU Lesser General Public
 | |
|  * License as published by the Free Software Foundation; either
 | |
|  * version 2 of the License, or (at your option) any later version.
 | |
|  *
 | |
|  * This library is distributed in the hope that it will be useful,
 | |
|  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | |
|  * Lesser General Public License for more details.
 | |
|  *
 | |
|  * You should have received a copy of the GNU Lesser General Public
 | |
|  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 | |
|  *
 | |
|  * Author:  Theppitak Karoonboonyanan <thep@linux.thai.net>
 | |
|  *
 | |
|  */
 | |
| 
 | |
| #include <string.h>
 | |
| 
 | |
| #include <gdk/gdkkeysyms.h>
 | |
| #include "gtkimcontextthai.h"
 | |
| #include "thai-charprop.h"
 | |
| 
 | |
| static void     gtk_im_context_thai_class_init          (GtkIMContextThaiClass *class);
 | |
| static void     gtk_im_context_thai_init                (GtkIMContextThai      *im_context_thai);
 | |
| static gboolean gtk_im_context_thai_filter_keypress     (GtkIMContext          *context,
 | |
| 						         GdkEventKey           *key);
 | |
| 
 | |
| #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
 | |
| static void     forget_previous_chars (GtkIMContextThai *context_thai);
 | |
| static void     remember_previous_char (GtkIMContextThai *context_thai,
 | |
|                                         gunichar new_char);
 | |
| #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
 | |
| 
 | |
| static GObjectClass *parent_class;
 | |
| 
 | |
| GType gtk_type_im_context_thai = 0;
 | |
| 
 | |
| void
 | |
| gtk_im_context_thai_register_type (GTypeModule *type_module)
 | |
| {
 | |
|   const GTypeInfo im_context_thai_info =
 | |
|   {
 | |
|     sizeof (GtkIMContextThaiClass),
 | |
|     (GBaseInitFunc) NULL,
 | |
|     (GBaseFinalizeFunc) NULL,
 | |
|     (GClassInitFunc) gtk_im_context_thai_class_init,
 | |
|     NULL,           /* class_finalize */    
 | |
|     NULL,           /* class_data */
 | |
|     sizeof (GtkIMContextThai),
 | |
|     0,
 | |
|     (GInstanceInitFunc) gtk_im_context_thai_init,
 | |
|   };
 | |
| 
 | |
|   gtk_type_im_context_thai = 
 | |
|     g_type_module_register_type (type_module,
 | |
|                                  GTK_TYPE_IM_CONTEXT,
 | |
|                                  "GtkIMContextThai",
 | |
|                                  &im_context_thai_info, 0);
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_im_context_thai_class_init (GtkIMContextThaiClass *class)
 | |
| {
 | |
|   GtkIMContextClass *im_context_class = GTK_IM_CONTEXT_CLASS (class);
 | |
| 
 | |
|   parent_class = g_type_class_peek_parent (class);
 | |
| 
 | |
|   im_context_class->filter_keypress = gtk_im_context_thai_filter_keypress;
 | |
| }
 | |
| 
 | |
| static void
 | |
| gtk_im_context_thai_init (GtkIMContextThai *context_thai)
 | |
| {
 | |
| #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
 | |
|   forget_previous_chars (context_thai);
 | |
| #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
 | |
|   context_thai->isc_mode = ISC_BASICCHECK;
 | |
| }
 | |
| 
 | |
| GtkIMContext *
 | |
| gtk_im_context_thai_new (void)
 | |
| {
 | |
|   GtkIMContextThai *result;
 | |
| 
 | |
|   result = GTK_IM_CONTEXT_THAI (g_object_new (GTK_TYPE_IM_CONTEXT_THAI, NULL));
 | |
| 
 | |
|   return GTK_IM_CONTEXT (result);
 | |
| }
 | |
| 
 | |
| GtkIMContextThaiISCMode
 | |
| gtk_im_context_thai_get_isc_mode (GtkIMContextThai *context_thai)
 | |
| {
 | |
|   return context_thai->isc_mode;
 | |
| }
 | |
| 
 | |
| GtkIMContextThaiISCMode
 | |
| gtk_im_context_thai_set_isc_mode (GtkIMContextThai *context_thai,
 | |
|                                   GtkIMContextThaiISCMode mode)
 | |
| {
 | |
|   GtkIMContextThaiISCMode prev_mode = context_thai->isc_mode;
 | |
|   context_thai->isc_mode = mode;
 | |
|   return prev_mode;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| is_context_lost_key(guint keyval)
 | |
| {
 | |
|   return ((keyval & 0xFF00) == 0xFF00) &&
 | |
|          (keyval == GDK_KEY_BackSpace ||
 | |
|           keyval == GDK_KEY_Tab ||
 | |
|           keyval == GDK_KEY_Linefeed ||
 | |
|           keyval == GDK_KEY_Clear ||
 | |
|           keyval == GDK_KEY_Return ||
 | |
|           keyval == GDK_KEY_Pause ||
 | |
|           keyval == GDK_KEY_Scroll_Lock ||
 | |
|           keyval == GDK_KEY_Sys_Req ||
 | |
|           keyval == GDK_KEY_Escape ||
 | |
|           keyval == GDK_KEY_Delete ||
 | |
|           (GDK_KEY_Home <= keyval && keyval <= GDK_KEY_Begin) || /* IsCursorkey */
 | |
|           (GDK_KEY_KP_Space <= keyval && keyval <= GDK_KEY_KP_Delete) || /* IsKeypadKey, non-chars only */
 | |
|           (GDK_KEY_Select <= keyval && keyval <= GDK_KEY_Break) || /* IsMiscFunctionKey */
 | |
|           (GDK_KEY_F1 <= keyval && keyval <= GDK_KEY_F35)); /* IsFunctionKey */
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| is_context_intact_key(guint keyval)
 | |
| {
 | |
|   return (((keyval & 0xFF00) == 0xFF00) &&
 | |
|            ((GDK_KEY_Shift_L <= keyval && keyval <= GDK_KEY_Hyper_R) || /* IsModifierKey */
 | |
|             (keyval == GDK_KEY_Mode_switch) ||
 | |
|             (keyval == GDK_KEY_Num_Lock))) ||
 | |
|          (((keyval & 0xFE00) == 0xFE00) &&
 | |
|           (GDK_KEY_ISO_Lock <= keyval && keyval <= GDK_KEY_ISO_Last_Group_Lock));
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| thai_is_accept (gunichar new_char, gunichar prev_char, gint isc_mode)
 | |
| {
 | |
|   switch (isc_mode)
 | |
|     {
 | |
|     case ISC_PASSTHROUGH:
 | |
|       return TRUE;
 | |
| 
 | |
|     case ISC_BASICCHECK:
 | |
|       return TAC_compose_input (prev_char, new_char) != 'R';
 | |
| 
 | |
|     case ISC_STRICT:
 | |
|       {
 | |
|         int op = TAC_compose_input (prev_char, new_char);
 | |
|         return op != 'R' && op != 'S';
 | |
|       }
 | |
| 
 | |
|     default:
 | |
|       return FALSE;
 | |
|     }
 | |
| }
 | |
| 
 | |
| #define thai_is_composible(n,p)  (TAC_compose_input ((p), (n)) == 'C')
 | |
| 
 | |
| #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
 | |
| static void
 | |
| forget_previous_chars (GtkIMContextThai *context_thai)
 | |
| {
 | |
|   memset (context_thai->char_buff, 0, sizeof (context_thai->char_buff));
 | |
| }
 | |
| 
 | |
| static void
 | |
| remember_previous_char (GtkIMContextThai *context_thai, gunichar new_char)
 | |
| {
 | |
|   memmove (context_thai->char_buff + 1, context_thai->char_buff,
 | |
|            (GTK_IM_CONTEXT_THAI_BUFF_SIZE - 1) * sizeof (context_thai->char_buff[0]));
 | |
|   context_thai->char_buff[0] = new_char;
 | |
| }
 | |
| #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
 | |
| 
 | |
| static gunichar
 | |
| get_previous_char (GtkIMContextThai *context_thai, gint offset)
 | |
| {
 | |
|   gchar *surrounding;
 | |
|   gint  cursor_index;
 | |
| 
 | |
|   if (gtk_im_context_get_surrounding ((GtkIMContext *)context_thai,
 | |
|                                       &surrounding, &cursor_index))
 | |
|     {
 | |
|       gunichar prev_char;
 | |
|       gchar *p, *q;
 | |
| 
 | |
|       prev_char = 0;
 | |
|       p = surrounding + cursor_index;
 | |
|       for (q = p; offset < 0 && q > surrounding; ++offset)
 | |
|         q = g_utf8_prev_char (q);
 | |
|       if (offset == 0)
 | |
|         {
 | |
|           prev_char = g_utf8_get_char_validated (q, p - q);
 | |
|           if (prev_char == (gunichar)-1 || prev_char == (gunichar)-2)
 | |
|             prev_char = 0;
 | |
|         }
 | |
|       g_free (surrounding);
 | |
|       return prev_char;
 | |
|     }
 | |
| #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
 | |
|   else
 | |
|     {
 | |
|       offset = -offset - 1;
 | |
|       if (0 <= offset && offset < GTK_IM_CONTEXT_THAI_BUFF_SIZE)
 | |
|         return context_thai->char_buff[offset];
 | |
|     }
 | |
| #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
 | |
| 
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_im_context_thai_commit_chars (GtkIMContextThai *context_thai,
 | |
|                                   gunichar *s, gsize len)
 | |
| {
 | |
|   gchar *utf8;
 | |
| 
 | |
|   utf8 = g_ucs4_to_utf8 (s, len, NULL, NULL, NULL);
 | |
|   if (!utf8)
 | |
|     return FALSE;
 | |
| 
 | |
|   g_signal_emit_by_name (context_thai, "commit", utf8);
 | |
| 
 | |
|   g_free (utf8);
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| accept_input (GtkIMContextThai *context_thai, gunichar new_char)
 | |
| {
 | |
| #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
 | |
|   remember_previous_char (context_thai, new_char);
 | |
| #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
 | |
| 
 | |
|   return gtk_im_context_thai_commit_chars (context_thai, &new_char, 1);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| reorder_input (GtkIMContextThai *context_thai,
 | |
|                gunichar prev_char, gunichar new_char)
 | |
| {
 | |
|   gunichar buf[2];
 | |
| 
 | |
|   if (!gtk_im_context_delete_surrounding (GTK_IM_CONTEXT (context_thai), -1, 1))
 | |
|     return FALSE;
 | |
| 
 | |
| #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
 | |
|   forget_previous_chars (context_thai);
 | |
|   remember_previous_char (context_thai, new_char);
 | |
|   remember_previous_char (context_thai, prev_char);
 | |
| #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
 | |
| 
 | |
|   buf[0] = new_char;
 | |
|   buf[1] = prev_char;
 | |
|   return gtk_im_context_thai_commit_chars (context_thai, buf, 2);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| replace_input (GtkIMContextThai *context_thai, gunichar new_char)
 | |
| {
 | |
|   if (!gtk_im_context_delete_surrounding (GTK_IM_CONTEXT (context_thai), -1, 1))
 | |
|     return FALSE;
 | |
| 
 | |
| #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
 | |
|   forget_previous_chars (context_thai);
 | |
|   remember_previous_char (context_thai, new_char);
 | |
| #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
 | |
| 
 | |
|   return gtk_im_context_thai_commit_chars (context_thai, &new_char, 1);
 | |
| }
 | |
| 
 | |
| static gboolean
 | |
| gtk_im_context_thai_filter_keypress (GtkIMContext *context,
 | |
|                                      GdkEventKey  *event)
 | |
| {
 | |
|   GtkIMContextThai *context_thai = GTK_IM_CONTEXT_THAI (context);
 | |
|   gunichar prev_char, new_char;
 | |
|   gboolean is_reject;
 | |
|   GtkIMContextThaiISCMode isc_mode;
 | |
| 
 | |
|   if (event->type != GDK_KEY_PRESS)
 | |
|     return FALSE;
 | |
| 
 | |
|   if (event->state & (GDK_MODIFIER_MASK
 | |
|                       & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_MOD2_MASK)) ||
 | |
|       is_context_lost_key (event->keyval))
 | |
|     {
 | |
| #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK
 | |
|       forget_previous_chars (context_thai);
 | |
| #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */
 | |
|       return FALSE;
 | |
|     }
 | |
|   if (event->keyval == 0 || is_context_intact_key (event->keyval))
 | |
|     {
 | |
|       return FALSE;
 | |
|     }
 | |
| 
 | |
|   prev_char = get_previous_char (context_thai, -1);
 | |
|   if (!prev_char)
 | |
|     prev_char = ' ';
 | |
|   new_char = gdk_keyval_to_unicode (event->keyval);
 | |
|   is_reject = TRUE;
 | |
|   isc_mode = gtk_im_context_thai_get_isc_mode (context_thai);
 | |
|   if (thai_is_accept (new_char, prev_char, isc_mode))
 | |
|     {
 | |
|       accept_input (context_thai, new_char);
 | |
|       is_reject = FALSE;
 | |
|     }
 | |
|   else
 | |
|     {
 | |
|       gunichar context_char;
 | |
| 
 | |
|       /* rejected, trying to correct */
 | |
|       context_char = get_previous_char (context_thai, -2);
 | |
|       if (context_char)
 | |
|         {
 | |
|           if (thai_is_composible (new_char, context_char))
 | |
|             {
 | |
|               if (thai_is_composible (prev_char, new_char))
 | |
|                 is_reject = !reorder_input (context_thai, prev_char, new_char);
 | |
|               else if (thai_is_composible (prev_char, context_char))
 | |
|                 is_reject = !replace_input (context_thai, new_char);
 | |
|               else if ((TAC_char_class (prev_char) == FV1
 | |
|                         || TAC_char_class (prev_char) == AM)
 | |
|                        && TAC_char_class (new_char) == TONE)
 | |
|                 is_reject = !reorder_input (context_thai, prev_char, new_char);
 | |
|             }
 | |
| 	  else if (thai_is_accept (new_char, context_char, isc_mode))
 | |
|             is_reject = !replace_input (context_thai, new_char);
 | |
|         }
 | |
|     }
 | |
|   if (is_reject)
 | |
|     {
 | |
|       /* reject character */
 | |
|       GdkDisplay *display = gdk_display_get_default ();
 | |
|       gdk_display_beep (display);
 | |
|     }
 | |
|   return TRUE;
 | |
| }
 | |
| 
 | 
