/*****************************************************************************/ /* Klavaro - a flexible touch typing tutor */ /* Copyright (C) 2005, 2006, 2007, 2008 Felipe Castro */ /* Copyright (C) 2009 Free Software Foundation */ /* */ /* This program is free software, licensed under the terms of the GNU */ /* General Public License as published by the Free Software Foundation, */ /* either version 3 of the License, or (at your option) any later version. */ /* You should have received a copy of the GNU General Public License */ /* along with this program. If not, see . */ /*****************************************************************************/ /* Functions to implement and manage the keyboard editing operations */ #include #include #include #include #include #include #include "auxiliar.h" #include "main.h" #include "callbacks.h" #include "translation.h" #include "keyboard.h" #define KEY_DX 35 #define KEY_DY 35 extern gchar *KEYB_CUSTOM; extern gchar *KEYB_EDIT; static struct { gchar *name; gchar *name_last; gboolean modified_status; gunichar lochars[4][KEY_LINE_LEN + 1]; gunichar upchars[4][KEY_LINE_LEN + 1]; GtkWidget *but[4][KEY_LINE_LEN - 1]; GtkWidget *entry; struct { guint i; guint j; } pos; gint cmb_n; gint intro_step; } keyb; static guint x0[4] = {0, 49, 59, 43}; static struct { KeybLayout *orig; // Original layouts already defined gint n_orig; KeybLayout *cust; // Custom layouts created by the user gint n_cust; } layouts; /* Constants */ const gunichar vowels[] = { L'a', L'e', L'i', L'o', L'u', (gunichar) 945, (gunichar) 949, (gunichar) 953, (gunichar) 959, (gunichar) 965, (gunichar) 1072, (gunichar) 1077, (gunichar) 1080, (gunichar) 1086, (gunichar) 1091, L'\0' }; // Diacritic chars must never appear one after another const gunichar diacritics[] = { // Urdu diacritics (gunichar) 0x0640, (gunichar) 0x064B, (gunichar) 0x064E, (gunichar) 0x064F, (gunichar) 0x0650, (gunichar) 0x0651, (gunichar) 0x0654, (gunichar) 0x0670, // Other arabic diacritics (gunichar) 0x0610, (gunichar) 0x0611, (gunichar) 0x0612, (gunichar) 0x0613, (gunichar) 0x0614, (gunichar) 0x0615, (gunichar) 0x0616, (gunichar) 0x0617, (gunichar) 0x0618, (gunichar) 0x0619, (gunichar) 0x061A, (gunichar) 0x064C, (gunichar) 0x064D, (gunichar) 0x0652, (gunichar) 0x0653, (gunichar) 0x0655, (gunichar) 0x0656, (gunichar) 0x0657, (gunichar) 0x0658, (gunichar) 0x0659, (gunichar) 0x065A, (gunichar) 0x065B, (gunichar) 0x065C, (gunichar) 0x065D, (gunichar) 0x065E, (gunichar) 0x06D6, (gunichar) 0x06D7, (gunichar) 0x06D8, (gunichar) 0x06D9, (gunichar) 0x06DA, (gunichar) 0x06DB, (gunichar) 0x06DC, (gunichar) 0x06DF, (gunichar) 0x06E0, (gunichar) 0x06E1, (gunichar) 0x06E2, (gunichar) 0x06E3, (gunichar) 0x06E4, (gunichar) 0x06E7, (gunichar) 0x06E8, (gunichar) 0x06EA, (gunichar) 0x06EB, (gunichar) 0x06EC, (gunichar) 0x06ED, L'\0' }; /******************************************************************************* * Interface functions */ gchar * keyb_get_name () { return (keyb.name); } gchar * keyb_get_name_last () { return (keyb.name_last); } void keyb_set_name (const gchar * name) { g_free (keyb.name_last); keyb.name_last = g_strdup (keyb.name); g_free (keyb.name); keyb.name = g_strdup (name); } void keyb_init_name (const gchar * name) { keyb.name = g_strdup (name); keyb.name_last = g_strdup (name); } gunichar keyb_get_lochars (gint i, gint j) { return (keyb.lochars[i][j]); } gunichar keyb_get_upchars (gint i, gint j) { return (keyb.upchars[i][j]); } gboolean keyb_get_modified_status () { return (keyb.modified_status); } void keyb_set_modified_status (gboolean new_status) { gtk_widget_set_sensitive (get_wg ("combobox_keyboard_country"), ! new_status); gtk_widget_set_sensitive (get_wg ("combobox_keyboard_variant"), ! new_status); gtk_widget_set_sensitive (get_wg ("button_kb_save"), new_status); keyb.modified_status = new_status; if (new_status) { callbacks_shield_set (TRUE); gtk_combo_box_set_active (GTK_COMBO_BOX (get_wg ("combobox_keyboard_country")), 0); gtk_combo_box_set_active (GTK_COMBO_BOX (get_wg ("combobox_keyboard_variant")), -1); callbacks_shield_set (FALSE); } } void keyb_create_virtual_keys () { gint i, j; GtkFixed *fix; GdkColor color; gdk_color_parse (KEYB_PURPLE, &color); fix = GTK_FIXED (get_wg ("fixed_keyboard")); for (i = 0; i < 4; i++) { for (j = 0; j < KEY_LINE_LEN - 1; j++) { keyb.but[i][j] = gtk_button_new (); gtk_fixed_put (fix, keyb.but[i][j], x0[i] + j * KEY_DX, i * KEY_DY); gtk_widget_set_size_request (keyb.but[i][j], 32, 32); g_signal_connect_after ((gpointer) keyb.but[i][j], "clicked", G_CALLBACK (on_virtual_key_clicked), NULL); g_signal_connect_after ((gpointer) keyb.but[i][j], "grab-focus", G_CALLBACK (on_virtual_key_grab_focus), NULL); //gtk_widget_modify_bg (keyb.but[i][j], GTK_STATE_NORMAL, &color); //gtk_widget_modify_bg (keyb.but[i][j], GTK_STATE_INSENSITIVE, &color); if (i > 0) { if (i == 1) { if (j > 12) continue; } else { if (j > 11) continue; } } gtk_widget_show (keyb.but[i][j]); } } gtk_widget_set_size_request (keyb.but[1][12], 53, 32); /* Key entry little evil */ keyb.entry = gtk_entry_new (); gtk_fixed_put (fix, keyb.entry, 2, 2); gtk_widget_set_size_request (keyb.entry, 28, 28); gtk_entry_set_max_length (GTK_ENTRY (keyb.entry), 1); gtk_entry_set_alignment (GTK_ENTRY (keyb.entry), 0.5); g_object_set (GTK_OBJECT (keyb.entry), "shadow-type", GTK_SHADOW_NONE, NULL); g_signal_connect_after ((gpointer) keyb.entry, "changed", G_CALLBACK (on_virtual_key_changed), NULL); gtk_widget_modify_bg (get_wg ("but_space"), GTK_STATE_NORMAL, &color); } /********************************************************************** * Read the character sets (keyb.lochars[] & keyb.upchars[]) * for the keyboard currently selected. */ void keyb_set_chars () { gint i; gchar *tmp_name = NULL; gchar tmp_str[6 * KEY_LINE_LEN + 1]; glong n_itens; gunichar *uchs; FILE *fh; /* Search at home */ tmp_name = g_strconcat (main_path_user (), G_DIR_SEPARATOR_S, keyb.name, ".kbd", NULL); fh = (FILE *) g_fopen (tmp_name, "r"); if (fh == NULL) { /* Search at data */ g_free (tmp_name); tmp_name = g_strconcat (main_path_data (), G_DIR_SEPARATOR_S, keyb.name, ".kbd", NULL); fh = (FILE *) g_fopen (tmp_name, "r"); } g_free (tmp_name); /* Success */ if (fh) { for (i = 0; i < 4; i++) { fgets (tmp_str, 6 * KEY_LINE_LEN + 1, fh); tmp_str[6 * KEY_LINE_LEN] = '\0'; uchs = g_utf8_to_ucs4_fast (tmp_str, -1, &n_itens); if (n_itens > KEY_LINE_LEN) g_error ("invalid keyboard layout: %s\n" "invalid line: %i\n" "invalid number of chars: %li", keyb.name, i + 1, n_itens); memcpy (keyb.lochars[i], uchs, (n_itens - 1) * sizeof (gunichar)); g_free (uchs); for (; n_itens < KEY_LINE_LEN; n_itens++) keyb.lochars[i][n_itens] = L' '; } for (i = 0; i < 4; i++) { fgets (tmp_str, 6 * KEY_LINE_LEN + 1, fh); tmp_str[6 * KEY_LINE_LEN] = '\0'; uchs = g_utf8_to_ucs4_fast (tmp_str, -1, &n_itens); if (n_itens > KEY_LINE_LEN) g_error ("invalid keyboard layout: %s\n" "invalid line: %i\n" "invalid number of chars: %li", keyb.name, i + 5, n_itens); memcpy (keyb.upchars[i], uchs, (n_itens - 1) * sizeof (gunichar)); g_free (uchs); for (; n_itens < KEY_LINE_LEN; n_itens++) keyb.upchars[i][n_itens] = L' '; } fclose (fh); keyb_set_modified_status (FALSE); } /* * Recursively try defaults */ else { if (g_str_equal (keyb.name, trans_get_default_keyboard ())) { main_preferences_remove ("tutor", "keyboard"); g_error ("couldn't open the default keyboard layout: [%s]", trans_get_default_keyboard ()); } g_message ("couldn't find the keyboard layout: \"%s\"\n" " Opening the default one: \"%s\"", keyb.name, trans_get_default_keyboard ()); main_preferences_set_string ("tutor", "keyboard", trans_get_default_keyboard()); keyb_set_name (trans_get_default_keyboard ()); keyb_set_chars (); return; } } /********************************************************************** * Test if chr is a vowel */ gboolean keyb_is_vowel (gunichar chr) { gint i; for (i = 0; vowels[i] != L'\0'; i++) if (g_unichar_tolower (chr) == vowels[i]) return (TRUE); return (FALSE); } /********************************************************************** * Test if chr is a diacritic character */ gboolean keyb_is_diacritic (gunichar chr) { gint i; for (i = 0; diacritics[i] != L'\0'; i++) if (chr == diacritics[i]) return (TRUE); return (FALSE); } /********************************************************************** * Get the set of available vowels of the keyboard */ gint keyb_get_vowels (gunichar * vows) { gint i; gint j; gint k = 0; for (i = 0; i < 4; i++) for (j = 0; j < KEY_LINE_LEN; j++) { if (keyb_is_vowel (keyb.lochars[i][j])) vows[k++] = keyb.lochars[i][j]; if (k == 20) break; } if (k == 0) for (i = j = 0, k = 5; i < 5 && j < 10; i++, j++) { for (; keyb_is_diacritic (keyb.lochars[2][j]) && j < 12; j++); vows[i] = keyb.lochars[2][j]; } return (k); } /********************************************************************** * Get the set of available consonants of the keyboard */ gint keyb_get_consonants (gunichar * consonants) { gint i, j; gint k = 0; gunichar chr; for (i = 0; i < 4; i++) for (j = 0; j < KEY_LINE_LEN; j++) { chr = keyb.lochars[i][j]; if (g_unichar_isalpha (chr) && (!keyb_is_vowel (chr))) consonants[k++] = chr; chr = g_unichar_tolower (keyb.upchars[i][j]); if (g_unichar_isalpha (chr) && (!keyb_is_vowel (chr)) && (chr != keyb.lochars[i][j])) consonants[k++] = chr; } return (k); } /********************************************************************** * Get the set of available symbols of the keyboard */ gint keyb_get_symbols (gunichar * symbols) { gint i, j; gint k = 0; gunichar chr; for (i = 0; i < 4; i++) for (j = 0; j < KEY_LINE_LEN; j++) { chr = keyb.lochars[i][j]; if (g_unichar_ispunct (chr)) symbols[k++] = chr; chr = keyb.upchars[i][j]; if (g_unichar_ispunct (chr)) symbols[k++] = chr; } return (k); } /********************************************************************** * Get the set of available non-arabic digits in the keyboard */ gint keyb_get_altnums (gunichar * altnums) { gint i, j; gint k = 0; gunichar chr; for (i = 0; i < 4; i++) for (j = 0; j < KEY_LINE_LEN; j++) { chr = keyb.lochars[i][j]; if (g_unichar_isdigit (chr) && chr > 255) altnums[k++] = chr; chr = keyb.upchars[i][j]; if (g_unichar_isdigit (chr) && chr > 255) altnums[k++] = chr; } return (k); } /********************************************************************** * Get the upper case of a letter, only if it's included in the keyboard (by shift) */ gunichar keyb_unichar_toupper (gunichar uchar) { gint i,j; gunichar Uchar; Uchar = g_unichar_toupper (uchar); for (i = 0; i < 4; i++) for (j = 0; j < KEY_LINE_LEN; j++) if (uchar == keyb.lochars[i][j] && Uchar == keyb.upchars[i][j]) return Uchar; return uchar; } /********************************************************************** * Save the custom keyboard layout created by the user */ void keyb_save_new_layout () { gint i; gchar *tmp_name = NULL; FILE *fh; assert_user_dir (); tmp_name = g_strconcat (main_path_user (), G_DIR_SEPARATOR_S, keyb.name, ".kbd", NULL); fh = (FILE *) g_fopen (tmp_name, "w"); g_free (tmp_name); for (i = 0; i < 4; i++) { tmp_name = g_ucs4_to_utf8 (keyb.lochars[i], KEY_LINE_LEN - 1, NULL, NULL, NULL); fprintf (fh, "%s\n", tmp_name); g_free (tmp_name); } for (i = 0; i < 4; i++) { tmp_name = g_ucs4_to_utf8 (keyb.upchars[i], KEY_LINE_LEN - 1, NULL, NULL, NULL); fprintf (fh, "%s\n", tmp_name); g_free (tmp_name); } fclose (fh); keyb_set_modified_status (FALSE); } /********************************************************************** * Remove custom keyboard layout created by the user */ void keyb_remove_user_layout () { guint active; gchar *aux; gchar *tmp_name; GtkComboBox *cmb; callbacks_shield_set (TRUE); cmb = GTK_COMBO_BOX (get_wg ("combobox_keyboard_variant")); aux = gtk_combo_box_get_active_text (cmb); active = gtk_combo_box_get_active (cmb); gtk_combo_box_remove_text (cmb, active); tmp_name = g_strconcat (main_path_user (), G_DIR_SEPARATOR_S, aux, ".kbd", NULL); g_unlink (tmp_name); g_free (tmp_name); keyb_set_keyboard_layouts (); gtk_combo_box_set_active (cmb, -1); callbacks_shield_set (FALSE); } /********************************************************************** * Update the virtual keyboard accordingly to its character set and * shift key state. */ void keyb_update_virtual_layout () { gint i, j; gchar ut8[7]; gunichar uch; gboolean tog_state; GtkWidget *wg; wg = get_wg ("toggle_shift1"); tog_state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wg)); for (i = 0; i < 4; i++) { for (j = 0; j < KEY_LINE_LEN - (i == 0 ? 1 : (i == 1 ? 2 : 3)); j++) { uch = tog_state ? keyb.upchars[i][j] : keyb.lochars[i][j]; if (g_unichar_isalpha (uch) && g_unichar_tolower (keyb.upchars[i][j]) == keyb.lochars[i][j]) uch = g_unichar_toupper (uch); ut8[g_unichar_to_utf8 (uch, ut8)] = '\0'; gtk_button_set_label (GTK_BUTTON (keyb.but[i][j]), ut8); } } } /* Get a list of .kbd file names in the subdir path, stripping their .kbd extensions */ GList * keyb_get_layout_list_from_path (gchar *path) { gsize name_len; GDir *dir = NULL; gchar *dentry = NULL; GList *files = NULL; dir = g_dir_open (path, 0, NULL); if (dir == NULL) g_error ("keyb_get_layout_list_from_path ():\n\tCould not find this directory:\n\t%s\n", path); while ( (dentry = g_strdup (g_dir_read_name (dir))) ) { name_len = strlen (dentry); if (name_len > 255 || name_len < 5) { g_free (dentry); continue; } if (! g_str_has_suffix (dentry, ".kbd")) { g_free (dentry); continue; } dentry[name_len - 4] = '\0'; if (g_str_equal (dentry, ".tmp")) { g_free (dentry); continue; } files = g_list_insert_sorted (files, dentry, compare_string_function); } g_dir_close (dir); return (files); } /* Get the country from a keyboard name */ gchar * keyb_get_country (const gchar *kbd) { gchar *country = NULL; gchar *code = NULL; code = strchr (kbd, '_'); if (code) code = strdup (code + 1); else code = strdup ("xx"); code[2] = '\0'; country = g_strdup (trans_code_to_country (code)); g_free (code); return country; } /* Get the variant from a keyboard name */ gchar * keyb_get_variant (const gchar *kbd) { gchar *begin; gchar *end; begin = g_strdup (kbd); end = strchr (begin, '_'); if (end == NULL) return begin; *end = '\0'; end++; end = strchr (end, '_'); if (end == NULL) return begin; end = g_strconcat (begin, end, NULL); g_free (begin); return end; } /* Set the array of available keyboard layouts */ #define LAYOUT_BLOCK 64 void keyb_set_keyboard_layouts () { static gboolean init = FALSE; gchar *data; gint i; GList *files; if (! init) { /* Read original layouts just once, now. */ files = keyb_get_layout_list_from_path (main_path_data ()); layouts.n_orig = g_list_length (files); layouts.orig = g_malloc (layouts.n_orig * sizeof (KeybLayout)); //g_printf ("==> Data dir: %s\n", main_path_data ()); for (i = 0; i < layouts.n_orig; i++) { data = g_list_nth_data (files, i); //g_printf ("kb(%i): %s\n", i, data); layouts.orig[i].name = data; layouts.orig[i].country = keyb_get_country (data); layouts.orig[i].variant = keyb_get_variant (data); //g_printf ("kb(%i): %s\t", i, layouts.orig[i].name); //g_printf ("%s\t", layouts.orig[i].country); //g_printf ("%s\n", layouts.orig[i].variant); } g_list_free (files); init = TRUE; layouts.n_cust = 0; layouts.cust = g_malloc (LAYOUT_BLOCK * sizeof (KeybLayout)); } /* * Reads the list of custom files */ for (i = 0; i < layouts.n_cust; i++) g_free (layouts.cust[i].name); assert_user_dir (); files = keyb_get_layout_list_from_path (main_path_user ()); layouts.n_cust = g_list_length (files); if (layouts.n_cust == 0) return; if (layouts.n_cust > LAYOUT_BLOCK) layouts.cust = g_realloc (layouts.cust, layouts.n_cust * sizeof (KeybLayout)); for (i = 0; i < layouts.n_cust; i++) { data = g_list_nth_data (files, i); layouts.cust[i].name = data; //g_printf ("kb(%i): %s\n", i, layouts.cust[i].name); } g_list_free (files); } void keyb_update_from_variant (gchar *cmb_country, gchar *cmb_variant) { gint i; gchar *country; gchar *variant; GtkComboBox *cmb; cmb = GTK_COMBO_BOX (get_wg (cmb_country)); country = gtk_combo_box_get_active_text (cmb); if (country == NULL) return; cmb = GTK_COMBO_BOX (get_wg (cmb_variant)); variant = gtk_combo_box_get_active_text (cmb); if (variant == NULL) { g_free (country); return; } callbacks_shield_set (TRUE); if (g_str_equal (country, KEYB_CUSTOM)) { /* Update the keyboard for a custom layout */ if (! g_str_equal (variant, KEYB_EDIT)) { keyb_set_name (variant); keyb_set_chars (); keyb_update_virtual_layout (); } } else { /* Update it for a original layout */ for (i = 0; i < layouts.n_orig; i++) { if (g_str_equal (layouts.orig[i].country, country)) if (g_str_equal (layouts.orig[i].variant, variant)) break; } if (i == layouts.n_orig) g_warning ("selected unavailable keyboard layout."); else { keyb_set_name (layouts.orig[i].name); keyb_set_chars (); keyb_update_virtual_layout (); } } g_free (country); g_free (variant); callbacks_shield_set (FALSE); } void keyb_set_combo_kbd_variant (gchar *cmb_country, gchar *cmb_variant) { gint i; gint n; gchar *country_txt; gboolean valid; GtkComboBox *cmb; GtkTreeModel *tmd; GtkTreeIter iter; callbacks_shield_set (TRUE); /* Clear the combo */ cmb = GTK_COMBO_BOX (get_wg (cmb_variant)); tmd = gtk_combo_box_get_model (cmb); n = 0; valid = gtk_tree_model_get_iter_first (tmd, &iter); while (valid) { n++; valid = gtk_tree_model_iter_next (tmd, &iter); } for (i = 0; i < n; i++) gtk_combo_box_remove_text (cmb, 0); /* Get the selected country text */ country_txt = gtk_combo_box_get_active_text (GTK_COMBO_BOX (get_wg (cmb_country))); if (country_txt == NULL) { g_warning ("Country combo not set, so nothing done with variant combo."); callbacks_shield_set (FALSE); return; } /* Set the original variants for the selected country */ if (! g_str_equal (country_txt, KEYB_CUSTOM)) { gchar *current; n = 0; for (i = 0; i < layouts.n_orig; i++) { if (g_str_equal (layouts.orig[i].country, country_txt)) { gtk_combo_box_append_text (cmb, layouts.orig[i].variant); n++; } } current = keyb_get_variant (keyb.name); for (i = 0; i < n; i++) { gchar *variant; gtk_combo_box_set_active (cmb, i); variant = gtk_combo_box_get_active_text (cmb); if (g_str_equal (variant, current)) { g_free (variant); break; } g_free (variant); } if (i == n) { if (n > 0) gtk_combo_box_set_active (cmb, 0); else gtk_combo_box_set_active (cmb, -1); } if (n > 1) gtk_widget_set_sensitive (get_wg (cmb_variant), TRUE); else gtk_widget_set_sensitive (get_wg (cmb_variant), FALSE); g_free (current); } /* Set custom layouts in the variant combo */ else { n = 0; if (g_str_equal (cmb_variant, "combobox_kbd_variant")) { gtk_combo_box_append_text (cmb, KEYB_EDIT); n++; } for (i = 0; i < layouts.n_cust; i++) { gtk_combo_box_append_text (cmb, layouts.cust[i].name); n++; } for (i = 0; i < n; i++) { gchar *variant; gtk_combo_box_set_active (cmb, i); variant = gtk_combo_box_get_active_text (cmb); if (g_str_equal (variant, keyb.name)) { g_free (variant); break; } g_free (variant); } if (i == n) { if (n > 1) gtk_combo_box_set_active (cmb, 1); else if (! GTK_WIDGET_VISIBLE (get_wg ("window_keyboard"))) { gtk_combo_box_set_active (cmb, 0); keyb_mode_edit (); } } if (layouts.n_cust > 0) gtk_widget_set_sensitive (get_wg (cmb_variant), TRUE); else gtk_widget_set_sensitive (get_wg (cmb_variant), FALSE); } g_free (country_txt); keyb_update_from_variant (cmb_country, cmb_variant); callbacks_shield_set (FALSE); } void keyb_set_combo_kbd (gchar *cmb_country, gchar *cmb_variant) { static gboolean init = FALSE; gchar *tmp; gint i, j; GtkComboBox *cmb; callbacks_shield_set (TRUE); if (! main_preferences_exist ("tutor", "keyboard")) main_preferences_set_string ("tutor", "keyboard", trans_get_default_keyboard ()); if (init == FALSE) { tmp = main_preferences_get_string ("tutor", "keyboard"); if (tmp == NULL) g_error ("Unexpected keyboard layout, NULL"); keyb_init_name (tmp); keyb_set_chars (); init = TRUE; g_free (tmp); } keyb_set_keyboard_layouts (); // if already initialized, this sets only the custom layouts cmb = GTK_COMBO_BOX (get_wg (cmb_country)); gtk_combo_box_remove_text (cmb, 0); keyb.cmb_n = 0; for (i = 0; i < layouts.n_orig; i++) { j = i - 1; while (j >= 0) { if (g_str_equal (layouts.orig[i].country, layouts.orig[j].country)) break; j--; } if (j < 0) { gtk_combo_box_append_text (cmb, layouts.orig[i].country); keyb.cmb_n++; } } gtk_combo_box_prepend_text (cmb, KEYB_CUSTOM); keyb.cmb_n++; keyb_update_combos (cmb_country, cmb_variant); callbacks_shield_set (FALSE); } void keyb_update_combos (gchar *cmb_country, gchar *cmb_variant) { gint i; GtkComboBox *cmb; callbacks_shield_set (TRUE); cmb = GTK_COMBO_BOX (get_wg (cmb_country)); for (i = 0; i < layouts.n_orig; i++) { if (g_str_equal (keyb.name, layouts.orig[i].name)) break; } if (i < layouts.n_orig) { gchar *country; gchar *current; /* Set original */ for (i = 1; i < keyb.cmb_n; i++) { gtk_combo_box_set_active (cmb, i); country = gtk_combo_box_get_active_text (cmb); current = keyb_get_country (keyb.name); if (g_str_equal (country, current)) { g_free (country); g_free (current); break; } g_free (country); g_free (current); } if (i == keyb.cmb_n) gtk_combo_box_set_active (cmb, 0); } else /* Set custom */ gtk_combo_box_set_active (cmb, 0); keyb_set_combo_kbd_variant (cmb_country, cmb_variant); callbacks_shield_set (FALSE); } void keyb_intro_step_next () { if (keyb.intro_step < 6) keyb_intro_step (++keyb.intro_step); } void keyb_intro_step_previous () { if (keyb.intro_step > 0) keyb_intro_step (--keyb.intro_step); } void keyb_intro_step (gint step) { gchar *intro00; GtkLabel *tit; GtkLabel *tx1; GtkLabel *tx2; GtkTextBuffer *buffer; static gchar *intro01 = NULL; static gchar *intro02 = NULL; static gchar *intro03 = NULL; static gchar *intro04 = NULL; static gchar *intro05 = NULL; static gchar *intro06 = NULL; static gchar *intro07 = NULL; static gchar *intro08 = NULL; static gchar *intro09 = NULL; static gchar *intro10 = NULL; if (intro01 == NULL) { intro01 = g_strdup (_( "Correct positioning of the hands and fingers is very important to efficient typing. " "You will learn faster and type better if you follow the next recomendations.")); intro02 = g_strdup (_( "The index-finger tips rest over each of the two keys which have a small raised mark, " "in the center of the keyboard.")); intro03 = g_strdup (_( "These marks function as 'tactile hooks' for your fingers to remain at the correct position. " "This way, with a little experience, you will not need to look at the keyboard to see if your " "fingers are properly positioned.")); intro04 = g_strdup (_( "The tips of the other fingers lie naturally beside the index ones, " "over the keys on the same row of the keyboard.")); intro05 = g_strdup (_( "The outside edges of your thumbs rest over the space bar.")); intro06 = g_strdup (_( "The part of the hands closest to the wrist (the base) rest over the table, " "outside the keyboard. Without this kind of support the arms would quickly tire.")); intro07 = g_strdup (_( "This is referred to as the home position for the hands. " "From it the fingers move all over the keyboard, " "reaching all the keys as naturally and quickly as possible. " "To reach this goal one uses a specific relation between each key and finger. " "This relation will be learned gradually as you complete the basic course.")); intro08 = g_strdup (_( "When learning the relation between fingers and keys, " "it is very important that you only move the finger which must press the key " "and allow all other fingers to remain in the home position.")); intro09 = g_strdup (_( "After memorizing this relationship, you can relax the previous rule some, " "so that you can attain greater speed while typing.")); intro10 = g_strdup (_( "You should be prepared to start training with the basic course. " "It will take effort and patience to be successful as a typist. " "We trust you have these and look forward to your success!")); } tit = GTK_LABEL (get_wg ("label_keyboard_title")); tx1 = GTK_LABEL (get_wg ("label_keyboard_text_1")); tx2 = GTK_LABEL (get_wg ("label_keyboard_text_2")); buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (get_wg ("textview_keyboard"))); intro00 = g_strdup_printf (_("Step %i"), step); switch (step) { case 0: /* Recommendations */ gtk_label_set_text (tit, _("To position the hands")); gtk_label_set_text (tx1, intro01); gtk_label_set_text (tx2, ""); gtk_text_buffer_set_text (buffer, intro01, -1); gtk_widget_grab_focus (get_wg ("button_keyboard_next")); keyb_set_sensitive (TRUE); hints_demo_fingers (0); break; case 1: /* Index fingers */ gtk_label_set_text (tit, intro00); gtk_label_set_text (tx1, intro02); gtk_label_set_text (tx2, intro03); gtk_text_buffer_set_text (buffer, intro02, -1); gtk_text_buffer_insert_at_cursor (buffer, "\n", -1); gtk_text_buffer_insert_at_cursor (buffer, intro03, -1); keyb_set_sensitive (FALSE); gtk_widget_set_sensitive (keyb.but[2][3], TRUE); gtk_widget_set_sensitive (keyb.but[2][6], TRUE); hints_demo_fingers (1000); break; case 2: /* Fingers beside index */ gtk_label_set_text (tit, intro00); gtk_label_set_text (tx1, intro04); gtk_label_set_text (tx2, ""); gtk_text_buffer_set_text (buffer, intro04, -1); keyb_set_sensitive (FALSE); gtk_widget_set_sensitive (keyb.but[2][0], TRUE); gtk_widget_set_sensitive (keyb.but[2][1], TRUE); gtk_widget_set_sensitive (keyb.but[2][2], TRUE); gtk_widget_set_sensitive (keyb.but[2][7], TRUE); gtk_widget_set_sensitive (keyb.but[2][8], TRUE); gtk_widget_set_sensitive (keyb.but[2][9], TRUE); hints_demo_fingers (1000/3); break; case 3: /* Thumbs and wrists */ gtk_label_set_text (tit, intro00); gtk_label_set_text (tx1, intro05); gtk_label_set_text (tx2, intro06); gtk_text_buffer_set_text (buffer, intro05, -1); gtk_text_buffer_insert_at_cursor (buffer, "\n", -1); gtk_text_buffer_insert_at_cursor (buffer, intro06, -1); keyb_set_sensitive (FALSE); gtk_widget_set_sensitive (get_wg ("but_space"), TRUE); hints_demo_fingers (0); gtk_widget_grab_focus (get_wg ("but_space")); hints_update_from_button (GTK_BUTTON (get_wg ("but_space"))); break; case 4: /* The home position */ gtk_label_set_text (tit, intro00); gtk_label_set_text (tx1, intro07); gtk_label_set_text (tx2, ""); gtk_text_buffer_set_text (buffer, intro07, -1); keyb_set_sensitive (FALSE); gtk_widget_set_sensitive (keyb.but[2][0], TRUE); gtk_widget_set_sensitive (keyb.but[2][1], TRUE); gtk_widget_set_sensitive (keyb.but[2][2], TRUE); gtk_widget_set_sensitive (keyb.but[2][3], TRUE); gtk_widget_set_sensitive (keyb.but[2][6], TRUE); gtk_widget_set_sensitive (keyb.but[2][7], TRUE); gtk_widget_set_sensitive (keyb.but[2][8], TRUE); gtk_widget_set_sensitive (keyb.but[2][9], TRUE); hints_demo_fingers (1000/4); break; case 5: /* Reaching keys */ gtk_label_set_text (tit, intro00); gtk_label_set_text (tx1, intro08); gtk_label_set_text (tx2, intro09); gtk_text_buffer_set_text (buffer, intro08, -1); gtk_text_buffer_insert_at_cursor (buffer, "\n", -1); gtk_text_buffer_insert_at_cursor (buffer, intro09, -1); keyb_set_sensitive (TRUE); gtk_widget_set_sensitive (keyb.but[2][0], FALSE); gtk_widget_set_sensitive (keyb.but[2][1], FALSE); gtk_widget_set_sensitive (keyb.but[2][2], FALSE); gtk_widget_set_sensitive (keyb.but[2][3], FALSE); gtk_widget_set_sensitive (keyb.but[2][6], FALSE); gtk_widget_set_sensitive (keyb.but[2][7], FALSE); gtk_widget_set_sensitive (keyb.but[2][8], FALSE); gtk_widget_set_sensitive (keyb.but[2][9], FALSE); gtk_widget_set_sensitive (get_wg ("but_space"), FALSE); gtk_widget_set_sensitive (get_wg ("toggle_shift1"), FALSE); gtk_widget_set_sensitive (get_wg ("toggle_shift2"), FALSE); hints_demo_fingers (1000/5); break; case 6: /* Final words */ gtk_label_set_text (tit, _("Go ahead!")); gtk_label_set_text (tx1, intro10); gtk_label_set_text (tx2, ""); gtk_text_buffer_set_text (buffer, intro10, -1); gtk_widget_grab_focus (get_wg ("button_keyboard_close")); keyb_set_sensitive (TRUE); hints_demo_fingers (0); break; default: gtk_label_set_text (tit, _("Relation between fingers and keys")); gtk_label_set_text (tx1, _("Click on any key to see which finger you must use:")); gtk_label_set_text (tx2, ""); keyb_set_sensitive (TRUE); hints_demo_fingers (0); } g_free (intro00); if (step == 0) gtk_widget_set_sensitive (get_wg ("button_keyboard_previous"), FALSE); else gtk_widget_set_sensitive (get_wg ("button_keyboard_previous"), TRUE); if (step == 6) gtk_widget_set_sensitive (get_wg ("button_keyboard_next"), FALSE); else gtk_widget_set_sensitive (get_wg ("button_keyboard_next"), TRUE); if (step >= 0 && step <= 6) keyb.intro_step = step; else keyb.intro_step = 0; } void keyb_mode_intro () { gchar *country; gchar *variant; gchar *tit; keyb_update_virtual_layout (); gtk_widget_hide (get_wg ("window_hints")); keyb_edit_none (); country = gtk_combo_box_get_active_text (GTK_COMBO_BOX (get_wg ("combobox_kbd_country"))); variant = gtk_combo_box_get_active_text (GTK_COMBO_BOX (get_wg ("combobox_kbd_variant"))); tit = g_strdup_printf ("%s - %s - %s [%s]", _("Introduction"), _("Keyboard"), country, variant); gtk_window_set_title (get_win ("window_keyboard"), tit); g_free (country); g_free (variant); g_free (tit); gtk_window_set_resizable (get_win ("window_keyboard"), TRUE); gtk_widget_set_size_request (get_wg ("window_keyboard"), -1, 420); gtk_widget_hide (get_wg ("label_keyboard_text_1")); gtk_widget_hide (get_wg ("button_kb_save")); gtk_widget_hide (get_wg ("button_keyboard_hands")); gtk_widget_hide (get_wg ("button_keyboard_cancel")); gtk_widget_hide (get_wg ("hbox_keyboard_selector")); gtk_widget_hide (get_wg ("hbox_keyboard_saveas")); gtk_widget_show (get_wg ("label_keyboard_spacer")); gtk_widget_show (get_wg ("scrolledwindow_keyboard")); gtk_widget_show (get_wg ("button_keyboard_close")); gtk_widget_show (get_wg ("button_keyboard_previous")); gtk_widget_show (get_wg ("button_keyboard_next")); gtk_widget_show (get_wg ("hbox_keyboard_hints")); hints_set_tips (); keyb_intro_step (0); gtk_widget_show (get_wg ("window_keyboard")); } void keyb_mode_hint () { gchar *country; gchar *variant; gchar *tit; keyb_update_virtual_layout (); gtk_widget_hide (get_wg ("window_hints")); keyb_edit_none (); country = gtk_combo_box_get_active_text (GTK_COMBO_BOX (get_wg ("combobox_kbd_country"))); variant = gtk_combo_box_get_active_text (GTK_COMBO_BOX (get_wg ("combobox_kbd_variant"))); tit = g_strdup_printf ("%s - %s [%s]", _("Keyboard"), country, variant); gtk_window_set_title (get_win ("window_keyboard"), tit); g_free (country); g_free (variant); g_free (tit); gtk_window_set_resizable (get_win ("window_keyboard"), FALSE); gtk_widget_set_size_request (get_wg ("window_keyboard"), -1, -1); gtk_widget_hide (get_wg ("label_keyboard_spacer")); gtk_widget_hide (get_wg ("scrolledwindow_keyboard")); gtk_widget_hide (get_wg ("button_kb_save")); gtk_widget_hide (get_wg ("button_keyboard_cancel")); gtk_widget_hide (get_wg ("button_keyboard_previous")); gtk_widget_hide (get_wg ("button_keyboard_next")); gtk_widget_hide (get_wg ("hbox_keyboard_selector")); gtk_widget_hide (get_wg ("hbox_keyboard_saveas")); gtk_widget_show (get_wg ("label_keyboard_text_1")); gtk_widget_show (get_wg ("button_keyboard_hands")); gtk_widget_show (get_wg ("button_keyboard_close")); gtk_widget_show (get_wg ("hbox_keyboard_hints")); hints_set_tips (); keyb_intro_step (-1); gtk_widget_grab_focus (get_wg ("button_keyboard_close")); gtk_widget_show (get_wg ("window_keyboard")); } void keyb_mode_edit () { gchar *tmp; /* Save the current name as 'name_last' */ tmp = g_strdup (keyb.name); keyb_set_name (tmp); g_free (tmp); keyb_set_modified_status (FALSE); if (layouts.n_cust == 0) gtk_widget_set_sensitive (get_wg ("button_kb_remove"), FALSE); else gtk_widget_set_sensitive (get_wg ("button_kb_remove"), TRUE); keyb_update_combos ("combobox_keyboard_country", "combobox_keyboard_variant"); keyb_update_virtual_layout (); keyb_edit_none (); gtk_window_set_title (get_win ("window_keyboard"), _("Create or modify a custom keyboard layout")); gtk_window_set_resizable (get_win ("window_keyboard"), FALSE); gtk_widget_set_size_request (get_wg ("window_keyboard"), -1, -1); gtk_widget_hide (get_wg ("button_keyboard_hands")); gtk_widget_hide (get_wg ("button_keyboard_close")); gtk_widget_hide (get_wg ("button_keyboard_previous")); gtk_widget_hide (get_wg ("button_keyboard_next")); gtk_widget_hide (get_wg ("hbox_keyboard_hints")); gtk_widget_show (get_wg ("button_keyboard_cancel")); gtk_widget_show (get_wg ("hbox_keyboard_selector")); gtk_widget_show (get_wg ("hbox_keyboard_saveas")); gtk_widget_show (get_wg ("button_kb_save")); keyb_set_sensitive (TRUE); gtk_widget_set_sensitive (get_wg ("but_space"), FALSE); hints_set_tips (); gtk_widget_grab_focus (get_wg ("button_keyboard_cancel")); gtk_widget_show (get_wg ("window_keyboard")); } void keyb_set_sensitive (gboolean state) { gint i, j; gint j_max; for (i = 0; i < 4; i++) { j_max = KEY_LINE_LEN - (i == 0 ? 1 : (i == 1 ? 2 : 3)); for (j = 0; j < j_max; j++) gtk_widget_set_sensitive (keyb.but[i][j], state); } gtk_widget_set_sensitive (get_wg ("but_space"), state); gtk_widget_set_sensitive (get_wg ("toggle_shift1"), state); gtk_widget_set_sensitive (get_wg ("toggle_shift2"), state); } gboolean keyb_button_match (GtkButton * button) { gint i, j; gint j_max; for (i = 0; i < 4; i++) { j_max = KEY_LINE_LEN - (i == 0 ? 1 : (i == 1 ? 2 : 3)); for (j = 0; j < j_max; j++) { if (keyb.but[i][j] == GTK_WIDGET (button)) { keyb.pos.i = i; keyb.pos.j = j; return TRUE; } } } return FALSE; } /********************************************************************** * Moves the entry widget to get a real key pressed and * writes the name of the virtual one. */ void keyb_edit_button (GtkButton * button) { if (! keyb_button_match (button)) return; callbacks_shield_set (TRUE); gtk_widget_grab_focus (GTK_WIDGET (button)); gtk_entry_set_text (GTK_ENTRY (keyb.entry), gtk_button_get_label (button)); gtk_fixed_move (GTK_FIXED (get_wg ("fixed_keyboard")), keyb.entry, 2 + x0[keyb.pos.i] + KEY_DX * keyb.pos.j, 2 + KEY_DY * keyb.pos.i); if (keyb.pos.i == 1 && keyb.pos.j == 12) gtk_widget_set_size_request (keyb.entry, 49, 28); else gtk_widget_set_size_request (keyb.entry, 28, 28); gtk_editable_select_region (GTK_EDITABLE (keyb.entry), 0, 1); gtk_widget_show (keyb.entry); gtk_widget_grab_focus (keyb.entry); callbacks_shield_set (FALSE); } void keyb_edit_none (void) { gtk_widget_hide (keyb.entry); } gboolean keyb_edit_next (void) { keyb.pos.j++; switch (keyb.pos.i) { case 0: if (keyb.pos.j > 13) { keyb.pos.i++; keyb.pos.j = 0; } break; case 1: if (keyb.pos.j > 12) { keyb.pos.i++; keyb.pos.j = 0; } break; default: if (keyb.pos.j > 11) { keyb.pos.i++; keyb.pos.j = 0; } } if (keyb.pos.i > 3) keyb.pos.i = 0; gtk_widget_hide (keyb.entry); if (GTK_WIDGET_SENSITIVE (keyb.but[keyb.pos.i][keyb.pos.j])) { gtk_widget_grab_focus (keyb.but[keyb.pos.i][keyb.pos.j]); return TRUE; } else return FALSE; } /********************************************************************** * Apply the key pressed to the virtual keyboard * and to the upper or lower character sets */ void keyb_change_key (gunichar real_key) { gint key_lin, key_col; gunichar str_char; gchar tmp_utf8[7]; gboolean tog_state; GtkWidget *wg; key_lin = keyb.pos.i; key_col = keyb.pos.j; str_char = g_unichar_toupper (real_key); tmp_utf8[g_unichar_to_utf8 (str_char, tmp_utf8)] = '\0'; gtk_button_set_label (GTK_BUTTON (keyb.but[key_lin][key_col]), tmp_utf8); wg = get_wg ("toggle_shift1"); tog_state = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (wg)); if (tog_state) { keyb.upchars[key_lin][key_col] = str_char; if (str_char >= L'A' && str_char <= L'Z') keyb.lochars[key_lin][key_col] = g_unichar_tolower (str_char); } else { keyb.lochars[key_lin][key_col] = g_unichar_tolower (str_char); if (str_char >= L'A' && str_char <= L'Z') keyb.upchars[key_lin][key_col] = str_char; } keyb_set_modified_status (TRUE); gtk_widget_set_sensitive (get_wg ("button_kb_save"), TRUE); gtk_widget_set_sensitive (get_wg ("combobox_keyboard_country"), FALSE); gtk_widget_set_sensitive (get_wg ("combobox_keyboard_variant"), FALSE); } /******************************************************************************* * Get an utf8 string for the par symbol */ gchar * keyb_get_utf8_paragraph_symbol () { static gchar parsym[7]; static gboolean is_initialized = FALSE; if (is_initialized == FALSE) { is_initialized = TRUE; parsym[g_unichar_to_utf8 (UPSYM, parsym)] = '\0'; } return (parsym); } /******************************************************************************* * Initialize the hints mapping array */ static gchar hints[4][KEY_LINE_LEN + 1]; static gboolean hints_is_initialized = FALSE; void hints_init () { gint i; gchar *tmp_name; FILE *fh; if (hints_is_initialized == TRUE) return; tmp_name = g_build_filename (main_path_data (), "fingers_position.txt", NULL); fh = (FILE *) g_fopen (tmp_name, "r"); if (fh) { hints_is_initialized = TRUE; for (i = 0; i < 4; i++) fgets (hints[i], KEY_LINE_LEN + 1, fh); fclose (fh); hints_set_tips (); hints_set_colors (); } else g_warning ("couldn't open the file:\n %s", tmp_name); g_free (tmp_name); } gchar * hints_string_from_charcode (gchar charcode) { gchar *fingerhint = NULL; switch (charcode) { case '1': fingerhint = g_strdup (_("small finger")); break; case '2': fingerhint = g_strdup (_("ring finger")); break; case '3': fingerhint = g_strdup (_("middle finger")); break; case '4': fingerhint = g_strdup (_("index finger")); break; case '5': fingerhint = g_strdup (_("thumbs")); break; case '6': fingerhint = g_strdup (_("index finger")); break; case '7': fingerhint = g_strdup (_("middle finger")); break; case '8': fingerhint = g_strdup (_("ring finger")); break; case '9': fingerhint = g_strdup (_("small finger")); break; default: fingerhint = g_strdup ("???"); } return (fingerhint); } gchar * hints_color_from_charcode (gchar charcode) { gchar *color = NULL; switch (charcode) { case '1': return KEYB_BLUE; case '2': return KEYB_RED; case '3': return KEYB_GREEN; case '4': return KEYB_YELLOW; case '5': return KEYB_PURPLE; case '6': return KEYB_ORANGE; case '7': return KEYB_GREEN; case '8': return KEYB_RED; case '9': return KEYB_BLUE; default: return "#AFAFAF"; } return (color); } void hints_set_tips () { static gchar *editme = NULL; gint i, j; gint j_max; gchar *tmp; if (editme == NULL) editme = g_strdup (_("Press and edit me")); if (hints_is_initialized == FALSE) { g_warning ("Not able to set keyboard tips without initializing the hints"); return; } for (i = 0; i < 4; i++) { j_max = KEY_LINE_LEN - (i == 0 ? 1 : (i == 1 ? 2 : 3)); for (j = 0; j < j_max; j++) { tmp = hints_string_from_charcode (hints[i][j]); if (! GTK_WIDGET_VISIBLE (get_wg ("hbox_keyboard_hints"))) gtk_widget_set_tooltip_text (keyb.but[i][j], editme); else gtk_widget_set_tooltip_text (keyb.but[i][j], tmp); g_free (tmp); } } } void hints_set_colors () { gint i, j; gint j_max; GdkColor color; if (hints_is_initialized == FALSE) { g_warning ("Not able to set keyboard colors without initializing the hints"); return; } for (i = 0; i < 4; i++) { j_max = KEY_LINE_LEN - (i == 0 ? 1 : (i == 1 ? 2 : 3)); for (j = 0; j < j_max; j++) { gdk_color_parse (hints_color_from_charcode (hints[i][j]), &color); gtk_widget_modify_bg (keyb.but[i][j], GTK_STATE_NORMAL, &color); } } } /* Update the image of the window_keyboard * Maps the button to the file which shows the finger associated with its key */ void hints_update_from_button (GtkButton *button) { gchar *pix_name; gchar ch; hints_init (); // if already initialized, do nothing if (keyb_button_match (button)) { pix_name = g_strdup ("hands_0.png"); ch = hints[keyb.pos.i][keyb.pos.j]; if (ch >= '1' && ch <= '9') pix_name[6] = ch; } else if ( button == GTK_BUTTON (get_wg ("but_space")) ) pix_name = g_strdup ("hands_5.png"); else if ( button == GTK_BUTTON (get_wg ("toggle_shift1")) ) pix_name = g_strdup ("hands_1.png"); else if ( button == GTK_BUTTON (get_wg ("toggle_shift2")) ) pix_name = g_strdup ("hands_9.png"); else pix_name = g_strdup ("hands_0.png"); set_pixmap ("pixmap_hints_fixed", pix_name); g_free (pix_name); } /* Update the image of the window_hints * Map the character to the file which shows the finger associated with that key */ void hints_update_from_char (gunichar character) { gchar file_name[32]; gint i, j; if (! GTK_WIDGET_VISIBLE (get_wg ("window_hints"))) return; strcpy (file_name, "hands_0.png"); if (character == UPSYM) strcpy (file_name, "hands_9.png"); else if (character == L' ') strcpy (file_name, "hands_5.png"); else if (character != 0) { hints_init (); // if already initialized, do nothing for (i = 3; i >= 0; i--) for (j = 0; j < 15; j++) if (character == keyb.lochars[i][j]) { file_name[6] = hints[i][j]; set_pixmap ("pixmap_hints", file_name); return; } for (i = 3; i >= 0; i--) for (j = 0; j < 15; j++) if (character == keyb.upchars[i][j]) { file_name[6] = hints[i][j]; set_pixmap ("pixmap_hints", file_name); return; } file_name[6] = '0'; } set_pixmap ("pixmap_hints", file_name); } gboolean hints_demo_fingers_move (gpointer data) { static int i = 0; if (data) { keyb.pos.i = 0; keyb.pos.j = 0; } for (i = 0; i < 500; i++) if (keyb_edit_next ()) break; return TRUE; } void hints_demo_fingers (guint msec) { static GSource *source = NULL; guint id; if (source != NULL) g_source_destroy (source); source = NULL; hints_demo_fingers_move (&msec); if (msec != 0) { id = g_timeout_add (msec, hints_demo_fingers_move, NULL); source = g_main_context_find_source_by_id (NULL, id); } }