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.
27 #include <X11/Xatom.h>
31 #include "hildon-im-ui.h"
34 void options_dialog_open(void);
37 void update_enabled_samples(void);
40 extern int training, cell_width, cell_height, cell_cols_pref;
42 GtkWidget *cell_widget_new(void);
43 void cell_widget_clear(void);
44 void cell_widget_render(void);
45 int cell_widget_insert(void);
46 void cell_widget_train(void);
47 void cell_widget_pack(void);
48 int cell_widget_update_colors(void);
49 void cell_widget_show_buffer(GtkWidget *button);
50 int cell_widget_scrollbar_width(void);
51 int cell_widget_get_height(void);
53 extern HildonIMUI * ui;
56 extern int keyboard_only;
63 GtkTooltips *tooltips;
64 int window_force_x = -1, window_force_y = -1, training_block = 0,
65 window_docked = WINDOW_UNDOCKED, window_force_docked = -1,
66 window_button_labels = TRUE, window_force_show = FALSE,
67 window_force_hide = FALSE, style_colors = TRUE, window_embedded = FALSE,
68 window_struts = FALSE;
71 static char *tab_xpm[] =
82 static GtkWidget *train_label_box, *train_label_frame, *train_label = NULL,
83 *bottom_box, *blocks_combo, *cell_widget,
84 *setup_button, *keys_button, *insert_button, *enter_button, *cancel_button,
85 *clear_button, *train_button, *buffer_button;
86 static GdkRectangle window_frame = {-1, -1, 0, 0}, window_frame_saved;
87 static int screen_width = -1, screen_height = -1,
88 window_shown = TRUE, history_valid = FALSE, keys_on = FALSE;
90 static void toggle_button_labels(int on)
92 static int labels_off;
94 if (labels_off && on) {
95 gtk_button_set_label(GTK_BUTTON(train_button), "Train");
96 gtk_button_set_label(GTK_BUTTON(setup_button), "Setup");
97 gtk_button_set_label(GTK_BUTTON(clear_button), "Clear");
98 gtk_button_set_label(GTK_BUTTON(insert_button), "Insert");
99 gtk_button_set_label(GTK_BUTTON(keys_button), "Keys");
100 } else if (!labels_off && !on) {
101 gtk_button_set_label(GTK_BUTTON(train_button), "");
102 gtk_button_set_label(GTK_BUTTON(setup_button), "");
103 gtk_button_set_label(GTK_BUTTON(keys_button), "");
104 gtk_button_set_label(GTK_BUTTON(clear_button), "");
105 gtk_button_set_label(GTK_BUTTON(insert_button), "");
106 gtk_button_set_label(GTK_BUTTON(keys_button), "");
111 void window_pack(void)
114 toggle_button_labels(window_button_labels);
116 gtk_widget_show(train_label_frame);
119 void window_update_colors(void)
123 if (cell_widget_update_colors() || keys_changed)
124 cell_widget_render();
127 static void update_struts(void)
128 /* Reserves screen space for the docked window.
129 FIXME In Metacity it causes the window to be shoved outside of its own
130 struts, which is especially devastating for top docking because this
131 causes an infinite loop of events causing the struts to repeatedly
132 scan down from the top of the screen. GOK and other applications
133 somehow get around this but I can't figure out how. */
135 static guint32 struts[12];
136 guint32 new2 = 0, new3 = 0, new9 = 0, new11 = 0;
137 GdkAtom atom_strut, atom_strut_partial, cardinal;
139 if (!window || !window->window || !window_struts)
141 if (window_docked == WINDOW_DOCKED_TOP) {
142 new2 = window_frame.y + window_frame.height;
143 new9 = window_frame.width;
144 } else if (window_docked == WINDOW_DOCKED_BOTTOM) {
145 new3 = window_frame.height;
146 new11 = window_frame.width;
148 if (new2 == struts[2] && new3 == struts[3] &&
149 new9 == struts[9] && new11 == struts[11])
151 trace("top=%d (%d) bottom=%d (%d)", new2, new9, new3, new11);
156 atom_strut = gdk_atom_intern("_NET_WM_STRUT", FALSE),
157 atom_strut_partial = gdk_atom_intern("_NET_WM_STRUT_PARTIAL", FALSE);
158 cardinal = gdk_atom_intern("CARDINAL", FALSE);
159 gdk_property_change(GDK_WINDOW(window->window), atom_strut, cardinal,
160 32, GDK_PROP_MODE_REPLACE, (guchar*)&struts, 4);
161 gdk_property_change(GDK_WINDOW(window->window), atom_strut_partial,
162 cardinal, 32, GDK_PROP_MODE_REPLACE,
163 (guchar*)&struts, 12);
166 static void set_geometry_hints(void)
168 GdkGeometry geometry;
170 geometry.min_width = -1;
171 geometry.min_height = -1;
172 geometry.max_width = -1;
173 geometry.max_height = -1;
175 /* Use window geometry to force the window to be as large as the
178 geometry.max_width = geometry.min_width = screen_width;
180 gtk_window_set_geometry_hints(GTK_WINDOW(window), window, &geometry,
181 GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
182 trace("%dx%d", geometry.min_width, geometry.min_height);
184 /* In some bright and sunny alternate universe when specifications are
185 actually implemented as inteded, this function alone would cause the
186 window frame to expand upwards without having to perform the ugly
187 hack in window_configure(). XFWM4 does not respect this hint and
188 setting this hint will further mess up the window_configure()
190 /*geometry.win_gravity = window_docked == WINDOW_DOCKED_TOP ?
191 GDK_GRAVITY_NORTH_WEST :
192 GDK_GRAVITY_SOUTH_WEST;
193 gtk_window_set_geometry_hints(GTK_WINDOW(window), window, &geometry,
194 GDK_HINT_WIN_GRAVITY);*/
197 static void docked_move_resize(void)
204 screen = gtk_window_get_screen(GTK_WINDOW(window));
205 if (window_docked == WINDOW_DOCKED_BOTTOM)
206 y = gdk_screen_get_height(screen) - window_frame.height;
207 set_geometry_hints();
208 gtk_window_move(GTK_WINDOW(window), 0, y);
213 static gboolean window_configure(GtkWidget *widget, GdkEventConfigure *event)
214 /* Intelligently grow the window up and/or left if we are in the bottom or
215 right corners of the screen respectively */
217 GdkRectangle new_frame = {0, 0, 0, 0};
220 int screen_w, screen_h, height_change, label_w;
222 if (!window || !window->window)
224 display = gtk_widget_get_display(window);
226 /* Get screen and window information */
227 screen = gtk_window_get_screen(GTK_WINDOW(window));
228 screen_w = gdk_screen_get_width(screen);
229 screen_h = gdk_screen_get_height(screen);
230 gdk_window_get_frame_extents(window->window, &new_frame);
232 /* We need to resize wrapped labels manually */
233 label_w = window->allocation.width - 16;
234 if (train_label && train_label->requisition.width != label_w)
235 gtk_widget_set_size_request(train_label, label_w, -1);
237 /* Docked windows have special placing requirements */
238 height_change = new_frame.height - window_frame.height;
240 window_frame = new_frame;
241 if (screen_w != screen_width || screen_h != screen_height ||
242 (height_change && window_docked == WINDOW_DOCKED_BOTTOM)) {
243 screen_width = screen_w;
244 screen_height = screen_h;
245 trace("move-sizing bottom-docked window");
246 docked_move_resize();
251 screen_width = screen_w;
252 screen_height = screen_h;
254 /* Do nothing on the first configure */
255 if (window_frame.height <= 1) {
256 window_frame = new_frame;
260 /* Keep the window aligned to the bottom border */
261 if (height_change && window_frame.y + window_frame.height / 2 >
262 gdk_screen_get_height(screen) / 2)
263 window_frame.y -= height_change;
267 /* Do not allow the window to go off-screen */
268 if (window_frame.x + new_frame.width > screen_w)
269 window_frame.x = screen_w - new_frame.width;
270 if (window_frame.y + new_frame.height > screen_h)
271 window_frame.y = screen_h - new_frame.height;
272 if (window_frame.x < 0)
274 if (window_frame.y < 0)
277 /* Some window managers (Metacity) do not allow windows to resize
278 larger than the screen and will move the window back within the
279 screen bounds when this happens. We don't like this because it
280 screws with our own correcting offset. Fortunately, both the move
281 and the resize are bundled in one configure event so we can work
282 around this by using our old x/y coordinates when the dimensions
284 if (height_change && (new_frame.x != window_frame.x ||
285 new_frame.y != window_frame.y)) {
286 gtk_window_move(GTK_WINDOW(window),
287 window_frame.x, window_frame.y);
288 window_frame.width = new_frame.width;
289 window_frame.height = new_frame.height;
290 trace("moving to (%d, %d)", window_frame.x, window_frame.y);
292 window_frame = new_frame;
297 void window_set_docked(int mode)
299 if (mode < WINDOW_UNDOCKED)
300 mode = WINDOW_UNDOCKED;
301 if (mode >= WINDOW_DOCKED_BOTTOM)
302 mode = WINDOW_DOCKED_BOTTOM;
303 if (mode && !window_docked)
304 window_frame_saved = window_frame;
305 window_docked = mode;
306 gtk_window_set_decorated(GTK_WINDOW(window), !mode);
307 set_geometry_hints();
310 /* Restore the old window position */
313 window_frame = window_frame_saved;
314 gtk_window_move(GTK_WINDOW(window), window_frame.x,
316 trace("moving to (%d, %d)", window_frame.x, window_frame.y);
319 /* Move the window into docked position */
321 docked_move_resize();
324 void train_button_toggled(void)
326 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(train_button))) {
328 gtk_widget_hide(clear_button);
329 gtk_widget_hide(keys_button);
330 gtk_widget_hide(insert_button);
331 gtk_widget_hide(enter_button);
332 gtk_widget_hide(buffer_button);
333 gtk_widget_show(blocks_combo);
334 gtk_widget_show(train_label_frame);
338 gtk_widget_hide(blocks_combo);
339 gtk_widget_hide(train_label_frame);
340 gtk_widget_show(clear_button);
341 gtk_widget_show(keys_button);
342 gtk_widget_show(insert_button);
343 gtk_widget_show(enter_button);
344 gtk_widget_show(buffer_button);
348 static int block_combo_to_unicode(int block)
349 /* Find the Combo Box index's unicode block */
353 for (i = 0, pos = 0; unicode_blocks[i].name; i++)
354 if (unicode_blocks[i].enabled && ++pos > block)
359 static int block_unicode_to_combo(int block)
360 /* Find the Unicode block's combo box position */
364 for (i = 0, pos = 0; i < block && unicode_blocks[i].name; i++)
365 if (unicode_blocks[i].enabled)
370 static void blocks_combo_changed(void)
374 pos = gtk_combo_box_get_active(GTK_COMBO_BOX(blocks_combo));
375 training_block = block_combo_to_unicode(pos);
380 static GtkWidget *create_blocks_combo(void)
382 GtkWidget *event_box;
386 gtk_widget_destroy(blocks_combo);
387 blocks_combo = gtk_combo_box_new_text();
388 block = unicode_blocks;
389 while (block->name) {
391 gtk_combo_box_append_text(GTK_COMBO_BOX(blocks_combo),
395 gtk_combo_box_set_active(GTK_COMBO_BOX(blocks_combo),
396 block_unicode_to_combo(training_block));
397 gtk_combo_box_set_focus_on_click(GTK_COMBO_BOX(blocks_combo), FALSE);
398 g_signal_connect(G_OBJECT(blocks_combo), "changed",
399 G_CALLBACK(blocks_combo_changed), NULL);
401 /* Wrap ComboBox in an EventBox for tooltips */
402 event_box = gtk_event_box_new();
403 gtk_tooltips_set_tip(tooltips, event_box,
404 "Select Unicode block to train", NULL);
405 gtk_container_add(GTK_CONTAINER(event_box), blocks_combo);
410 void window_toggle(void)
412 if (GTK_WIDGET_VISIBLE(window)) {
413 gtk_widget_hide(window);
414 window_shown = FALSE;
416 /* User may have rendered themselves unable to interact with
417 the program widgets by pressing one of the modifier keys
418 that, for instance, puts the WM in move-window mode, so
419 if the window is closed we need to reset the held keys */
421 gtk_widget_show(window);
426 void window_show(void)
428 if (!(GTK_WIDGET_VISIBLE(window)))
432 void window_hide(void)
434 if (GTK_WIDGET_VISIBLE(window))
438 gboolean window_close(void)
444 static void window_style_set(GtkWidget *w)
446 GdkColor train_label_bg = RGB_TO_GDKCOLOR(255, 255, 200),
447 train_label_fg = RGB_TO_GDKCOLOR(0, 0, 0);
449 /* The training label color is taken from tooltips */
452 #if GTK_CHECK_VERSION(2, 10, 0)
453 gtk_style_lookup_color(w->style, "tooltip_bg_color", &train_label_bg);
454 gtk_style_lookup_color(w->style, "tooltip_fg_color", &train_label_fg);
456 gtk_widget_modify_bg(train_label_frame, GTK_STATE_NORMAL,
458 gtk_widget_modify_bg(train_label_box, GTK_STATE_NORMAL,
460 gtk_widget_modify_fg(train_label, GTK_STATE_NORMAL,
462 gtk_widget_modify_fg(blocks_combo, GTK_STATE_NORMAL,
466 static void button_set_image_xpm(GtkWidget *button, char **xpm)
467 /* Creates a button with an XPM icon */
473 pixmap = gdk_pixmap_colormap_create_from_xpm_d
474 (NULL, gdk_colormap_get_system(), &mask, NULL, xpm);
475 image = gtk_image_new_from_pixmap(pixmap, mask);
476 g_object_unref(pixmap);
477 gtk_button_set_image(GTK_BUTTON(button), image);
480 void cell_widget_insert_surrounding_string();
482 static void insert_button_clicked(void)
485 if (cell_widget_insert()) {
486 history_valid = TRUE;
487 gtk_widget_set_sensitive(buffer_button, TRUE);
490 cell_widget_insert_surrounding_string();
495 hildon_im_ui_restore_previous_mode(ui);
502 static void return_button_clicked(void)
505 if (cell_widget_insert()) {
506 history_valid = TRUE;
507 gtk_widget_set_sensitive(buffer_button, TRUE);
508 //hildon_im_ui_send_communication_message(ui, HILDON_IM_CONTEXT_HANDLE_ENTER);
509 hildon_im_ui_send_utf8(ui, "\n");
512 cell_widget_insert_surrounding_string();
514 //hildon_im_ui_send_communication_message(ui, HILDON_IM_CONTEXT_HANDLE_ENTER);
515 //hildon_im_ui_send_communication_message(ui, HILDON_IM_CONTEXT_ENTER_ON_FOCUS);
516 hildon_im_ui_send_utf8(ui, "\n");
521 hildon_im_ui_restore_previous_mode(ui);
527 static void cancel_button_clicked(void){
530 hildon_im_ui_restore_previous_mode(ui);
535 static void buffer_button_pressed(void)
537 if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buffer_button))) {
538 cell_widget_show_buffer(buffer_button);
539 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(buffer_button),
544 static void print_window_xid(GtkWidget *widget)
546 g_print("%d\n", (unsigned int)GDK_WINDOW_XID(widget->window));
549 void window_create(GtkWidget *parent)
550 /* Create the main window and child widgets */
552 GtkWidget *widget, *window_vbox, *image;
555 /* Create the window or plug */
557 window = !window_embedded ? gtk_window_new(GTK_WINDOW_TOPLEVEL) :
562 g_signal_connect(G_OBJECT(window), "delete-event",
563 G_CALLBACK(window_close), NULL);
564 g_signal_connect(G_OBJECT(window), "destroy",
565 G_CALLBACK(gtk_main_quit), NULL);
566 g_signal_connect(G_OBJECT(window), "style-set",
567 G_CALLBACK(window_style_set), NULL);
568 g_signal_connect(G_OBJECT(window), "configure-event",
569 G_CALLBACK(window_configure), NULL);
570 gtk_window_set_accept_focus(GTK_WINDOW(window), FALSE);
571 gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
573 /* This hint was alleged to fix the strut problems with metacity but
574 doesn't and only causes the window to overlap the docked panels */
575 /*gtk_window_set_type_hint(GTK_WINDOW(window),
576 GDK_WINDOW_TYPE_HINT_DOCK);*/
579 tooltips = gtk_tooltips_new();
580 gtk_tooltips_enable(tooltips);
583 window_vbox = gtk_vbox_new(FALSE, 0);
584 gtk_widget_show(window_vbox);
586 /* Training info label frame */
587 train_label_frame = gtk_frame_new(NULL);
588 gtk_widget_set_no_show_all(train_label_frame, TRUE);
589 gtk_frame_set_shadow_type(GTK_FRAME(train_label_frame), GTK_SHADOW_IN);
590 gtk_container_set_border_width(GTK_CONTAINER(train_label_frame), 2);
592 /* Training info label */
593 train_label = gtk_label_new(NULL);
594 gtk_label_set_line_wrap(GTK_LABEL(train_label), TRUE);
595 gtk_label_set_justify(GTK_LABEL(train_label), GTK_JUSTIFY_FILL);
596 gtk_label_set_markup(GTK_LABEL(train_label),
597 "<b>Training Mode:</b> Carefully draw each "
598 "character in its cell.");
599 gtk_widget_show(train_label);
601 /* Training info label colored box */
602 train_label_box = gtk_event_box_new();
603 gtk_widget_show(train_label_box);
604 gtk_container_add(GTK_CONTAINER(train_label_box), train_label);
605 gtk_container_add(GTK_CONTAINER(train_label_frame), train_label_box);
606 gtk_widget_show_all(train_label_frame);
607 gtk_box_pack_start(GTK_BOX(window_vbox), train_label_frame,
611 cell_widget = cell_widget_new();
612 gtk_box_pack_start(GTK_BOX(window_vbox), cell_widget, TRUE, TRUE, 2);
614 gtk_widget_show_all(cell_widget);
617 bottom_box = gtk_hbox_new(FALSE, 0);
620 train_button = gtk_toggle_button_new_with_label("Train");
621 gtk_button_set_focus_on_click(GTK_BUTTON(train_button), FALSE);
622 gtk_button_set_image(GTK_BUTTON(train_button),
623 gtk_image_new_from_stock(GTK_STOCK_MEDIA_RECORD,
624 GTK_ICON_SIZE_BUTTON));
625 gtk_button_set_relief(GTK_BUTTON(train_button), GTK_RELIEF_NONE);
626 gtk_box_pack_start(GTK_BOX(bottom_box), train_button, FALSE, FALSE, 0);
627 g_signal_connect(G_OBJECT(train_button), "toggled",
628 G_CALLBACK(train_button_toggled), 0);
629 gtk_tooltips_set_tip(tooltips, train_button, "Toggle training mode",
633 setup_button = gtk_button_new_with_label("Setup");
634 gtk_button_set_focus_on_click(GTK_BUTTON(setup_button), FALSE);
635 gtk_button_set_image(GTK_BUTTON(setup_button),
636 gtk_image_new_from_stock(GTK_STOCK_PREFERENCES,
637 GTK_ICON_SIZE_BUTTON));
638 gtk_button_set_relief(GTK_BUTTON(setup_button), GTK_RELIEF_NONE);
639 gtk_box_pack_start(GTK_BOX(bottom_box), setup_button, FALSE, FALSE, 0);
640 g_signal_connect(G_OBJECT(setup_button), "clicked",
641 G_CALLBACK(options_dialog_open), 0);
642 gtk_tooltips_set_tip(tooltips, setup_button, "Edit program options",
645 /* Expanding box to keep things tidy */
646 widget = gtk_vbox_new(FALSE, 0);
647 gtk_box_pack_start(GTK_BOX(bottom_box), widget, TRUE, FALSE, 0);
649 /* Training Unicode Block selector */
650 widget = create_blocks_combo();
651 gtk_box_pack_start(GTK_BOX(bottom_box), widget, FALSE, FALSE, 0);
652 gtk_widget_set_no_show_all(blocks_combo, TRUE);
655 clear_button = gtk_button_new_with_label("Clear");
656 gtk_button_set_focus_on_click(GTK_BUTTON(clear_button), FALSE);
657 image = gtk_image_new_from_stock(GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON);
658 gtk_button_set_image(GTK_BUTTON(clear_button), image);
659 gtk_button_set_relief(GTK_BUTTON(clear_button), GTK_RELIEF_NONE);
660 gtk_box_pack_start(GTK_BOX(bottom_box), clear_button, FALSE, FALSE, 0);
661 g_signal_connect(G_OBJECT(clear_button), "clicked",
662 G_CALLBACK(cell_widget_clear), 0);
663 gtk_tooltips_set_tip(tooltips, clear_button, "Clear current input",
667 cancel_button = gtk_button_new_with_label("Cancel");
668 gtk_button_set_focus_on_click(GTK_BUTTON(cancel_button), FALSE);
669 gtk_button_set_image(GTK_BUTTON(cancel_button),
670 gtk_image_new_from_stock(GTK_STOCK_OK,
671 GTK_ICON_SIZE_BUTTON));
672 gtk_button_set_relief(GTK_BUTTON(cancel_button), GTK_RELIEF_NONE);
673 gtk_box_pack_start(GTK_BOX(bottom_box), cancel_button, FALSE, FALSE, 0);
674 g_signal_connect(G_OBJECT(cancel_button), "clicked",
675 G_CALLBACK(cancel_button_clicked), 0);
676 gtk_tooltips_set_tip(tooltips, cancel_button,
677 "Insert input and press Enter key", NULL);
679 enter_button = gtk_button_new_with_label("Enter");
680 gtk_button_set_focus_on_click(GTK_BUTTON(enter_button), FALSE);
681 gtk_button_set_image(GTK_BUTTON(enter_button),
682 gtk_image_new_from_stock(GTK_STOCK_OK,
683 GTK_ICON_SIZE_BUTTON));
684 gtk_button_set_relief(GTK_BUTTON(enter_button), GTK_RELIEF_NONE);
685 gtk_box_pack_start(GTK_BOX(bottom_box), enter_button, FALSE, FALSE, 0);
686 g_signal_connect(G_OBJECT(enter_button), "clicked",
687 G_CALLBACK(return_button_clicked), 0);
688 gtk_tooltips_set_tip(tooltips, enter_button,
689 "Insert input and press Enter key", NULL);
692 insert_button = gtk_button_new_with_label("Insert");
693 gtk_button_set_focus_on_click(GTK_BUTTON(insert_button), FALSE);
694 gtk_button_set_image(GTK_BUTTON(insert_button),
695 gtk_image_new_from_stock(GTK_STOCK_OK,
696 GTK_ICON_SIZE_BUTTON));
697 gtk_button_set_relief(GTK_BUTTON(insert_button), GTK_RELIEF_NONE);
698 gtk_box_pack_start(GTK_BOX(bottom_box), insert_button, FALSE, FALSE, 0);
699 g_signal_connect(G_OBJECT(insert_button), "clicked",
700 G_CALLBACK(insert_button_clicked), 0);
701 gtk_tooltips_set_tip(tooltips, insert_button,
702 "Insert input", NULL);
704 /* Back buffer button */
705 buffer_button = gtk_toggle_button_new();
706 gtk_button_set_focus_on_click(GTK_BUTTON(buffer_button), FALSE);
707 button_set_image_xpm(buffer_button, tab_xpm);
708 gtk_button_set_relief(GTK_BUTTON(buffer_button), GTK_RELIEF_NONE);
709 gtk_box_pack_start(GTK_BOX(bottom_box), buffer_button, FALSE, FALSE, 0);
710 g_signal_connect(G_OBJECT(buffer_button), "pressed",
711 G_CALLBACK(buffer_button_pressed), NULL);
712 gtk_tooltips_set_tip(tooltips, buffer_button,
713 "Recall previously entered input", NULL);
714 gtk_widget_set_sensitive(buffer_button, FALSE);
716 /* Pack the regular bottom box */
717 gtk_box_pack_start(GTK_BOX(window_vbox), bottom_box, FALSE, FALSE, 0);
719 gtk_widget_show_all(bottom_box);
721 /* Update button labels */
722 toggle_button_labels(window_button_labels);
724 /* Set window style */
725 window_style_set(window);
727 if (window_embedded) {
729 /* Embedding in a screensaver won't let us popup new windows */
730 gtk_widget_hide(buffer_button);
731 gtk_widget_hide(train_button);
732 gtk_widget_hide(setup_button);
734 /* If we are embedded we need to print the plug's window XID */
735 g_signal_connect_after(G_OBJECT(window), "realize",
736 G_CALLBACK(print_window_xid), NULL);
738 gtk_container_add(GTK_CONTAINER(window), window_vbox);
739 gtk_widget_show(window);
743 /* Non-embedded window configuration */
744 gtk_container_add(GTK_CONTAINER(window), window_vbox);
747 gtk_window_set_keep_above(GTK_WINDOW(window), TRUE);
748 gtk_window_set_type_hint(GTK_WINDOW(window),
749 GDK_WINDOW_TYPE_HINT_UTILITY);
750 gtk_window_set_title(GTK_WINDOW(window), PACKAGE_NAME);
751 gtk_window_set_skip_pager_hint(GTK_WINDOW(window), TRUE);
752 gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window), TRUE);
753 gtk_window_set_decorated(GTK_WINDOW(window), TRUE);
754 gtk_window_stick(GTK_WINDOW(window));
756 /* Coordinates passed on the command-line */
757 if (window_force_x >= 0)
758 window_frame.x = window_force_x;
759 if (window_force_y >= 0)
760 window_frame.y = window_force_y;
762 /* Center window on initial startup */
763 screen = gtk_window_get_screen(GTK_WINDOW(window));
764 if (window_frame.x < 0)
765 window_frame.x = gdk_screen_get_width(screen) / 2;
766 if (window_frame.y < 0)
767 window_frame.y = gdk_screen_get_height(screen) * 3 / 4;
768 gtk_window_move(GTK_WINDOW(window), window_frame.x,
771 /* Set the window size */
772 if (window_force_docked >= WINDOW_UNDOCKED)
773 window_docked = window_force_docked;
777 mode = window_docked;
778 window_docked = WINDOW_UNDOCKED;
779 window_set_docked(mode);
783 if (window_force_hide)
784 window_shown = FALSE;
785 else if (window_force_show)
788 gtk_widget_show(window);
792 void window_sync(void)
793 /* Sync data with profile, do not change item order! */
795 profile_write("window");
797 /* Docking the window will mess up the desired natural frame */
798 if (!profile_read_only && window_docked) {
799 profile_sync_int(&window_frame_saved.x);
800 profile_sync_int(&window_frame_saved.y);
802 profile_sync_int(&window_frame.x);
803 profile_sync_int(&window_frame.y);
806 profile_sync_int(&training_block);
807 profile_sync_int(&window_shown);
808 profile_sync_int(&window_button_labels);
809 profile_sync_int(&keyboard_size);
810 profile_sync_int(&window_docked);
814 void window_cleanup(void)
822 /* This table is based on unicode-blocks.h from the gucharmap project */
823 UnicodeBlock unicode_blocks[] =
825 { TRUE, 0x0000, 0x007F, "Basic Latin" },
826 { TRUE, 0x0080, 0x00FF, "Latin-1 Supplement" },
827 { FALSE, 0x0100, 0x017F, "Latin Extended-A" },
828 { FALSE, 0x0180, 0x024F, "Latin Extended-B" },
829 { FALSE, 0x0250, 0x02AF, "IPA Extensions" },
830 { FALSE, 0x02B0, 0x02FF, "Spacing Modifier Letters" },
831 { FALSE, 0x0300, 0x036F, "Combining Diacritical Marks" },
832 { FALSE, 0x0370, 0x03FF, "Greek and Coptic" },
833 { FALSE, 0x0400, 0x04FF, "Cyrillic" },
834 { FALSE, 0x0500, 0x052F, "Cyrillic Supplement" },
835 { FALSE, 0x0530, 0x058F, "Armenian" },
836 { FALSE, 0x0590, 0x05FF, "Hebrew" },
837 { FALSE, 0x0600, 0x06FF, "Arabic" },
838 { FALSE, 0x0700, 0x074F, "Syriac" },
839 { FALSE, 0x0750, 0x077F, "Arabic Supplement" },
840 { FALSE, 0x0780, 0x07BF, "Thaana" },
841 { FALSE, 0x07C0, 0x07FF, "N'Ko" },
842 { FALSE, 0x0900, 0x097F, "Devanagari" },
843 { FALSE, 0x0980, 0x09FF, "Bengali" },
844 { FALSE, 0x0A00, 0x0A7F, "Gurmukhi" },
845 { FALSE, 0x0A80, 0x0AFF, "Gujarati" },
846 { FALSE, 0x0B00, 0x0B7F, "Oriya" },
847 { FALSE, 0x0B80, 0x0BFF, "Tamil" },
848 { FALSE, 0x0C00, 0x0C7F, "Telugu" },
849 { FALSE, 0x0C80, 0x0CFF, "Kannada" },
850 { FALSE, 0x0D00, 0x0D7F, "Malayalam" },
851 { FALSE, 0x0D80, 0x0DFF, "Sinhala" },
852 { FALSE, 0x0E00, 0x0E7F, "Thai" },
853 { FALSE, 0x0E80, 0x0EFF, "Lao" },
854 { FALSE, 0x0F00, 0x0FFF, "Tibetan" },
855 { FALSE, 0x1000, 0x109F, "Myanmar" },
856 { FALSE, 0x10A0, 0x10FF, "Georgian" },
857 { FALSE, 0x1100, 0x11FF, "Hangul Jamo" },
858 { FALSE, 0x1200, 0x137F, "Ethiopic" },
859 { FALSE, 0x1380, 0x139F, "Ethiopic Supplement" },
860 { FALSE, 0x13A0, 0x13FF, "Cherokee" },
861 { FALSE, 0x1400, 0x167F, "Unified Canadian Aboriginal Syllabics" },
862 { FALSE, 0x1680, 0x169F, "Ogham" },
863 { FALSE, 0x16A0, 0x16FF, "Runic" },
864 { FALSE, 0x1700, 0x171F, "Tagalog" },
865 { FALSE, 0x1720, 0x173F, "Hanunoo" },
866 { FALSE, 0x1740, 0x175F, "Buhid" },
867 { FALSE, 0x1760, 0x177F, "Tagbanwa" },
868 { FALSE, 0x1780, 0x17FF, "Khmer" },
869 { FALSE, 0x1800, 0x18AF, "Mongolian" },
870 { FALSE, 0x1900, 0x194F, "Limbu" },
871 { FALSE, 0x1950, 0x197F, "Tai Le" },
872 { FALSE, 0x1980, 0x19DF, "New Tai Lue" },
873 { FALSE, 0x19E0, 0x19FF, "Khmer Symbols" },
874 { FALSE, 0x1A00, 0x1A1F, "Buginese" },
875 { FALSE, 0x1B00, 0x1B7F, "Balinese" },
876 { FALSE, 0x1D00, 0x1D7F, "Phonetic Extensions" },
877 { FALSE, 0x1D80, 0x1DBF, "Phonetic Extensions Supplement" },
878 { FALSE, 0x1DC0, 0x1DFF, "Combining Diacritical Marks Supplement" },
879 { FALSE, 0x1E00, 0x1EFF, "Latin Extended Additional" },
880 { FALSE, 0x1F00, 0x1FFF, "Greek Extended" },
881 { FALSE, 0x2000, 0x206F, "General Punctuation" },
882 { FALSE, 0x2070, 0x209F, "Superscripts and Subscripts" },
883 { FALSE, 0x20A0, 0x20CF, "Currency Symbols" },
884 { FALSE, 0x20D0, 0x20FF, "Combining Diacritical Marks for Symbols" },
885 { FALSE, 0x2100, 0x214F, "Letterlike Symbols" },
886 { FALSE, 0x2150, 0x218F, "Number Forms" },
887 { FALSE, 0x2190, 0x21FF, "Arrows" },
888 { FALSE, 0x2200, 0x22FF, "Mathematical Operators" },
889 { FALSE, 0x2300, 0x23FF, "Miscellaneous Technical" },
890 { FALSE, 0x2400, 0x243F, "Control Pictures" },
891 { FALSE, 0x2440, 0x245F, "Optical Character Recognition" },
892 { FALSE, 0x2460, 0x24FF, "Enclosed Alphanumerics" },
893 { FALSE, 0x2500, 0x257F, "Box Drawing" },
894 { FALSE, 0x2580, 0x259F, "Block Elements" },
895 { FALSE, 0x25A0, 0x25FF, "Geometric Shapes" },
896 { FALSE, 0x2600, 0x26FF, "Miscellaneous Symbols" },
897 { FALSE, 0x2700, 0x27BF, "Dingbats" },
898 { FALSE, 0x27C0, 0x27EF, "Miscellaneous Mathematical Symbols-A" },
899 { FALSE, 0x27F0, 0x27FF, "Supplemental Arrows-A" },
900 { FALSE, 0x2800, 0x28FF, "Braille Patterns" },
901 { FALSE, 0x2900, 0x297F, "Supplemental Arrows-B" },
902 { FALSE, 0x2980, 0x29FF, "Miscellaneous Mathematical Symbols-B" },
903 { FALSE, 0x2A00, 0x2AFF, "Supplemental Mathematical Operators" },
904 { FALSE, 0x2B00, 0x2BFF, "Miscellaneous Symbols and Arrows" },
905 { FALSE, 0x2C00, 0x2C5F, "Glagolitic" },
906 { FALSE, 0x2C60, 0x2C7F, "Latin Extended-C" },
907 { FALSE, 0x2C80, 0x2CFF, "Coptic" },
908 { FALSE, 0x2D00, 0x2D2F, "Georgian Supplement" },
909 { FALSE, 0x2D30, 0x2D7F, "Tifinagh" },
910 { FALSE, 0x2D80, 0x2DDF, "Ethiopic Extended" },
911 { FALSE, 0x2E00, 0x2E7F, "Supplemental Punctuation" },
912 { FALSE, 0x2E80, 0x2EFF, "CJK Radicals Supplement" },
913 { FALSE, 0x2F00, 0x2FDF, "Kangxi Radicals" },
914 { FALSE, 0x2FF0, 0x2FFF, "Ideographic Description Characters" },
915 { FALSE, 0x3000, 0x303F, "CJK Symbols and Punctuation" },
916 { FALSE, 0x3040, 0x309F, "Hiragana" },
917 { FALSE, 0x30A0, 0x30FF, "Katakana" },
918 { FALSE, 0x3100, 0x312F, "Bopomofo" },
919 { FALSE, 0x3130, 0x318F, "Hangul Compatibility Jamo" },
920 { FALSE, 0x3190, 0x319F, "Kanbun" },
921 { FALSE, 0x31A0, 0x31BF, "Bopomofo Extended" },
922 { FALSE, 0x31C0, 0x31EF, "CJK Strokes" },
923 { FALSE, 0x31F0, 0x31FF, "Katakana Phonetic Extensions" },
924 { FALSE, 0x3200, 0x32FF, "Enclosed CJK Letters and Months" },
925 { FALSE, 0x3300, 0x33FF, "CJK Compatibility" },
926 { FALSE, 0x3400, 0x4DBF, "CJK Unified Ideographs Extension A" },
927 { FALSE, 0x4DC0, 0x4DFF, "Yijing Hexagram Symbols" },
928 { FALSE, 0x4E00, 0x9FFF, "CJK Unified Ideographs" },
929 { FALSE, 0xA000, 0xA48F, "Yi Syllables" },
930 { FALSE, 0xA490, 0xA4CF, "Yi Radicals" },
931 { FALSE, 0xA700, 0xA71F, "Modifier Tone Letters" },
932 { FALSE, 0xA720, 0xA7FF, "Latin Extended-D" },
933 { FALSE, 0xA800, 0xA82F, "Syloti Nagri" },
934 { FALSE, 0xA840, 0xA87F, "Phags-pa" },
935 { FALSE, 0xAC00, 0xD7AF, "Hangul Syllables" },
936 { FALSE, 0xD800, 0xDB7F, "High Surrogates" },
937 { FALSE, 0xDB80, 0xDBFF, "High Private Use Surrogates" },
938 { FALSE, 0xDC00, 0xDFFF, "Low Surrogates" },
939 { FALSE, 0xE000, 0xF8FF, "Private Use Area" },
940 { FALSE, 0xF900, 0xFAFF, "CJK Compatibility Ideographs" },
941 { FALSE, 0xFB00, 0xFB4F, "Alphabetic Presentation Forms" },
942 { FALSE, 0xFB50, 0xFDFF, "Arabic Presentation Forms-A" },
943 { FALSE, 0xFE00, 0xFE0F, "Variation Selectors" },
944 { FALSE, 0xFE10, 0xFE1F, "Vertical Forms" },
945 { FALSE, 0xFE20, 0xFE2F, "Combining Half Marks" },
946 { FALSE, 0xFE30, 0xFE4F, "CJK Compatibility Forms" },
947 { FALSE, 0xFE50, 0xFE6F, "Small Form Variants" },
948 { FALSE, 0xFE70, 0xFEFF, "Arabic Presentation Forms-B" },
949 { FALSE, 0xFF00, 0xFFEF, "Halfwidth and Fullwidth Forms" },
950 { FALSE, 0xFFF0, 0xFFFF, "Specials" },
952 /* Cut the table here because we only support 4-byte characters */
953 { FALSE, 0, 0, NULL },
956 void blocks_sync(void)
960 profile_write("blocks");
961 block = unicode_blocks;
962 while (block->name) {
963 profile_sync_short(&block->enabled);
969 void unicode_block_toggle(int block, int on)
971 int pos, active, training_block_saved;
973 if (block < 0 || unicode_blocks[block].enabled == on)
975 unicode_blocks[block].enabled = on;
976 active = gtk_combo_box_get_active(GTK_COMBO_BOX(blocks_combo));
977 pos = block_unicode_to_combo(block);
978 training_block_saved = training_block;
980 gtk_combo_box_remove_text(GTK_COMBO_BOX(blocks_combo), pos);
982 gtk_combo_box_insert_text(GTK_COMBO_BOX(blocks_combo), pos,
983 unicode_blocks[block].name);
984 update_enabled_samples();
985 if ((!on && block <= training_block_saved) || active < 0)
986 gtk_combo_box_set_active(GTK_COMBO_BOX(blocks_combo),
987 active > 0 ? active - 1 : 0);
989 /* Are we out of blocks? */
990 if (gtk_combo_box_get_active(GTK_COMBO_BOX(blocks_combo)) < 0) {
997 Start-up message dialog
1000 #define WELCOME_MSG "You are either starting " PACKAGE_NAME " for the first " \
1001 "time or have not yet created any training samples.\n\n" \
1002 PACKAGE_NAME " requires accurate training samples of " \
1003 "your characters before it can work.\n\n" \
1004 PACKAGE_NAME " will now enter training mode. " \
1005 "Carefully draw each character in its cell and then " \
1006 "press the 'Train' button."
1008 void startup_splash_show(void)
1012 dialog = gtk_message_dialog_new(GTK_WINDOW(window),
1013 GTK_DIALOG_DESTROY_WITH_PARENT |
1014 GTK_DIALOG_MODAL, GTK_MESSAGE_INFO,
1016 "Welcome to " PACKAGE_STRING "!");
1017 gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1019 gtk_window_set_title(GTK_WINDOW(dialog),
1020 "Welcome to " PACKAGE_NAME "!");
1021 gtk_dialog_run(GTK_DIALOG(dialog));
1022 gtk_widget_destroy(dialog);
1024 /* Press in the training button for the user */
1025 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(train_button), TRUE);