Initial check-in
[him-cellwriter] / src / window.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 "keys.h"
26 #include <memory.h>
27 #include <X11/Xatom.h>
28 #include <X11/Xlib.h>
29 #include <gdk/gdkx.h>
30
31 #include "hildon-im-ui.h"
32
33 /* options.c */
34 void options_dialog_open(void);
35
36 /* recognize.c */
37 void update_enabled_samples(void);
38
39 /* cellwidget.c */
40 extern int training, cell_width, cell_height, cell_cols_pref;
41
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);
52
53 extern HildonIMUI * ui;
54
55 /* main.c */
56 extern int keyboard_only;
57
58 /*
59         Main window
60 */
61
62 GtkWidget *window;
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;
69
70 /* Tab XPM image */
71 static char *tab_xpm[] =
72 {
73         "7 4 2 1",
74         "       c None",
75         ".      c #000000",
76         ".......",
77         " ..... ",
78         "  ...  ",
79         "   .   "
80 };
81
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;
89
90 static void toggle_button_labels(int on)
91 {
92         static int labels_off;
93
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), "");
107         }
108         labels_off = !on;
109 }
110
111 void window_pack(void)
112 {
113         cell_widget_pack();
114         toggle_button_labels(window_button_labels);
115         if (training)
116                 gtk_widget_show(train_label_frame);
117 }
118
119 void window_update_colors(void)
120 {
121         int keys_changed;
122
123         if (cell_widget_update_colors() || keys_changed)
124                 cell_widget_render();
125 }
126
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. */
134 {
135         static guint32 struts[12];
136         guint32 new2 = 0, new3 = 0, new9 = 0, new11 = 0;
137         GdkAtom atom_strut, atom_strut_partial, cardinal;
138
139         if (!window || !window->window || !window_struts)
140                 return;
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;
147         }
148         if (new2 == struts[2] && new3 == struts[3] &&
149             new9 == struts[9] && new11 == struts[11])
150                 return;
151         trace("top=%d (%d) bottom=%d (%d)", new2, new9, new3, new11);
152         struts[2] = new2;
153         struts[3] = new3;
154         struts[9] = new9;
155         struts[11] = 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);
164 }
165
166 static void set_geometry_hints(void)
167 {
168         GdkGeometry geometry;
169
170         geometry.min_width = -1;
171         geometry.min_height = -1;
172         geometry.max_width = -1;
173         geometry.max_height = -1;
174
175         /* Use window geometry to force the window to be as large as the
176            screen */
177         if (window_docked)
178                 geometry.max_width = geometry.min_width = screen_width;
179
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);
183
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()
189            movement code. */
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);*/
195 }
196
197 static void docked_move_resize(void)
198 {
199         GdkScreen *screen;
200         int y = 0;
201
202         if (!window_docked)
203                 return;
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);
209         cell_widget_pack();
210         trace("y=%d", y);
211 }
212
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 */
216 {
217         GdkRectangle new_frame = {0, 0, 0, 0};
218         GdkScreen *screen;
219         GdkDisplay *display;
220         int screen_w, screen_h, height_change, label_w;
221
222         if (!window || !window->window)
223                 return FALSE;
224         display = gtk_widget_get_display(window);
225
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);
231
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);
236
237         /* Docked windows have special placing requirements */
238         height_change = new_frame.height - window_frame.height;
239         if (window_docked) {
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();
247                 }
248                 update_struts();
249                 return FALSE;
250         }
251         screen_width = screen_w;
252         screen_height = screen_h;
253
254         /* Do nothing on the first configure */
255         if (window_frame.height <= 1) {
256                 window_frame = new_frame;
257                 return FALSE;
258         }
259
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;
264         else
265                 height_change = 0;
266
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)
273                 window_frame.x = 0;
274         if (window_frame.y < 0)
275                 window_frame.y = 0;
276
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
283            change. */
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);
291         } else
292                 window_frame = new_frame;
293
294         return FALSE;
295 }
296
297 void window_set_docked(int mode)
298 {
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();
308         cell_widget_pack();
309
310         /* Restore the old window position */
311         if (!mode) {
312                 update_struts();
313                 window_frame = window_frame_saved;
314                 gtk_window_move(GTK_WINDOW(window), window_frame.x,
315                                 window_frame.y);
316                 trace("moving to (%d, %d)", window_frame.x, window_frame.y);
317         }
318
319         /* Move the window into docked position */
320         else
321                 docked_move_resize();
322 }
323
324 void train_button_toggled(void)
325 {
326         if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(train_button))) {
327                 cell_widget_train();
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);
335         } else {
336           save_profile();
337                 cell_widget_clear();
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);
345         }
346 }
347
348 static int block_combo_to_unicode(int block)
349 /* Find the Combo Box index's unicode block */
350 {
351         int i, pos;
352
353         for (i = 0, pos = 0; unicode_blocks[i].name; i++)
354                 if (unicode_blocks[i].enabled && ++pos > block)
355                         break;
356         return i;
357 }
358
359 static int block_unicode_to_combo(int block)
360 /* Find the Unicode block's combo box position */
361 {
362         int i, pos;
363
364         for (i = 0, pos = 0; i < block && unicode_blocks[i].name; i++)
365                 if (unicode_blocks[i].enabled)
366                         pos++;
367         return pos;
368 }
369
370 static void blocks_combo_changed(void)
371 {
372         int pos;
373
374         pos = gtk_combo_box_get_active(GTK_COMBO_BOX(blocks_combo));
375         training_block = block_combo_to_unicode(pos);
376         if (training)
377                 cell_widget_train();
378 }
379
380 static GtkWidget *create_blocks_combo(void)
381 {
382         GtkWidget *event_box;
383         UnicodeBlock *block;
384
385         if (blocks_combo)
386                 gtk_widget_destroy(blocks_combo);
387         blocks_combo = gtk_combo_box_new_text();
388         block = unicode_blocks;
389         while (block->name) {
390                 if (block->enabled)
391                         gtk_combo_box_append_text(GTK_COMBO_BOX(blocks_combo),
392                                                   block->name);
393                 block++;
394         }
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);
400
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);
406
407         return event_box;
408 }
409
410 void window_toggle(void)
411 {
412         if (GTK_WIDGET_VISIBLE(window)) {
413                 gtk_widget_hide(window);
414                 window_shown = FALSE;
415
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 */
420         } else {
421                 gtk_widget_show(window);
422                 window_shown = TRUE;
423         }
424 }
425
426 void window_show(void)
427 {
428         if (!(GTK_WIDGET_VISIBLE(window)))
429                 window_toggle();
430 }
431
432 void window_hide(void)
433 {
434         if (GTK_WIDGET_VISIBLE(window))
435                 window_toggle();
436 }
437
438 gboolean window_close(void)
439 {
440   window_hide();
441         return FALSE;
442 }
443
444 static void window_style_set(GtkWidget *w)
445 {
446         GdkColor train_label_bg = RGB_TO_GDKCOLOR(255, 255, 200),
447                  train_label_fg = RGB_TO_GDKCOLOR(0, 0, 0);
448
449         /* The training label color is taken from tooltips */
450         if (!train_label)
451                 return;
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);
455 #endif
456         gtk_widget_modify_bg(train_label_frame, GTK_STATE_NORMAL,
457                              &train_label_bg);
458         gtk_widget_modify_bg(train_label_box, GTK_STATE_NORMAL,
459                              &train_label_bg);
460         gtk_widget_modify_fg(train_label, GTK_STATE_NORMAL,
461                              &train_label_fg);
462         gtk_widget_modify_fg(blocks_combo, GTK_STATE_NORMAL,
463                              &train_label_fg);
464 }
465
466 static void button_set_image_xpm(GtkWidget *button, char **xpm)
467 /* Creates a button with an XPM icon */
468 {
469         GdkPixmap *pixmap;
470         GdkBitmap *mask;
471         GtkWidget *image;
472
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);
478 }
479
480 void cell_widget_insert_surrounding_string();
481
482 static void insert_button_clicked(void)
483 {
484   if(FALSE){
485     if (cell_widget_insert()) {
486       history_valid = TRUE;
487       gtk_widget_set_sensitive(buffer_button, TRUE);
488     }
489   }else{
490     cell_widget_insert_surrounding_string();
491     cell_widget_clear();
492   }
493
494         if(ui){
495           hildon_im_ui_restore_previous_mode(ui);
496
497           window_hide();
498         }
499 }
500
501
502 static void return_button_clicked(void)
503 {
504   if(FALSE){
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");
510     }
511   }else{
512     cell_widget_insert_surrounding_string();
513     cell_widget_clear();
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");
517   }
518
519
520   if(ui){
521     hildon_im_ui_restore_previous_mode(ui);
522     
523     window_hide();
524   }
525 }
526
527 static void cancel_button_clicked(void){
528   cell_widget_clear();
529   if(ui){
530     hildon_im_ui_restore_previous_mode(ui);
531     window_hide();
532   }  
533 }
534
535 static void buffer_button_pressed(void)
536 {
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),
540                                              TRUE);
541         }
542 }
543
544 static void print_window_xid(GtkWidget *widget)
545 {
546         g_print("%d\n", (unsigned int)GDK_WINDOW_XID(widget->window));
547 }
548
549 void window_create(GtkWidget *parent)
550 /* Create the main window and child widgets */
551 {
552         GtkWidget *widget, *window_vbox, *image;
553         GdkScreen *screen;
554
555         /* Create the window or plug */
556         if (!parent)
557           window = !window_embedded ? gtk_window_new(GTK_WINDOW_TOPLEVEL) :
558                                       gtk_plug_new(0);
559         else
560           window = parent;
561
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);
572
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);*/
577
578         /* Tooltips */
579         tooltips = gtk_tooltips_new();
580         gtk_tooltips_enable(tooltips);
581
582         /* Root box */
583         window_vbox = gtk_vbox_new(FALSE, 0);
584         gtk_widget_show(window_vbox);
585
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);
591
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);
600
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,
608                            FALSE, FALSE, 0);
609
610         /* Cell widget */
611         cell_widget = cell_widget_new();
612         gtk_box_pack_start(GTK_BOX(window_vbox), cell_widget, TRUE, TRUE, 2);
613         if (!keyboard_only)
614                 gtk_widget_show_all(cell_widget);
615
616         /* Bottom box */
617         bottom_box = gtk_hbox_new(FALSE, 0);
618
619         /* Train button */
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",
630                              NULL);
631
632         /* Setup button */
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",
643                              NULL);
644
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);
648
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);
653
654         /* Clear button */
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",
664                              NULL);
665
666         /* Cancel button */
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);
678         /* Enter button */
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);
690
691         /* Insert button */
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);
703
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);
715
716         /* Pack the regular bottom box */
717         gtk_box_pack_start(GTK_BOX(window_vbox), bottom_box, FALSE, FALSE, 0);
718         if (!keyboard_only)
719                 gtk_widget_show_all(bottom_box);
720
721         /* Update button labels */
722         toggle_button_labels(window_button_labels);
723
724         /* Set window style */
725         window_style_set(window);
726
727         if (window_embedded) {
728
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);
733
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);
737
738                 gtk_container_add(GTK_CONTAINER(window), window_vbox);
739                 gtk_widget_show(window);
740                 return;
741         }
742
743         /* Non-embedded window configuration */
744         gtk_container_add(GTK_CONTAINER(window), window_vbox);
745
746         if(!parent){
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));
755
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;
761           
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,
769                           window_frame.y);
770
771           /* Set the window size */
772           if (window_force_docked >= WINDOW_UNDOCKED)
773             window_docked = window_force_docked;
774           if (window_docked) {
775             int mode;
776             
777             mode = window_docked;
778             window_docked = WINDOW_UNDOCKED;
779             window_set_docked(mode);
780           }
781           
782           /* Show window */
783           if (window_force_hide)
784             window_shown = FALSE;
785           else if (window_force_show)
786             window_shown = TRUE;
787           if (window_shown)
788             gtk_widget_show(window);
789         }
790 }
791         
792 void window_sync(void)
793 /* Sync data with profile, do not change item order! */
794 {
795         profile_write("window");
796
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);
801         } else {
802                 profile_sync_int(&window_frame.x);
803                 profile_sync_int(&window_frame.y);
804         }
805
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);
811         profile_write("\n");
812 }
813
814 void window_cleanup(void)
815 {
816 }
817
818 /*
819         Unicode blocks
820 */
821
822 /* This table is based on unicode-blocks.h from the gucharmap project */
823 UnicodeBlock unicode_blocks[] =
824 {
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" },
951
952         /* Cut the table here because we only support 4-byte characters */
953         { FALSE, 0,      0,      NULL },
954 };
955
956 void blocks_sync(void)
957 {
958         UnicodeBlock *block;
959
960         profile_write("blocks");
961         block = unicode_blocks;
962         while (block->name) {
963                 profile_sync_short(&block->enabled);
964                 block++;
965         }
966         profile_write("\n");
967 }
968
969 void unicode_block_toggle(int block, int on)
970 {
971         int pos, active, training_block_saved;
972
973         if (block < 0 || unicode_blocks[block].enabled == on)
974                 return;
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;
979         if (!on)
980                 gtk_combo_box_remove_text(GTK_COMBO_BOX(blocks_combo), pos);
981         else
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);
988
989         /* Are we out of blocks? */
990         if (gtk_combo_box_get_active(GTK_COMBO_BOX(blocks_combo)) < 0) {
991                 training_block = -1;
992                 cell_widget_train();
993         }
994 }
995
996 /*
997         Start-up message dialog
998 */
999
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."
1007
1008 void startup_splash_show(void)
1009 {
1010         GtkWidget *dialog;
1011
1012         dialog = gtk_message_dialog_new(GTK_WINDOW(window),
1013                                         GTK_DIALOG_DESTROY_WITH_PARENT |
1014                                         GTK_DIALOG_MODAL, GTK_MESSAGE_INFO,
1015                                         GTK_BUTTONS_OK,
1016                                         "Welcome to " PACKAGE_STRING "!");
1017         gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
1018                                                  WELCOME_MSG);
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);
1023
1024         /* Press in the training button for the user */
1025         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(train_button), TRUE);
1026 }
1027