4 cellwriter -- a character recognition input method
5 Copyright (C) 2007 Michael Levin <risujin@risujin.org>
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
25 #include "recognize.h"
29 #include <libgnome/libgnome.h>
33 int ignore_stroke_dir, ignore_stroke_num;
36 extern int cell_width, cell_height, cell_cols_pref, cell_rows_pref,
37 train_on_input, right_to_left, keyboard_enabled, xinput_enabled;
38 extern GdkColor custom_active_color, custom_inactive_color,
39 custom_ink_color, custom_select_color;
41 void cell_widget_render(void);
42 void cell_widget_set_cursor(int recreate);
43 void cell_widget_enable_xinput(int on);
46 GdkColor custom_key_color;
49 void key_widget_update_colors(void);
51 int status_menu_left_click;
57 static void color_sync(GdkColor *color)
59 profile_sync_short((short*)&color->red);
60 profile_sync_short((short*)&color->green);
61 profile_sync_short((short*)&color->blue);
64 void options_sync(void)
65 /* Read or write options. Order here is important for compatibility. */
67 profile_write("options");
68 profile_sync_int(&cell_width);
69 profile_sync_int(&cell_height);
70 profile_sync_int(&cell_cols_pref);
71 profile_sync_int(&cell_rows_pref);
72 color_sync(&custom_active_color);
73 color_sync(&custom_inactive_color);
74 color_sync(&custom_select_color);
75 color_sync(&custom_ink_color);
76 profile_sync_int(&train_on_input);
77 profile_sync_int(&ignore_stroke_dir);
78 profile_sync_int(&ignore_stroke_num);
79 profile_sync_int(&wordfreq_enable);
80 profile_sync_int(&right_to_left);
81 color_sync(&custom_key_color);
82 profile_sync_int(&keyboard_enabled);
83 profile_sync_int(&xinput_enabled);
84 profile_sync_int(&style_colors);
85 profile_sync_int(&status_menu_left_click);
93 static void unicode_block_toggled(GtkCellRendererToggle *renderer, gchar *path,
94 GtkListStore *blocks_store)
97 GtkTreePath *tree_path;
103 /* Get the block this checkbox references */
104 tree_path = gtk_tree_path_new_from_string(path);
105 gtk_tree_model_get_iter(GTK_TREE_MODEL(blocks_store), &iter, tree_path);
106 index = gtk_tree_path_get_indices(tree_path)[0];
107 gtk_tree_path_free(tree_path);
108 block = unicode_blocks + index;
110 /* Toggle its value */
111 memset(&value, 0, sizeof (value));
112 gtk_tree_model_get_value(GTK_TREE_MODEL(blocks_store), &iter, 0,
114 enabled = !g_value_get_boolean(&value);
115 gtk_list_store_set(blocks_store, &iter, 0, enabled, -1);
116 unicode_block_toggle(index, enabled);
119 static GtkWidget *create_blocks_list(void)
121 GtkWidget *view, *scrolled;
123 GtkTreeViewColumn *column;
124 GtkListStore *blocks_store;
125 GtkCellRenderer *renderer;
129 blocks_store = gtk_list_store_new(2, G_TYPE_BOOLEAN, G_TYPE_STRING);
130 view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(blocks_store));
131 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(view), FALSE);
132 gtk_tooltips_set_tip(tooltips, view,
133 "Controls which blocks are enabled for "
134 "recognition and appear in the training mode "
138 column = gtk_tree_view_column_new();
139 gtk_tree_view_insert_column(GTK_TREE_VIEW(view), column, 0);
140 renderer = gtk_cell_renderer_toggle_new();
141 g_signal_connect(G_OBJECT(renderer), "toggled",
142 G_CALLBACK(unicode_block_toggled), blocks_store);
143 gtk_tree_view_column_pack_start(column, renderer, FALSE);
144 gtk_tree_view_column_add_attribute(column, renderer, "active", 0);
145 renderer = gtk_cell_renderer_text_new();
146 gtk_tree_view_column_pack_start(column, renderer, TRUE);
147 gtk_tree_view_column_add_attribute(column, renderer, "text", 1);
149 /* Fill blocks list */
150 block = unicode_blocks;
151 while (block->name) {
152 gtk_list_store_append(blocks_store, &iter);
153 gtk_list_store_set(blocks_store, &iter, 0, block->enabled,
158 /* Scrolled window */
159 scrolled = gtk_scrolled_window_new(NULL, NULL);
160 gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scrolled),
161 GTK_SHADOW_ETCHED_IN);
162 gtk_container_add(GTK_CONTAINER(scrolled), view);
171 #define CELL_WIDTH_MIN 24
172 #define CELL_HEIGHT_MIN 48
173 #define CELL_HEIGHT_MAX 96
175 static GtkWidget *options_dialog = NULL, *cell_width_spin, *cell_height_spin,
178 static void close_dialog(void)
181 gtk_widget_hide(options_dialog);
184 static void color_set(GtkColorButton *button, GdkColor *color)
186 gtk_color_button_get_color(button, color);
187 window_update_colors();
190 static void ink_color_set(void)
192 cell_widget_set_cursor(TRUE);
195 static void xinput_enabled_toggled(void)
197 cell_widget_enable_xinput(xinput_enabled);
200 static void spin_value_changed_int(GtkSpinButton *button, int *value)
202 *value = (int)gtk_spin_button_get_value(button);
205 static void spin_value_changed_int_repack(GtkSpinButton *button, int *value)
207 spin_value_changed_int(button, value);
211 static void check_button_toggled(GtkToggleButton *button, int *value)
213 *value = gtk_toggle_button_get_active(button);
216 static void check_button_toggled_repack(GtkToggleButton *button, int *value)
218 check_button_toggled(button, value);
222 static GtkWidget *label_new_markup(const char *s)
226 w = gtk_label_new(NULL);
227 gtk_label_set_markup(GTK_LABEL(w), s);
228 gtk_misc_set_alignment(GTK_MISC(w), 0, 0.5);
232 static GtkWidget *spacer_new(int width, int height)
236 w = gtk_hbox_new(FALSE, 0);
237 gtk_widget_set_size_request(w, width, height);
241 static GtkWidget *spin_button_new_int(int min, int max, int *variable,
246 w = gtk_spin_button_new_with_range(min, max, 1.);
247 gtk_spin_button_set_value(GTK_SPIN_BUTTON(w), *variable);
248 g_signal_connect(G_OBJECT(w), "value-changed",
249 repack ? G_CALLBACK(spin_value_changed_int_repack) :
250 G_CALLBACK(spin_value_changed_int), variable);
254 static GtkWidget *check_button_new(const char *label, int *variable, int repack)
258 w = gtk_check_button_new_with_label(label);
259 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(w), *variable);
260 g_signal_connect(G_OBJECT(w), "toggled",
261 repack ? G_CALLBACK(check_button_toggled_repack) :
262 G_CALLBACK(check_button_toggled), variable);
266 static void cell_height_value_changed(void)
268 gtk_spin_button_set_range(GTK_SPIN_BUTTON(cell_width_spin),
269 CELL_WIDTH_MIN, cell_height);
272 static void cell_width_value_changed(void)
276 min = CELL_HEIGHT_MIN > cell_width ? CELL_HEIGHT_MIN : cell_width;
277 gtk_spin_button_set_range(GTK_SPIN_BUTTON(cell_height_spin),
278 min, CELL_HEIGHT_MAX);
281 static void style_colors_changed(void)
283 #if GTK_CHECK_VERSION(2, 10, 0)
284 gtk_widget_set_sensitive(color_table, !style_colors);
285 window_update_colors();
291 static void help_clicked(void)
293 GError *error = NULL;
295 gnome_url_show(CELLWRITER_URL, &error);
297 g_warning("Failed to launch help: %s", error->message);
302 static GtkWidget *create_color_table(void)
312 { "<b>Custom colors:</b>", NULL, FALSE },
313 { "Used cell:", &custom_active_color, FALSE },
314 { "Blank cell:", &custom_inactive_color, FALSE },
315 { "Highlight:", &custom_select_color, FALSE },
316 { "Text and ink:", &custom_ink_color, TRUE },
317 { "Key face:", &custom_key_color, FALSE },
320 entries = (int)(sizeof (colors) / sizeof (*colors));
321 table = gtk_table_new(entries, 2, TRUE);
322 for (i = 0; i < entries; i++) {
326 if (!colors[i].color)
327 w = label_new_markup(colors[i].string);
331 hbox = gtk_hbox_new(FALSE, 0);
332 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1),
334 w = label_new_markup(colors[i].string);
335 gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
336 gtk_misc_set_alignment(GTK_MISC(w), 0, 0.5);
340 gtk_table_attach(GTK_TABLE(table), w, 0, 1, i, i + 1,
341 GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
342 if (!colors[i].color)
345 /* Attach color selection button */
346 w = gtk_color_button_new_with_color(colors[i].color);
347 g_signal_connect(G_OBJECT(w), "color-set",
348 G_CALLBACK(color_set), colors[i].color);
349 gtk_table_attach(GTK_TABLE(table), w, 1, 2, i, i + 1,
350 GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0);
352 /* Some colors reset the cursor */
353 if (colors[i].reset_cursor)
354 g_signal_connect(G_OBJECT(w), "color-set",
355 G_CALLBACK(ink_color_set), NULL);
360 static void window_docking_changed(GtkComboBox *combo)
364 mode = gtk_combo_box_get_active(combo);
365 window_set_docked(mode);
368 static void create_dialog(void)
370 GtkWidget *vbox, *hbox, *vbox2, *notebook, *w;
374 vbox = gtk_vbox_new(FALSE, 0);
377 hbox = gtk_hbutton_box_new();
378 gtk_box_pack_end(GTK_BOX(vbox), hbox, FALSE, TRUE, 0);
379 gtk_button_box_set_layout(GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END);
382 w = gtk_button_new_with_label("Close");
383 gtk_button_set_image(GTK_BUTTON(w),
384 gtk_image_new_from_stock(GTK_STOCK_CLOSE,
385 GTK_ICON_SIZE_BUTTON));
386 gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
387 g_signal_connect(G_OBJECT(w), "clicked",
388 G_CALLBACK(close_dialog), NULL);
390 gtk_box_pack_end(GTK_BOX(vbox), spacer_new(-1, 8), FALSE, TRUE, 0);
392 /* Create notebook */
393 notebook = gtk_notebook_new();
394 gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
397 vbox2 = gtk_vbox_new(FALSE, 0);
398 w = gtk_label_new("Colors");
399 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox2, w);
400 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 8);
402 /* Colors -> Use style */
403 w = check_button_new("Use default theme colors", &style_colors, FALSE);
404 g_signal_connect(G_OBJECT(w), "toggled",
405 G_CALLBACK(style_colors_changed), NULL);
406 gtk_box_pack_start(GTK_BOX(vbox2), w, FALSE, FALSE, 0);
408 /* Colors -> Custom colors */
409 gtk_box_pack_start(GTK_BOX(vbox2), spacer_new(-1, 8), FALSE, FALSE, 0);
410 color_table = create_color_table();
411 gtk_box_pack_start(GTK_BOX(vbox2), color_table, FALSE, FALSE, 0);
412 style_colors_changed();
415 vbox2 = gtk_vbox_new(FALSE, 0);
416 w = gtk_label_new("Languages");
417 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox2, w);
418 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 8);
420 /* Unicode -> Displayed blocks */
421 w = label_new_markup("<b>Enabled Unicode blocks</b>");
422 gtk_box_pack_start(GTK_BOX(vbox2), w, FALSE, FALSE, 0);
423 hbox = gtk_hbox_new(FALSE, 0);
424 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(-1, 4), FALSE, FALSE, 0);
425 gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
427 /* Unicode -> Blocks list */
428 hbox = gtk_hbox_new(FALSE, 0);
429 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1), FALSE, FALSE, 0);
430 w = create_blocks_list();
431 gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 0);
432 gtk_box_pack_start(GTK_BOX(vbox2), hbox, TRUE, TRUE, 0);
434 /* Recognition -> Duplicate glyphs */
435 gtk_box_pack_start(GTK_BOX(vbox2), spacer_new(-1, 8), FALSE, FALSE, 0);
436 w = label_new_markup("<b>Language options</b>");
437 gtk_box_pack_start(GTK_BOX(vbox2), w, FALSE, FALSE, 0);
439 /* Unicode -> Disable Latin letters */
440 hbox = gtk_hbox_new(FALSE, 0);
441 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1), FALSE, FALSE, 0);
442 w = check_button_new("Disable Basic Latin letters",
443 &no_latin_alpha, TRUE);
444 gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 0);
445 gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
446 gtk_tooltips_set_tip(tooltips, w,
447 "If you have trained both the Basic Latin block "
448 "and a block with characters similar to Latin "
449 "letters (for instance, Cyrillic) you can disable "
450 "the Basic Latin letters in order to use only "
451 "numbers and symbols from Basic Latin.", NULL);
453 /* Unicode -> Right-to-left */
454 hbox = gtk_hbox_new(FALSE, 0);
455 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1), FALSE, FALSE, 0);
456 w = check_button_new("Enable right-to-left mode",
457 &right_to_left, TRUE);
458 gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 0);
459 gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
460 gtk_tooltips_set_tip(tooltips, w,
461 PACKAGE_NAME " will expect you to write from "
462 "the rightmost cell to the left and will pad "
463 "cells and create new lines accordingly.", NULL);
465 /* Recognition page */
466 vbox2 = gtk_vbox_new(FALSE, 0);
467 gtk_container_set_border_width(GTK_CONTAINER(vbox2), 8);
468 w = gtk_label_new("Recognition");
469 gtk_notebook_append_page(GTK_NOTEBOOK(notebook), vbox2, w);
471 /* Recognition -> Samples */
472 w = label_new_markup("<b>Training samples</b>");
473 gtk_box_pack_start(GTK_BOX(vbox2), w, FALSE, FALSE, 0);
475 /* Recognition -> Samples -> Train on input */
476 hbox = gtk_hbox_new(FALSE, 0);
477 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1), FALSE, FALSE, 0);
478 w = check_button_new("Train on input when entering",
479 &train_on_input, FALSE);
480 gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 0);
481 gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
482 gtk_tooltips_set_tip(tooltips, w,
483 "When enabled, input characters will be used as "
484 "training samples when 'Enter' is pressed. This "
485 "is a good way to quickly build up many samples, "
486 "but can generate poor samples if your writing "
487 "gets sloppy.", NULL);
489 /* Recognition -> Samples -> Maximum */
490 hbox = gtk_hbox_new(FALSE, 0);
491 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1), FALSE, FALSE, 0);
492 w = label_new_markup("Samples per character: ");
493 gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
494 w = spin_button_new_int(2, SAMPLES_MAX, &samples_max, FALSE);
495 gtk_box_pack_start(GTK_BOX(hbox), w, FALSE, FALSE, 0);
496 gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
497 gtk_tooltips_set_tip(tooltips, w,
498 "The maximum number of training samples kept per "
499 "character. Lower this value if recognition is "
500 "too slow or the program uses too much memory.",
503 /* Recognition -> Word context */
504 gtk_box_pack_start(GTK_BOX(vbox2), spacer_new(-1, 8), FALSE, FALSE, 0);
505 w = label_new_markup("<b>Word context</b>");
506 gtk_box_pack_start(GTK_BOX(vbox2), w, FALSE, FALSE, 0);
508 /* Recognition -> Word context -> English */
509 hbox = gtk_hbox_new(FALSE, 0);
510 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1), FALSE, FALSE, 0);
511 w = check_button_new("Enable English word context",
512 &wordfreq_enable, FALSE);
513 gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 0);
514 gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
515 gtk_tooltips_set_tip(tooltips, w,
516 "Use a dictionary of the most frequent English "
517 "words to assist recognition. Also aids in "
518 "consistent recognition of numbers and "
519 "capitalization.", NULL);
521 /* Recognition -> Preprocessor */
522 gtk_box_pack_start(GTK_BOX(vbox2), spacer_new(-1, 8), FALSE, FALSE, 0);
523 w = label_new_markup("<b>Preprocessor</b>");
524 gtk_box_pack_start(GTK_BOX(vbox2), w, FALSE, FALSE, 0);
526 /* Recognition -> Preprocessor -> Ignore stroke direction */
527 hbox = gtk_hbox_new(FALSE, 0);
528 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1), FALSE, FALSE, 0);
529 w = check_button_new("Ignore stroke direction",
530 &ignore_stroke_dir, FALSE);
531 gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 0);
532 gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
533 gtk_tooltips_set_tip(tooltips, w,
534 "Match input strokes with training sample strokes "
535 "that were drawn in the opposite direction. "
536 "Disabling this can boost recognition speed.",
539 /* Recognition -> Preprocessor -> Ignore stroke number */
540 hbox = gtk_hbox_new(FALSE, 0);
541 gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1), FALSE, FALSE, 0);
542 w = check_button_new("Match differing stroke numbers",
543 &ignore_stroke_num, FALSE);
544 gtk_box_pack_start(GTK_BOX(hbox), w, TRUE, TRUE, 0);
545 gtk_box_pack_start(GTK_BOX(vbox2), hbox, FALSE, FALSE, 0);
546 gtk_tooltips_set_tip(tooltips, w,
547 "Match inputs to training samples that do not "
548 "have the same number of strokes. Disabling this "
549 "can boost recognition speed.", NULL);
551 /* Create dialog window */
552 options_dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
553 g_signal_connect(G_OBJECT(options_dialog), "delete_event",
554 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
555 gtk_window_set_destroy_with_parent(GTK_WINDOW(options_dialog), TRUE);
556 gtk_window_set_resizable(GTK_WINDOW(options_dialog), TRUE);
557 gtk_window_set_title(GTK_WINDOW(options_dialog), "CellWriter Setup");
558 gtk_container_set_border_width(GTK_CONTAINER(options_dialog), 8);
559 gtk_container_add(GTK_CONTAINER(options_dialog), vbox);
560 if (!window_embedded)
561 gtk_window_set_transient_for(GTK_WINDOW(options_dialog),
565 void options_dialog_open(void)
568 gtk_widget_show_all(options_dialog);