Initial check-in
[him-cellwriter] / src / options.c
1
2 /*
3
4 cellwriter -- a character recognition input method
5 Copyright (C) 2007 Michael Levin <risujin@risujin.org>
6
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.
11
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.
16
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.
20
21 */
22
23 #include "config.h"
24 #include "common.h"
25 #include "recognize.h"
26 #include <stdlib.h>
27 #include <string.h>
28 #ifdef HAVE_GNOME
29 #include <libgnome/libgnome.h>
30 #endif
31
32 /* preprocess.c */
33 int ignore_stroke_dir, ignore_stroke_num;
34
35 /* cellwidget.c */
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;
40
41 void cell_widget_render(void);
42 void cell_widget_set_cursor(int recreate);
43 void cell_widget_enable_xinput(int on);
44
45 /* keywidget.c */
46 GdkColor custom_key_color;
47 int keyboard_size;
48
49 void key_widget_update_colors(void);
50
51 int status_menu_left_click;
52
53 /*
54         Profile options
55 */
56
57 static void color_sync(GdkColor *color)
58 {
59         profile_sync_short((short*)&color->red);
60         profile_sync_short((short*)&color->green);
61         profile_sync_short((short*)&color->blue);
62 }
63
64 void options_sync(void)
65 /* Read or write options. Order here is important for compatibility. */
66 {
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);
86         profile_write("\n");
87 }
88
89 /*
90         Unicode blocks list
91 */
92
93 static void unicode_block_toggled(GtkCellRendererToggle *renderer, gchar *path,
94                                   GtkListStore *blocks_store)
95 {
96         UnicodeBlock *block;
97         GtkTreePath *tree_path;
98         GtkTreeIter iter;
99         GValue value;
100         gboolean enabled;
101         int index;
102
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;
109
110         /* Toggle its value */
111         memset(&value, 0, sizeof (value));
112         gtk_tree_model_get_value(GTK_TREE_MODEL(blocks_store), &iter, 0,
113                                  &value);
114         enabled = !g_value_get_boolean(&value);
115         gtk_list_store_set(blocks_store, &iter, 0, enabled, -1);
116         unicode_block_toggle(index, enabled);
117 }
118
119 static GtkWidget *create_blocks_list(void)
120 {
121         GtkWidget *view, *scrolled;
122         GtkTreeIter iter;
123         GtkTreeViewColumn *column;
124         GtkListStore *blocks_store;
125         GtkCellRenderer *renderer;
126         UnicodeBlock *block;
127
128         /* Tree view */
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 "
135                              "combo box.", NULL);
136
137         /* Column */
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);
148
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,
154                                    1, block->name, -1);
155                 block++;
156         }
157
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);
163
164         return scrolled;
165 }
166
167 /*
168         Options dialog
169 */
170
171 #define CELL_WIDTH_MIN  24
172 #define CELL_HEIGHT_MIN 48
173 #define CELL_HEIGHT_MAX 96
174
175 static GtkWidget *options_dialog = NULL, *cell_width_spin, *cell_height_spin,
176                  *color_table;
177
178 static void close_dialog(void)
179 {
180   save_profile();
181         gtk_widget_hide(options_dialog);
182 }
183
184 static void color_set(GtkColorButton *button, GdkColor *color)
185 {
186         gtk_color_button_get_color(button, color);
187         window_update_colors();
188 }
189
190 static void ink_color_set(void)
191 {
192         cell_widget_set_cursor(TRUE);
193 }
194
195 static void xinput_enabled_toggled(void)
196 {
197         cell_widget_enable_xinput(xinput_enabled);
198 }
199
200 static void spin_value_changed_int(GtkSpinButton *button, int *value)
201 {
202         *value = (int)gtk_spin_button_get_value(button);
203 }
204
205 static void spin_value_changed_int_repack(GtkSpinButton *button, int *value)
206 {
207         spin_value_changed_int(button, value);
208         window_pack();
209 }
210
211 static void check_button_toggled(GtkToggleButton *button, int *value)
212 {
213         *value = gtk_toggle_button_get_active(button);
214 }
215
216 static void check_button_toggled_repack(GtkToggleButton *button, int *value)
217 {
218         check_button_toggled(button, value);
219         window_pack();
220 }
221
222 static GtkWidget *label_new_markup(const char *s)
223 {
224         GtkWidget *w;
225
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);
229         return w;
230 }
231
232 static GtkWidget *spacer_new(int width, int height)
233 {
234         GtkWidget *w;
235
236         w = gtk_hbox_new(FALSE, 0);
237         gtk_widget_set_size_request(w, width, height);
238         return w;
239 }
240
241 static GtkWidget *spin_button_new_int(int min, int max, int *variable,
242                                       int repack)
243 {
244         GtkWidget *w;
245
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);
251         return w;
252 }
253
254 static GtkWidget *check_button_new(const char *label, int *variable, int repack)
255 {
256         GtkWidget *w;
257
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);
263         return w;
264 }
265
266 static void cell_height_value_changed(void)
267 {
268         gtk_spin_button_set_range(GTK_SPIN_BUTTON(cell_width_spin),
269                                   CELL_WIDTH_MIN, cell_height);
270 }
271
272 static void cell_width_value_changed(void)
273 {
274         int min;
275
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);
279 }
280
281 static void style_colors_changed(void)
282 {
283 #if GTK_CHECK_VERSION(2, 10, 0)
284         gtk_widget_set_sensitive(color_table, !style_colors);
285         window_update_colors();
286 #endif
287 }
288
289 #ifdef HAVE_GNOME
290
291 static void help_clicked(void)
292 {
293         GError *error = NULL;
294
295         gnome_url_show(CELLWRITER_URL, &error);
296         if (error)
297                 g_warning("Failed to launch help: %s", error->message);
298 }
299
300 #endif
301
302 static GtkWidget *create_color_table(void)
303 {
304         GtkWidget *table;
305         int i, entries;
306
307         struct {
308                 const char *string;
309                 GdkColor *color;
310                 int reset_cursor;
311         } colors[] = {
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 },
318         };
319
320         entries = (int)(sizeof (colors) / sizeof (*colors));
321         table = gtk_table_new(entries, 2, TRUE);
322         for (i = 0; i < entries; i++) {
323                 GtkWidget *w, *hbox;
324
325                 /* Headers */
326                 if (!colors[i].color)
327                         w = label_new_markup(colors[i].string);
328
329                 /* Color label */
330                 else {
331                         hbox = gtk_hbox_new(FALSE, 0);
332                         gtk_box_pack_start(GTK_BOX(hbox), spacer_new(16, -1),
333                                            FALSE, FALSE, 0);
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);
337                         w = hbox;
338                 }
339
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)
343                         continue;
344
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);
351
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);
356         }
357         return table;
358 }
359
360 static void window_docking_changed(GtkComboBox *combo)
361 {
362         int mode;
363
364         mode = gtk_combo_box_get_active(combo);
365         window_set_docked(mode);
366 }
367
368 static void create_dialog(void)
369 {
370         GtkWidget *vbox, *hbox, *vbox2, *notebook, *w;
371
372         if (options_dialog)
373                 return;
374         vbox = gtk_vbox_new(FALSE, 0);
375
376         /* Buttons box */
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);
380
381         /* Close button */
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);
389
390         gtk_box_pack_end(GTK_BOX(vbox), spacer_new(-1, 8), FALSE, TRUE, 0);
391
392         /* Create notebook */
393         notebook = gtk_notebook_new();
394         gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
395
396         /* Colors page */
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);
401
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);
407
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();
413
414         /* Unicode page */
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);
419
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);
426
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);
433
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);
438
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);
452
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);
464
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);
470
471         /* Recognition -> Samples */
472         w = label_new_markup("<b>Training samples</b>");
473         gtk_box_pack_start(GTK_BOX(vbox2), w, FALSE, FALSE, 0);
474
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);
488
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.",
501                              NULL);
502
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);
507
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);
520
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);
525
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.",
537                              NULL);
538
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);
550
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),
562                                              GTK_WINDOW(window));
563 }
564
565 void options_dialog_open(void)
566 {
567         create_dialog();
568         gtk_widget_show_all(options_dialog);
569 }
570