/* 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 . * * Author: Theppitak Karoonboonyanan * */ #include #include #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; GdkModifierType state; guint keyval; if (gdk_event_get_event_type ((GdkEvent *) event) != GDK_KEY_PRESS || !gdk_event_get_state ((GdkEvent *) event, &state) || !gdk_event_get_keyval ((GdkEvent *) event, &keyval)) return FALSE; if (state & (GDK_MODIFIER_MASK & ~(GDK_SHIFT_MASK | GDK_LOCK_MASK | GDK_MOD2_MASK)) || is_context_lost_key (keyval)) { #ifndef GTK_IM_CONTEXT_THAI_NO_FALLBACK forget_previous_chars (context_thai); #endif /* !GTK_IM_CONTEXT_THAI_NO_FALLBACK */ return FALSE; } if (keyval == 0 || is_context_intact_key (keyval)) { return FALSE; } prev_char = get_previous_char (context_thai, -1); if (!prev_char) prev_char = ' '; new_char = gdk_keyval_to_unicode (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 */ gdk_beep (); } return TRUE; }