Initial check-in
[him-cellwriter] / src / cellwidget.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 "keys.h"
27 #include <string.h>
28 #include <malloc.h>
29
30 #include "hildon-im-ui.h"
31
32 /* stroke.c */
33 void smooth_stroke(Stroke *s);
34 void simplify_stroke(Stroke *s);
35
36 /* cellwidget.c */
37 int cell_widget_scrollbar_width(void);
38 static void start_timeout(void);
39 static void show_context_menu(int button, int time);
40 static void stop_drawing(void);
41
42 /*
43         Cells
44 */
45
46 #define ALTERNATES 5
47 #define CELL_BASELINE (cell_height / 3)
48 #define CELL_BORDER (cell_height / 12)
49
50 /* Msec of no mouse motion before a cell is finished */
51 #define MOTION_TIMEOUT 500
52
53 /* Cell flags */
54 #define CELL_SHOW_INK   0x01
55 #define CELL_DIRTY      0x02
56 #define CELL_VERIFIED   0x04
57 #define CELL_SHIFTED    0x08
58
59 struct Cell {
60         Sample sample, *alts[ALTERNATES];
61         gunichar2 ch;
62         int alt_used[ALTERNATES];
63         char flags, alt_ratings[ALTERNATES];
64 };
65
66 /* Cell preferences */
67 int cell_width = 40, cell_height = 70, cell_cols_pref = 12, cell_rows_pref = 4,
68     enable_cairo = TRUE, training = FALSE, train_on_input = TRUE,
69     right_to_left = FALSE, keyboard_enabled = TRUE, xinput_enabled = FALSE;
70
71 /* Statistics */
72 int corrections = 0, rewrites = 0, characters = 0, inputs = 0;
73
74 /* Colors */
75 GdkColor custom_active_color = RGB_TO_GDKCOLOR(255, 255, 255),
76          custom_inactive_color = RGB_TO_GDKCOLOR(212, 222, 226),
77          custom_ink_color = RGB_TO_GDKCOLOR(0, 0, 0),
78          custom_select_color = RGB_TO_GDKCOLOR(204, 0, 0);
79 static GdkColor color_active, color_inactive, color_ink, color_select;
80
81 static Cell *cells = NULL, *cells_saved = NULL;
82 static GtkWidget *drawing_area = NULL, *training_menu, *scrollbar;
83 static GdkPixmap *pixmap = NULL;
84 static GdkGC *pixmap_gc = NULL;
85 static GdkColor color_bg, color_bg_dark;
86 static cairo_t *cairo = NULL;
87 static PangoContext *pango = NULL;
88 static PangoFontDescription *pango_font_desc = NULL;
89 static gunichar2 *history[HISTORY_MAX];
90 static int cell_cols, cell_rows, cell_row_view = 0, current_cell = -1, old_cc,
91            cell_cols_saved, cell_rows_saved, cell_row_view_saved,
92            timeout_source,
93            drawing = FALSE, inserting = FALSE, eraser = FALSE, invalid = FALSE,
94            potential_insert = FALSE, potential_hold = FALSE, cross_out = FALSE,
95            show_keys = TRUE, is_clear = TRUE, keys_dirty = FALSE;
96 static double cursor_x, cursor_y;
97
98 static void cell_coords(int cell, int *px, int *py)
99 /* Get the int position of a cell from its index */
100 {
101         int cell_y, cell_x;
102
103         cell -= cell_row_view * cell_cols;
104         cell_y = cell / cell_cols;
105         cell_x = cell - cell_y * cell_cols;
106         *px = (!right_to_left ? cell_x * cell_width :
107                                 (cell_cols - cell_x - 1) * cell_width) + 1;
108         *py = cell_y * cell_height + 1;
109 }
110
111 static void set_pen_color(Sample *sample, int cell)
112 /* Selects the pen color depending on if the sample being drawn is the input
113    or the template sample */
114 {
115         if (sample == input || sample == &cells[cell].sample)
116                 cairo_set_source_gdk_color(cairo, &color_ink, 1.);
117         else
118                 cairo_set_source_gdk_color(cairo, &color_select, 1.);
119 }
120
121 static void render_point(Sample *sample, int cell, int stroke, Vec2 *offset)
122 /* Draw a single point stroke */
123 {
124         double x, y, radius;
125         int cx, cy;
126
127         if (!pixmap || stroke < 0 || !sample || stroke >= sample->len ||
128             sample->strokes[stroke]->len < 1)
129                 return;
130
131         /* Apply offset */
132         x = sample->strokes[stroke]->points[0].x;
133         y = sample->strokes[stroke]->points[0].y;
134         if (offset) {
135                 x += offset->x;
136                 y += offset->y;
137         }
138
139         /* Unscale coordinates */
140         cell_coords(cell, &cx, &cy);
141         x = cx + cell_width / 2 + x * cell_height / SCALE;
142         y = cy + cell_height / 2 + y * cell_height / SCALE;
143
144         /* Draw a dot with cairo */
145         cairo_new_path(cairo);
146         radius = cell_height / 33.;
147         cairo_arc(cairo, x, y, radius > 1. ? radius : 1., 0., 2 * M_PI);
148         set_pen_color(sample, cell);
149         cairo_fill(cairo);
150
151         gtk_widget_queue_draw_area(drawing_area, x - radius - 0.5,
152                                    y - radius - 0.5, radius * 2 + 0.5,
153                                    radius * 2 + 0.5);
154 }
155
156 static void render_segment(Sample *sample, int cell, int stroke, int seg,
157                            Vec2 *offset)
158 /* Draw a segment of the stroke
159    FIXME since the segments are not properly connected according to Cairo,
160          there is a bit of missing value at the segment connection points */
161 {
162         double pen_width, x1, x2, y1, y2;
163         int xmin, xmax, ymin, ymax, cx, cy, pen_range;
164
165         if (!cairo || stroke < 0 || !sample || stroke >= sample->len ||
166             seg < 0 || seg >= sample->strokes[stroke]->len - 1)
167                 return;
168
169         x1 = sample->strokes[stroke]->points[seg].x;
170         x2 = sample->strokes[stroke]->points[seg + 1].x;
171         y1 = sample->strokes[stroke]->points[seg].y;
172         y2 = sample->strokes[stroke]->points[seg + 1].y;
173
174         /* Apply offset */
175         if (offset) {
176                 x1 += offset->x;
177                 y1 += offset->y;
178                 x2 += offset->x;
179                 y2 += offset->y;
180         }
181
182         /* Unscale coordinates */
183         cell_coords(cell, &cx, &cy);
184         x1 = cx + cell_width / 2 + x1 * cell_height / SCALE;
185         x2 = cx + cell_width / 2 + x2 * cell_height / SCALE;
186         y1 = cy + cell_height / 2 + y1 * cell_height / SCALE;
187         y2 = cy + cell_height / 2 + y2 * cell_height / SCALE;
188
189         /* Find minimum and maximum x and y */
190         if (x1 > x2) {
191                 xmax = x1 + 0.9999;
192                 xmin = x2;
193         } else {
194                 xmin = x1;
195                 xmax = x2 + 0.9999;
196         }
197         if (y1 > y2) {
198                 ymax = y1 + 0.9999;
199                 ymin = y2;
200         } else {
201                 ymin = y1;
202                 ymax = y2 + 0.9999;
203         }
204
205         /* Draw the new segment using Cairo */
206         cairo_new_path(cairo);
207         cairo_move_to(cairo, x1, y1);
208         cairo_line_to(cairo, x2, y2);
209         set_pen_color(sample, cell);
210         pen_width = cell_height / 33.;
211         if (pen_width < 1.)
212                 pen_width = 1.;
213         cairo_set_line_width(cairo, pen_width);
214         cairo_stroke(cairo);
215
216         /* Dirty only the new segment */
217         pen_range = 2 * pen_width + 0.9999;
218         gtk_widget_queue_draw_area(drawing_area, xmin - pen_range,
219                                    ymin - pen_range,
220                                    xmax - xmin + pen_range + 1,
221                                    ymax - ymin + pen_range + 1);
222 }
223
224 static void render_sample(Sample *sample, int cell)
225 /* Render the ink from a sample in a cell */
226 {
227         Vec2 sc_to_ic;
228         int i, j;
229
230         if (!sample)
231                 return;
232
233         /* Center stored samples on input */
234         if (sample != &cells[cell].sample)
235                 center_samples(&sc_to_ic, sample, &cells[cell].sample);
236         else
237                 vec2_set(&sc_to_ic, 0., 0.);
238
239         for (i = 0; i < sample->len; i++)
240                 if (sample->strokes[i]->len <= 1 ||
241                     sample->strokes[i]->spread < DOT_SPREAD)
242                         render_point(sample, cell, i, &sc_to_ic);
243                 else
244                         for (j = 0; j < sample->strokes[i]->len - 1; j++)
245                                 render_segment(sample, cell, i, j, &sc_to_ic);
246 }
247
248 static int cell_offscreen(int cell)
249 {
250         int rows, cols;
251
252         cols = cell_cols;
253         rows = cell_rows < cell_rows_pref ? cell_rows : cell_rows_pref;
254         return cell < cell_row_view * cols ||
255                cell >= (cell_row_view + rows) * cols;
256 }
257
258 static void dirty_cell(int cell)
259 {
260         if (!cell_offscreen(cell))
261                 cells[cell].flags |= CELL_DIRTY;
262 }
263
264 static void dirty_all(void)
265 {
266         int i, rows;
267
268         rows = cell_row_view + cell_rows_pref > cell_rows ?
269                cell_rows : cell_row_view + cell_rows_pref;
270         for (i = cell_cols * cell_row_view; i < rows * cell_cols; i++)
271                 cells[i].flags |= CELL_DIRTY;
272 }
273
274 static void render_cell(int i)
275 {
276         cairo_pattern_t *pattern;
277         GdkColor color, *base_color;
278         Cell *pc;
279         int x, y, active, cols, samples = 0;
280
281         if (!cairo || !pixmap || !pixmap_gc || cell_offscreen(i))
282                 return;
283         pc = cells + i;
284         cell_coords(i, &x, &y);
285         if (training) {
286                 samples = char_trained(pc->ch);
287                 active = pc->ch && (samples > 0 ||
288                                     (current_cell == i && input &&
289                                      !invalid && input->len));
290         } else
291                 active = pc->ch || (current_cell == i && !inserting &&
292                                     !invalid && input && input->len);
293         base_color = active ? &color_active : &color_inactive;
294
295         /* Fill above baseline */
296         gdk_gc_set_rgb_fg_color(pixmap_gc, base_color);
297         gdk_draw_rectangle(pixmap, pixmap_gc, TRUE, x, y, cell_width,
298                                 cell_height - CELL_BASELINE);
299
300         /* Fill baseline */
301         highlight_gdk_color(base_color, &color, 0.1);
302         gdk_gc_set_rgb_fg_color(pixmap_gc, &color);
303         gdk_draw_rectangle(pixmap, pixmap_gc, TRUE, x, y + cell_height -
304                            CELL_BASELINE, cell_width, CELL_BASELINE);
305
306         /* Cairo clip region */
307         cairo_reset_clip(cairo);
308         cairo_rectangle(cairo, x, y, cell_width, cell_height);
309         cairo_clip(cairo);
310
311         /* Separator line */
312         cols = cell_cols;
313         if ((!right_to_left && i % cell_cols) ||
314             (right_to_left && i % cell_cols != cols - 1)) {
315                 highlight_gdk_color(base_color, &color, 0.5);
316                 pattern = cairo_pattern_create_linear(x, y, x, y + cell_height);
317                 cairo_pattern_add_gdk_color_stop(pattern, 0.0, &color, 0.);
318                 cairo_pattern_add_gdk_color_stop(pattern, 0.5, &color, 1.);
319                 cairo_pattern_add_gdk_color_stop(pattern, 1.0, &color, 0.);
320                 cairo_set_source(cairo, pattern);
321                 cairo_set_line_width(cairo, 0.5);
322                 cairo_move_to(cairo, x + 0.5, y);
323                 cairo_line_to(cairo, x + 0.5, y + cell_height - 1);
324                 cairo_stroke(cairo);
325                 cairo_pattern_destroy(pattern);
326         }
327
328         /* Draw ink if shown */
329         if ((cells[i].ch && cells[i].flags & CELL_SHOW_INK) ||
330             (current_cell == i && input && input->len)) {
331                 int j;
332
333                 render_sample(&cells[i].sample, i);
334                 if (cells[i].ch)
335                         for (j = 0; j < ALTERNATES && cells[i].alts[j]; j++)
336                                 if (sample_valid(cells[i].alts[j],
337                                                  cells[i].alt_used[j]) &&
338                                     cells[i].alts[j]->ch == cells[i].ch) {
339                                         render_sample(cells[i].alts[j], i);
340                                         break;
341                                 }
342         }
343
344         /* Draw letter if recognized or training */
345         else if (pc->ch && (current_cell != i || !input || !input->len)) {
346                 PangoLayout *layout;
347                 PangoRectangle ink_ext, log_ext;
348                 char string[6] = { 0, 0, 0, 0, 0, 0 };
349
350                 /* Training color is determined by how well a character is
351                    trained */
352                 if (training) {
353                         if (samples)
354                                 highlight_gdk_color(&color_ink, &color,
355                                                     0.5 - ((double)samples) /
356                                                     samples_max / 2.);
357                         else
358                                 highlight_gdk_color(&color_inactive,
359                                                     &color, 0.2);
360                 }
361
362                 /* Use ink color unless this is a questionable match */
363                 else {
364                         color = color_ink;
365                         if (!(pc->flags & CELL_VERIFIED) && pc->alts[0] &&
366                             pc->alts[1] && pc->ch == pc->alts[0]->ch &&
367                             pc->alt_ratings[0] - pc->alt_ratings[1] <= 10)
368                                 color = color_select;
369                 }
370
371                 cairo_set_source_gdk_color(cairo, &color, 1.);
372                 layout = pango_layout_new(pango);
373                 cairo_move_to(cairo, x, y);
374                 g_unichar_to_utf8(pc->ch, string);
375                 pango_layout_set_text(layout, string, 6);
376                 pango_layout_set_font_description(layout, pango_font_desc);
377                 pango_layout_get_pixel_extents(layout, &ink_ext, &log_ext);
378                 cairo_rel_move_to(cairo,
379                                   cell_width / 2 - log_ext.width / 2, 2);
380                 pango_cairo_show_layout(cairo, layout);
381                 g_object_unref(layout);
382         }
383
384         /* Insertion arrows */
385         if (!invalid && inserting &&
386             (current_cell == i || current_cell == i + 1)) {
387                 double width, stem, height;
388
389                 cairo_set_source_gdk_color(cairo, &color_select, 1.);
390                 width = CELL_BORDER;
391                 stem = CELL_BORDER / 2;
392                 height = CELL_BORDER;
393                 if ((!right_to_left && current_cell == i) ||
394                     (right_to_left && current_cell == i + 1)) {
395
396                         /* Top right arrow */
397                         cairo_move_to(cairo, x, y + 1);
398                         cairo_line_to(cairo, x + stem, y + 1);
399                         cairo_line_to(cairo, x + stem, y + height);
400                         cairo_line_to(cairo, x + width, y + height);
401                         cairo_line_to(cairo, x, y + height * 2);
402                         cairo_close_path(cairo);
403                         cairo_fill(cairo);
404
405                         /* Bottom right arrow */
406                         cairo_move_to(cairo, x, y + cell_height - 1);
407                         cairo_line_to(cairo, x + stem, y + cell_height - 1);
408                         cairo_line_to(cairo, x + stem,
409                                       y + cell_height - height);
410                         cairo_line_to(cairo, x + width,
411                                       y + cell_height - height);
412                         cairo_line_to(cairo, x, y + cell_height - height * 2);
413                         cairo_close_path(cairo);
414                         cairo_fill(cairo);
415
416                 } else if ((!right_to_left && current_cell == i + 1) ||
417                            (right_to_left && current_cell == i)) {
418                         double ox;
419
420                         ox = i % cell_cols == cell_cols - 1 ? 0. : 1.;
421
422                         /* Top left arrow */
423                         cairo_move_to(cairo, x + cell_width + ox, y + 1);
424                         cairo_line_to(cairo, x + cell_width - stem + ox,
425                                       y + 1);
426                         cairo_line_to(cairo, x + cell_width - stem + ox,
427                                       y + height);
428                         cairo_line_to(cairo, x + cell_width - width + ox,
429                                       y + height);
430                         cairo_line_to(cairo, x + cell_width + ox,
431                                       y + height * 2);
432                         cairo_close_path(cairo);
433                         cairo_fill(cairo);
434
435                         /* Bottom left arrow */
436                         cairo_move_to(cairo, x + cell_width + ox,
437                                       y + cell_height - 1);
438                         cairo_line_to(cairo, x + cell_width - stem + ox,
439                                       y + cell_height - 1);
440                         cairo_line_to(cairo, x + cell_width - stem + ox,
441                                       y + cell_height - height);
442                         cairo_line_to(cairo, x + cell_width - width + ox,
443                                       y + cell_height - height);
444                         cairo_line_to(cairo, x + cell_width + ox,
445                                       y + cell_height - height * 2);
446                         cairo_close_path(cairo);
447                         cairo_fill(cairo);
448
449                 }
450         }
451
452         gtk_widget_queue_draw_area(drawing_area, x, y, cell_width, cell_height);
453         pc->flags &= ~CELL_DIRTY;
454
455 }
456
457 static void render_dirty(void)
458 /* Render cells marked dirty */
459 {
460         int i;
461
462         for (i = cell_row_view * cell_cols; i < cell_rows * cell_cols; i++)
463                 if (cells[i].flags & CELL_DIRTY)
464                         render_cell(i);
465 }
466
467 void cell_widget_render(void)
468 /* Render the cells */
469 {
470         int i, cols, rows, width, height;
471
472         if (!cairo || !pixmap || !pixmap_gc)
473                 return;
474
475         /* On-screen keyboard eats up some cells on the end */
476         cols = cell_cols;
477
478         /* Render cells */
479         for (i = cell_row_view * cols; i < cell_rows * cols; i++)
480                 render_cell(i);
481
482         /* Draw border */
483         rows = cell_rows < cell_rows_pref ? cell_rows : cell_rows_pref;
484         width = cell_width * cols + 1;
485         height = cell_height * rows + 1;
486         gdk_gc_set_rgb_fg_color(pixmap_gc, &color_bg_dark);
487         if (!right_to_left)
488                 gdk_draw_rectangle(pixmap, pixmap_gc, FALSE, 0, 0,
489                                    width, height);
490         else
491                 gdk_draw_rectangle(pixmap, pixmap_gc, FALSE,
492                                    drawing_area->allocation.width - width - 1,
493                                    0, width, height);
494
495         /* Fill extra space to the right */
496         gdk_gc_set_rgb_fg_color(pixmap_gc, &color_bg);
497         if (!right_to_left)
498                 gdk_draw_rectangle(pixmap, pixmap_gc, TRUE, width + 1, 0,
499                                    drawing_area->allocation.width - width,
500                                    height + 1);
501         else
502                 gdk_draw_rectangle(pixmap, pixmap_gc, TRUE, 0, 0,
503                                    drawing_area->allocation.width - width - 1,
504                                    height + 1);
505
506         /* Fill extra space below */
507         gdk_draw_rectangle(pixmap, pixmap_gc, TRUE, 0, height + 1,
508                            drawing_area->allocation.width,
509                            drawing_area->allocation.height - height + 1);
510
511         /* Dirty the entire drawing area */
512         gtk_widget_queue_draw(drawing_area);
513 }
514
515 static void clear_cell(int i)
516 {
517         Cell *cell;
518
519         cell = cells + i;
520         cell->flags = 0;
521         if (cell->ch || i == current_cell) {
522                 if (i == current_cell)
523                         input = NULL;
524                 cell->flags |= CELL_DIRTY;
525         }
526         clear_sample(&cell->sample);
527         cell->ch = 0;
528         cell->alts[0] = NULL;
529 }
530
531 static void pad_cell(int cell)
532 {
533         int i;
534
535         /* Turn any blank cells behind the cell into spaces */
536         for (i = cell - 1; i >= 0 && !cells[i].ch; i--) {
537                 cells[i].ch = ' ';
538                 cells[i].flags |= CELL_DIRTY;
539         }
540 }
541
542 static void free_cells(void)
543 /* Free sample data */
544 {
545         int i;
546
547         if (!cells)
548                 return;
549         for (i = 0; i < cell_rows * cell_cols; i++)
550                 clear_cell(i);
551         g_free(cells);
552         cells = NULL;
553         input = NULL;
554 }
555
556 static void wrap_cells(int new_rows, int new_cols)
557 /* Word wrap cells */
558 {
559         Cell *new_cells;
560         int i, j, size, row, col, break_i = -1, break_j = -1;
561
562         /* Allocate and clear the new grid */
563         if (new_rows < 1)
564                 new_rows = 1;
565         size = new_rows * new_cols * sizeof (Cell);
566         new_cells = g_malloc0(size);
567
568         for (i = 0, j = 0, row = 0, col = 0; i < cell_rows * cell_cols; i++) {
569                 if (!cells[i].ch)
570                         continue;
571
572                 /* Break at non-alphanumeric characters */
573                 if (!g_unichar_isalnum(cells[i].ch)) {
574                         break_i = i;
575                         break_j = j;
576                 }
577
578                 if (col >= new_cols) {
579
580                         /* If we need to, allocate room for the new row */
581                         if (++row >= new_rows) {
582                                 size = ++new_rows * new_cols * sizeof (Cell);
583                                 new_cells = g_realloc(new_cells, size);
584                                 memset(new_cells + (new_rows - 1) * new_cols,
585                                        0, new_cols * sizeof (Cell));
586                         }
587
588                         /* Move any hanging words down to the next row */
589                         size = i - break_i - 1;
590                         if (size >= 0 && size < i - 1) {
591                                 memset(new_cells + break_j + 1, 0,
592                                        sizeof (Cell) * size);
593                                 i = break_i + 1;
594                                 break_i = -1;
595                         }
596                         col = 0;
597                         if (!cells[i].ch)
598                                 continue;
599                 }
600                 new_cells[j++] = cells[i];
601                 col++;
602         }
603
604         /* If we have filled the last row, we need to add a new row */
605         if (col >= new_cols && row >= new_rows - 1) {
606                 size = ++new_rows * new_cols * sizeof (Cell);
607                 new_cells = g_realloc(new_cells, size);
608                 memset(new_cells + (new_rows - 1) * new_cols, 0,
609                        new_cols * sizeof (Cell));
610         }
611
612         /* Only free the cell array, NOT the samples as we have copied the
613            Sample data over to the new cell array */
614         g_free(cells);
615         cells = new_cells;
616
617         /* Scroll the grid */
618         if (new_rows > cell_rows && new_rows > cell_rows_pref)
619                 cell_row_view += new_rows - cell_rows;
620
621         /* Do not let the row view look too far down */
622         if (cell_row_view + cell_rows_pref > new_rows) {
623                 cell_row_view = new_rows - cell_rows_pref;
624                 if (cell_row_view < 0)
625                         cell_row_view = 0;
626         }
627
628         cell_rows = new_rows;
629         cell_cols = new_cols;
630 }
631
632 static int set_size_request(int force)
633 /* Resize the drawing area if necessary */
634 {
635         int new_w, new_h, rows, resized;
636
637         new_w = cell_cols * cell_width + 2;
638         rows = cell_rows;
639         if (rows > cell_rows_pref)
640                 rows = cell_rows_pref;
641         new_h = rows * cell_height + 2;
642         resized = new_w != drawing_area->allocation.width ||
643                   new_h != drawing_area->allocation.height || force;
644         if (!resized)
645                 return FALSE;
646         gtk_widget_set_size_request(drawing_area, new_w, new_h);
647         return TRUE;
648 }
649
650 static int pack_cells(int new_rows, int new_cols)
651 /* Pack and position cells, resize widget and window when necessary.
652    Returns TRUE if the widget was resized in the process and can expect a
653    configure event in the near future. */
654 {
655         int i, rows, range, new_range;
656
657         /* Must have at least one row */
658         if (new_rows < 1)
659                 new_rows = 1;
660
661         /* Word wrapping will perform its own memory allocation */
662         if (!training && cells)
663                 wrap_cells(new_rows, new_cols);
664
665         else if (!cells || new_rows != cell_rows || new_cols != cell_cols) {
666
667                 /* Find minimum number of rows necessary */
668                 if (cells) {
669                         for (i = cell_rows * cell_cols - 1; i > 0; i--)
670                                 if (cells[i].ch)
671                                         break;
672                         rows = i / new_cols + 1;
673                         if (new_rows < rows)
674                                 new_rows = rows;
675                         new_range = new_rows * new_cols;
676
677                         /* If we have shrunk the grid, clear cells outside */
678                         range = cell_rows * cell_cols;
679                         for (i = new_range; i < range; i++)
680                                 clear_cell(i);
681                 } else {
682                         range = 0;
683                         new_range = new_rows * new_cols;
684                 }
685
686                 /* Allocate enough room, clear any new cells */
687                 cells = g_realloc(cells, new_rows * new_cols * sizeof (Cell));
688                 if (new_range > range)
689                         memset(cells + range, 0,
690                                (new_range - range) * sizeof (Cell));
691
692                 cell_rows = new_rows;
693                 cell_cols = new_cols;
694         }
695         dirty_all();
696
697         /* Update the scrollbar */
698         if (cell_rows <= cell_rows_pref) {
699                 cell_row_view = 0;
700                 gtk_widget_hide(scrollbar);
701         } else {
702                 GtkObject *adjustment;
703
704                 if (cell_row_view > cell_rows - cell_rows_pref)
705                         cell_row_view = cell_rows - cell_rows_pref;
706                 if (cell_row_view < 0)
707                         cell_row_view = 0;
708                 adjustment = gtk_adjustment_new(cell_row_view, 0, cell_rows, 1,
709                                                 cell_rows_pref, cell_rows_pref);
710                 gtk_range_set_adjustment(GTK_RANGE(scrollbar),
711                                          GTK_ADJUSTMENT(adjustment));
712                 gtk_widget_show(scrollbar);
713         }
714
715         return set_size_request(FALSE);
716 }
717
718 static void stop_timeout(void)
719 {
720         if (!timeout_source)
721                 return;
722         g_source_remove(timeout_source);
723         timeout_source = 0;
724 }
725
726 static void finish_cell(int cell)
727 {
728         stop_timeout();
729         if (cell < 0 || cell >= cell_rows * cell_cols ||
730             !input || input->len < 1)
731                 return;
732         cells[cell].flags |= CELL_DIRTY;
733
734         /* Train on the input */
735         if (training)
736                 train_sample(&cells[cell].sample, TRUE);
737
738         /* Recognize input */
739         else if (input && input->strokes[0] && input->strokes[0]->len) {
740                 Cell *pc = cells + cell;
741                 int i;
742
743                 /* Track stats */
744                 if (pc->ch && pc->ch != ' ')
745                         rewrites++;
746                 inputs++;
747
748                 old_cc = cell;
749                 recognize_sample(input, pc->alts, ALTERNATES);
750                 pc->ch = input->ch;
751                 pc->flags &= ~CELL_VERIFIED;
752                 if (pc->ch)
753                         pad_cell(cell);
754
755                 /* Copy the alternate ratings and usage stamps before they're
756                    overwritten by another call to recognize_sample() */
757                 for (i = 0; i < ALTERNATES && pc->alts[i]; i++) {
758                         pc->alt_ratings[i] = pc->alts[i]->rating;
759                         pc->alt_used[i] = pc->alts[i]->used;
760                 }
761
762                 /* Add a row if this is the last cell */
763                 if (cell == cell_rows * cell_cols - 1)
764                         pack_cells(0, cell_cols);
765         }
766
767         input = NULL;
768         drawing = FALSE;
769 }
770
771 static gboolean finish_timeout(void)
772 /* Motion timeout for finishing drawing a cell */
773 {
774         finish_cell(current_cell);
775         render_dirty();
776         timeout_source = 0;
777         start_timeout();
778         return FALSE;
779 }
780
781 static gboolean row_timeout(void)
782 /* Motion timeout for adding a row */
783 {
784         pack_cells(cell_rows + 1, cell_cols);
785         cell_widget_render();
786         timeout_source = 0;
787         return FALSE;
788 }
789
790 static int check_clear(void)
791 {
792         int i;
793
794         if (is_clear)
795                 return TRUE;
796         if (training || (input && input->len))
797                 return FALSE;
798         for (i = 0; i < cell_cols * cell_rows; i++)
799                 if (cells[i].ch)
800                         return FALSE;
801         return TRUE;
802 }
803
804 static gboolean is_clear_timeout(void)
805 /* Motion timeout for checking clear state */
806 {
807         timeout_source = 0;
808         if (is_clear || !check_clear())
809                 return FALSE;
810
811         /* Show the on-screen keyboard */
812         show_keys = keyboard_enabled;
813         is_clear = TRUE;
814
815         pack_cells(1, cell_cols);
816         cell_widget_render();
817         return FALSE;
818 }
819
820 static gboolean hold_timeout(void)
821 /* Motion timeout for popping up a hold-click context menu */
822 {
823         if (potential_hold) {
824                 potential_hold = FALSE;
825                 stop_drawing();
826                 show_context_menu(1, gtk_get_current_event_time());
827         }
828         timeout_source = 0;
829         return FALSE;
830 }
831
832 static void start_timeout(void)
833 /* If a timeout action is approriate for the current situation, start a
834    timeout */
835 {
836         GSourceFunc func = NULL;
837
838         if (potential_hold)
839                 return;
840         stop_timeout();
841         if (cross_out)
842                 return;
843
844         /* Events below are not triggered while drawing */
845         if (!drawing) {
846                 if (input)
847                         func = (GSourceFunc)finish_timeout;
848                 else if (!cells[cell_rows * cell_cols - 1].ch &&
849                          cells[cell_rows * cell_cols - 2].ch && !training)
850                         func = (GSourceFunc)row_timeout;
851                 else if (!is_clear && check_clear())
852                         func = (GSourceFunc)is_clear_timeout;
853         }
854
855         if (func)
856                 timeout_source = g_timeout_add(MOTION_TIMEOUT, func, NULL);
857 }
858
859 static void start_hold(void)
860 {
861         potential_hold = TRUE;
862         if (timeout_source)
863                 g_source_remove(timeout_source);
864         timeout_source = g_timeout_add(MOTION_TIMEOUT,
865                                        (GSourceFunc)hold_timeout, NULL);
866 }
867
868 void cell_widget_set_cursor(int recreate)
869 /* Set the drawing area cursor to a black box pen cursor or to a blank cursor
870    depending on which is appropriate */
871 {
872         static char bits[] = { 0xff, 0xff, 0xff };      /* Square cursor */
873                            /*{ 0x02, 0xff, 0x02 };*/    /* Cross cursor */
874         static GdkCursor *square;
875         GdkPixmap *pixmap;
876         GdkCursor *cursor;
877
878         /* Ink color changed, recreate cursor */
879         if (recreate) {
880                 if (square)
881                         gdk_cursor_unref(square);
882                 pixmap = gdk_bitmap_create_from_data(NULL, bits, 3, 3);
883                 square = gdk_cursor_new_from_pixmap(pixmap, pixmap,
884                                                     &color_ink,
885                                                     &color_ink, 1, 1);
886                 g_object_unref(pixmap);
887         }
888         cursor = square;
889
890         /* Eraser cursor */
891         if (eraser || cross_out) {
892                 GdkDisplay *display;
893
894                 display = gtk_widget_get_display(drawing_area);
895                 cursor = gdk_cursor_new_for_display(display, GDK_CIRCLE);
896         }
897
898         gdk_window_set_cursor(drawing_area->window,
899                               invalid || inserting ? NULL : cursor);
900 }
901
902 static void stop_drawing(void)
903 /* Ends the current stroke and applies various processing functions */
904 {
905         Stroke *stroke;
906
907         if (!drawing) {
908                 if (cross_out) {
909                         cross_out = FALSE;
910                         cell_widget_set_cursor(FALSE);
911                 }
912                 return;
913         }
914         drawing = FALSE;
915         if (!input || input->len >= STROKES_MAX)
916                 return;
917         stroke = input->strokes[input->len - 1];
918         smooth_stroke(stroke);
919         simplify_stroke(stroke);
920         process_stroke(stroke);
921         render_cell(current_cell);
922         render_sample(input, current_cell);
923         start_timeout();
924 }
925
926 static void erase_cell(int cell)
927 {
928         if (!training) {
929                 clear_cell(cell);
930                 render_dirty();
931         } else {
932                 untrain_char(cells[cell].ch);
933                 render_cell(cell);
934         }
935 }
936
937 static void check_cell(double x, double y, GdkDevice *device)
938 /* Check if we have changed to a different cell */
939 {
940         int cell_x, cell_y, cell, rem_x, rem_y,
941             old_inserting, old_invalid, old_eraser, old_cross_out;
942
943         /* Stop drawing first */
944         old_cross_out = cross_out;
945         if (drawing && !cross_out) {
946                 int dx, dy;
947
948                 /* Check if we have started the cross-out gesture */
949                 cell_coords(current_cell, &cell_x, &cell_y);
950                 cell_x += cell_width / 2;
951                 cell_y += cell_height / 2;
952                 dx = cell_x - x;
953                 dy = cell_y - y;
954                 if (dx < 0)
955                         dx = -dx;
956                 if (dy < 0)
957                         dy = -dy;
958                 if (dx < cell_width && dy < cell_height)
959                         return;
960
961                 cross_out = TRUE;
962                 drawing = FALSE;
963                 clear_sample(input);
964                 input = NULL;
965                 erase_cell(current_cell);
966         }
967
968         /* Is this the eraser tip? */
969         old_eraser = eraser;
970         eraser = device && device->source == GDK_SOURCE_ERASER;
971
972         /* Adjust for border */
973         x--;
974         y--;
975
976         /* Right-to-left mode inverts the x-axis */
977         if (right_to_left)
978                 x = cell_cols * cell_width - x - 1;
979
980         /* What cell are we hovering over? */
981         cell_y = y / cell_height + cell_row_view;
982         cell_x = x / cell_width;
983         cell = cell_cols * cell_y + cell_x;
984
985         /* Out of bounds or invalid cell */
986         old_invalid = invalid;
987         invalid = cell_x < 0 || cell_y < 0 || cell_x >= cell_cols ||
988                   cell_y >= cell_rows || cell_offscreen(cell) ||
989                   (training && !cells[cell].ch);
990
991         /* Are we in the insertion hotspot? */
992         rem_x = x - cell_x * cell_width;
993         rem_y = y - (cell_y - cell_row_view) * cell_height;
994         old_inserting = inserting;
995         inserting = FALSE;
996         if (!cross_out && !eraser && !invalid && !training && !input &&
997             (rem_y <= CELL_BORDER * 2 ||
998              rem_y > cell_height - CELL_BORDER * 2)) {
999                 if (rem_x <= CELL_BORDER + 1)
1000                         inserting = TRUE;
1001                 else if (cell < cell_rows * cell_cols - 1 &&
1002                          rem_x > cell_width - CELL_BORDER) {
1003                         inserting = TRUE;
1004                         cell++;
1005                 }
1006         }
1007
1008         /* Current cell has changed */
1009         old_cc = current_cell;
1010         if (current_cell != cell) {
1011                 current_cell = cell;
1012                 if (!cross_out)
1013                         finish_cell(old_cc);
1014         }
1015
1016         /* We have moved into or out of the insertion hotspot */
1017         if (old_inserting != inserting || old_cc != cell) {
1018                 if (old_inserting) {
1019                         dirty_cell(old_cc);
1020                         dirty_cell(old_cc - 1);
1021                 }
1022                 if (inserting) {
1023                         dirty_cell(current_cell);
1024                         dirty_cell(current_cell - 1);
1025                 }
1026         }
1027
1028         /* Update cursor if necessary */
1029         if (old_invalid != invalid || old_inserting != inserting ||
1030             old_eraser != eraser || old_cross_out != cross_out)
1031                 cell_widget_set_cursor(FALSE);
1032
1033         render_dirty();
1034 }
1035
1036 static void unclear(int render)
1037 /* Hides the on-screen keyboard and re-renders the cells.
1038    FIXME we only need to render dirty cells */
1039 {
1040         is_clear = FALSE;
1041         if (!show_keys)
1042                 return;
1043         show_keys = FALSE;
1044         if (render)
1045                 cell_widget_render();
1046 }
1047
1048 static void draw(double x, double y)
1049 {
1050         Stroke *s;
1051         int cx, cy;
1052
1053         if (current_cell < 0)
1054                 return;
1055
1056         /* Hide the on-screen keyboard */
1057         unclear(TRUE);
1058
1059         /* New character */
1060         if (!input || !input->len) {
1061                 clear_sample(&cells[current_cell].sample);
1062                 cells[current_cell].alts[0] = NULL;
1063                 input = &cells[current_cell].sample;
1064                 cells[current_cell].sample.ch = cells[current_cell].ch;
1065         }
1066
1067         /* Allocate a new stroke if we aren't already drawing */
1068         s = input->strokes[input->len - 1];
1069         if (!drawing) {
1070                 if (input->len >= STROKES_MAX)
1071                         return;
1072                 s = input->strokes[input->len++]= stroke_new(0);
1073                 drawing = TRUE;
1074                 if (input->len == 1)
1075                         render_cell(current_cell);
1076         }
1077
1078         /* Check bounds */
1079         cell_coords(current_cell, &cx, &cy);
1080
1081         /* Normalize the input */
1082         x = (x - cx - cell_width / 2) * SCALE / cell_height;
1083         y = (y - cy - cell_height / 2) * SCALE / cell_height;
1084
1085         draw_stroke(&input->strokes[input->len - 1], x, y);
1086 }
1087
1088 static void insert_cell(int cell)
1089 {
1090         int i;
1091
1092         /* Find a blank to consume */
1093         for (i = cell; i < cell_rows * cell_cols; i++)
1094                 if (!cells[i].ch)
1095                         break;
1096
1097         /* Insert a row if necessary */
1098         if (i >= cell_rows * cell_cols - 1) {
1099                 cells = g_realloc(cells,
1100                                   ++cell_rows * cell_cols * sizeof (Cell));
1101                 memset(cells + (cell_rows - 1) * cell_cols, 0,
1102                        cell_cols * sizeof (Cell));
1103                 if (cell_rows > cell_rows_pref)
1104                         cell_row_view++;
1105         }
1106
1107         if (i > cell)
1108                 memmove(cells + cell + 1, cells + cell,
1109                         (i - cell) * sizeof (Cell));
1110         cells[cell].ch = ' ';
1111         cells[cell].alts[0] = NULL;
1112         cells[cell].sample.len = 0;
1113         cells[cell].sample.ch = 0;
1114         pad_cell(cell);
1115         pack_cells(0, cell_cols);
1116         unclear(FALSE);
1117         cell_widget_render();
1118 }
1119
1120 static void delete_cell(int cell)
1121 {
1122         int i, rows;
1123
1124         clear_cell(cell);
1125         memmove(cells + cell, cells + cell + 1,
1126                 (cell_rows * cell_cols - cell - 1) * sizeof (Cell));
1127
1128         /* Delete a row if necessary */
1129         for (i = 0; i < cell_cols &&
1130              !cells[(cell_rows - 1) * cell_cols + i].ch; i++);
1131         rows = cell_rows;
1132         if (i == cell_cols && cell_rows > 1 &&
1133             !cells[(cell_rows - 1) * cell_cols - 1].ch)
1134                 rows--;
1135         cells[cell_rows * cell_cols - 1].ch = 0;
1136         cells[cell_rows * cell_cols - 1].alts[0] = NULL;
1137
1138         pack_cells(0, cell_cols);
1139         cell_widget_render();
1140 }
1141
1142 static void send_cell_key(int cell)
1143 /* Send the key event for the cell */
1144 {
1145         int i;
1146
1147         if (!cells[cell].ch)
1148                 return;
1149
1150         /* Collect stats and train on corrections */
1151         if (cells[cell].ch != ' ') {
1152                 if (cells[cell].ch != cells[cell].sample.ch)
1153                         corrections++;
1154                 if (train_on_input && !(cells[cell].flags & CELL_SHIFTED) &&
1155                     cells[cell].sample.len) {
1156                         cells[cell].sample.ch = cells[cell].ch;
1157                         train_sample(&cells[cell].sample, FALSE);
1158                 }
1159                 characters++;
1160         }
1161
1162         /* Update the usage time for the sample that matched this character */
1163         for (i = 0; i < ALTERNATES && cells[cell].alts[i]; i++) {
1164                 if (!sample_valid(cells[cell].alts[i], cells[cell].alt_used[i]))
1165                         break;
1166                 if (cells[cell].alts[i]->ch == cells[cell].ch) {
1167                         promote_sample(cells[cell].alts[i]);
1168                         break;
1169                 }
1170                 demote_sample(cells[cell].alts[i]);
1171         }
1172
1173         key_event_send_char(cells[cell].ch);
1174 }
1175
1176 /*
1177         Events
1178 */
1179
1180 /* Hold click area radius */
1181 #define HOLD_CLICK_WIDTH 3.
1182
1183 /* Mask for possible buttons used by the eraser */
1184 #define ERASER_BUTTON_MASK (GDK_MOD5_MASK | GDK_BUTTON1_MASK | \
1185                             GDK_BUTTON2_MASK | GDK_BUTTON3_MASK | \
1186                             GDK_BUTTON4_MASK | GDK_BUTTON5_MASK)
1187
1188 static int menu_cell, alt_menu_alts[ALTERNATES];
1189
1190 static void training_menu_reset(void)
1191 {
1192         untrain_char(cells[menu_cell].ch);
1193         render_cell(menu_cell);
1194 }
1195
1196 static void alt_menu_selection_done(GtkWidget *widget)
1197 {
1198         gtk_widget_destroy(widget);
1199 }
1200
1201 static void alt_menu_activate(GtkWidget *widget, int *alt)
1202 {
1203         cells[menu_cell].ch = *alt;
1204         cells[menu_cell].flags |= CELL_VERIFIED;
1205         cells[menu_cell].flags &= ~CELL_SHIFTED;
1206         render_cell(menu_cell);
1207 }
1208
1209 static void alt_menu_delete(void)
1210 {
1211         delete_cell(menu_cell);
1212 }
1213
1214 static void alt_menu_show_ink(void)
1215 {
1216         cells[menu_cell].flags ^= CELL_SHOW_INK;
1217         render_cell(menu_cell);
1218 }
1219
1220 static void alt_menu_change_case(void)
1221 {
1222         if (g_unichar_isupper(cells[menu_cell].ch)) {
1223                 cells[menu_cell].ch = g_unichar_tolower(cells[menu_cell].ch);
1224                 cells[menu_cell].flags |= CELL_SHIFTED;
1225                 render_cell(menu_cell);
1226         } else if (g_unichar_islower(cells[menu_cell].ch)) {
1227                 cells[menu_cell].ch = g_unichar_toupper(cells[menu_cell].ch);
1228                 cells[menu_cell].flags |= CELL_SHIFTED;
1229                 render_cell(menu_cell);
1230         } else
1231                 g_debug("Cannot change case, not an alphabetic character");
1232 }
1233
1234 static gboolean scrollbar_scroll_event(GtkWidget *widget, GdkEventScroll *event)
1235 {
1236         check_cell(event->x, event->y, event->device);
1237         return FALSE;
1238 }
1239
1240 static gboolean scroll_event(GtkWidget *widget, GdkEventScroll *event)
1241 {
1242         if (scrollbar && GTK_WIDGET_VISIBLE(scrollbar))
1243                 gtk_widget_event(scrollbar, (GdkEvent*)event);
1244         return FALSE;
1245 }
1246
1247 static void context_menu_position(GtkMenu *menu, gint *x, gint *y,
1248                                   gboolean *push_in)
1249 /* Positions the two-column context menu so that the column divide is at
1250    the cursor rather than the upper left hand point */
1251 {
1252         if (cells[menu_cell].alts[0])
1253                 *x -= GTK_WIDGET(menu)->requisition.width / 2;
1254         *push_in = TRUE;
1255 }
1256
1257 static void show_context_menu(int button, int time)
1258 /* Popup the cell context menu for the current cell */
1259 {
1260         GtkWidget *menu, *widget;
1261         int i, pos;
1262
1263         /* Training menu is the same for all cells */
1264         if (training) {
1265                 if (!char_trained(cells[current_cell].ch))
1266                         return;
1267                 menu_cell = current_cell;
1268                 gtk_menu_popup(GTK_MENU(training_menu), 0, 0, 0, 0,
1269                                button, time);
1270                 return;
1271         }
1272
1273         /* Can't delete blanks */
1274         if (!cells[current_cell].ch)
1275                 return;
1276
1277         /* Construct an alternates menu for the current button */
1278         menu = gtk_menu_new();
1279         menu_cell = current_cell;
1280
1281         /* Menu -> Delete */
1282         widget = gtk_menu_item_new_with_label("Delete");
1283         g_signal_connect(G_OBJECT(widget), "activate",
1284                          G_CALLBACK(alt_menu_delete), NULL);
1285         gtk_menu_attach(GTK_MENU(menu), widget, 0, 1, 0, 1);
1286
1287         /* Menu -> Show Ink */
1288         if (cells[menu_cell].sample.ch) {
1289                 const char *label;
1290
1291                 label = cells[menu_cell].flags & CELL_SHOW_INK ?
1292                         "Hide ink" : "Show ink";
1293                 widget = gtk_menu_item_new_with_label(label);
1294                 g_signal_connect(G_OBJECT(widget), "activate",
1295                                  G_CALLBACK(alt_menu_show_ink), NULL);
1296                 gtk_menu_attach(GTK_MENU(menu), widget, 0, 1, 1, 2);
1297         }
1298
1299         /* Menu -> Change case */
1300         if (g_unichar_isupper(cells[menu_cell].ch) ||
1301                 g_unichar_islower(cells[menu_cell].ch)) {
1302                 const char *string = "To upper";
1303
1304                 if (g_unichar_isupper(cells[menu_cell].ch))
1305                         string = "To lower";
1306                 widget = gtk_menu_item_new_with_label(string);
1307                 g_signal_connect(G_OBJECT(widget), "activate",
1308                                  G_CALLBACK(alt_menu_change_case), NULL);
1309                 gtk_menu_attach(GTK_MENU(menu), widget, 0, 1, 2, 3);
1310         }
1311
1312         /* Menu -> Alternates */
1313         for (i = 0, pos = 0; i < ALTERNATES &&
1314                 cells[current_cell].alts[i]; i++) {
1315                 char *str;
1316
1317                 if (!sample_valid(cells[current_cell].alts[i],
1318                                   cells[current_cell].alt_used[i]))
1319                         continue;
1320                 str = va("%C\t%d%%", cells[current_cell].alts[i]->ch,
1321                          cells[current_cell].alt_ratings[i]);
1322                 alt_menu_alts[i] = cells[current_cell].alts[i]->ch;
1323                 widget = gtk_check_menu_item_new_with_label(str);
1324                 if (cells[current_cell].ch == cells[current_cell].alts[i]->ch)
1325                         gtk_check_menu_item_set_active(
1326                                              GTK_CHECK_MENU_ITEM(widget), TRUE);
1327                 g_signal_connect(G_OBJECT(widget), "activate",
1328                                  G_CALLBACK(alt_menu_activate),
1329                                  alt_menu_alts + i);
1330                 gtk_menu_attach(GTK_MENU(menu), widget, 1, 2, pos, pos + 1);
1331                 pos++;
1332         }
1333         g_signal_connect(G_OBJECT(menu), "selection-done",
1334                          G_CALLBACK(alt_menu_selection_done), NULL);
1335         gtk_widget_show_all(menu);
1336         gtk_menu_popup(GTK_MENU(menu), 0, 0,
1337                        (GtkMenuPositionFunc)context_menu_position,
1338                        0, button, time);
1339
1340 }
1341
1342 static gboolean button_press_event(GtkWidget *widget, GdkEventButton *event)
1343 /* Mouse button is pressed over drawing area */
1344 {
1345         /* Don't process double clicks */
1346         if (event->type != GDK_BUTTON_PRESS)
1347                 return TRUE;
1348
1349         /* Check validity every time */
1350         check_cell(event->x, event->y, event->device);
1351         if (invalid)
1352                 return TRUE;
1353
1354         /* If we are drawing and we get a button press event it is possible
1355            that we never received a button release event for some reason.
1356            This is a fix for Zaurus drawing connected lines. */
1357         if (drawing)
1358                 stop_drawing();
1359
1360         /* If we have pressed with the eraser, erase the cell */
1361         if (eraser || event->button == 2) {
1362                 erase_cell(current_cell);
1363                 return TRUE;
1364         }
1365
1366         /* Draw/activate insert with left click */
1367         if (event->button == 1) {
1368                 if (inserting)
1369                         potential_insert = TRUE;
1370                 else if (cells[current_cell].ch) {
1371                         start_hold();
1372                 } else
1373                         draw(event->x, event->y);
1374
1375                 /* We are now counting on getting valid coordinates here so
1376                    save in case we are doing a potential insert/hold and we
1377                    don't get a motion event in between */
1378                 cursor_x = event->x;
1379                 cursor_y = event->y;
1380
1381                 return TRUE;
1382         }
1383
1384         /* Right-click opens context menu */
1385         else if (event->button == 3 && current_cell >= 0 && !inserting &&
1386                  (!input || !input->len)) {
1387                 show_context_menu(event->button, event->time);
1388                 return TRUE;
1389         }
1390
1391         return FALSE;
1392 }
1393
1394 static gboolean button_release_event(GtkWidget *widget, GdkEventButton *event)
1395 /* Mouse button is released over drawing area */
1396 {
1397         /* Only handle left-clicks */
1398         if (event->button != 1)
1399                 return TRUE;
1400
1401         /* Complete an insertion */
1402         if (potential_insert && inserting) {
1403                 insert_cell(current_cell);
1404                 potential_insert = FALSE;
1405                 return TRUE;
1406         }
1407
1408         /* Cancel a hold-click */
1409         if (potential_hold) {
1410                 potential_hold = FALSE;
1411                 draw(cursor_x, cursor_y);
1412         }
1413
1414         stop_drawing();
1415         return TRUE;
1416 }
1417
1418 static gboolean motion_notify_event(GtkWidget *widget, GdkEventMotion *event)
1419 /* Mouse is moved over drawing area */
1420 {
1421         GdkModifierType state;
1422         double x, y;
1423
1424         /* Fetch event coordinates */
1425         x = event->x;
1426         y = event->y;
1427         if (xinput_enabled) {
1428                 gdk_device_get_state(event->device, event->window, NULL,
1429                                      &state);
1430                 gdk_event_get_coords((GdkEvent*)event, &x, &y);
1431         }
1432
1433 #if GTK_CHECK_VERSION(2, 12, 0)
1434         /* Process a hint event (GTK >= 2.12) */
1435         gdk_event_request_motions(event);
1436 #else
1437         /* Process a hint event (GTK <= 2.10) */
1438         else if (event->is_hint) {
1439                 int nx, ny;
1440
1441                 gdk_window_get_pointer(event->window, &nx, &ny, &state);
1442                 x = nx;
1443                 y = ny;
1444         }
1445 #endif
1446
1447         /* If we are getting invalid output from this device with XInput
1448            enabled, try disabling it */
1449         if ((x < 0 || x > drawing_area->allocation.width ||
1450              y < 0 || y > drawing_area->allocation.width) &&
1451             event->device->mode != GDK_MODE_DISABLED && xinput_enabled) {
1452                 g_warning("Extended input device is generating invalid "
1453                           "coordinates, disabled");
1454                 gdk_device_set_mode(event->device, GDK_MODE_DISABLED);
1455                 return TRUE;
1456         }
1457
1458         /* Check where the pointer is */
1459         check_cell(x, y, event->device);
1460
1461         /* Cancel a potential insert */
1462         if (potential_insert) {
1463                 if (!inserting) {
1464                         potential_insert = FALSE;
1465                         draw(cursor_x, cursor_y);
1466                 } else
1467                         return TRUE;
1468         }
1469
1470         /* Cancel a potential hold-click */
1471         if (potential_hold) {
1472                 double dx, dy;
1473
1474                 dx = x - cursor_x;
1475                 dy = y - cursor_y;
1476                 if (dx < -HOLD_CLICK_WIDTH || dx > HOLD_CLICK_WIDTH ||
1477                     dy < -HOLD_CLICK_WIDTH || dy > HOLD_CLICK_WIDTH) {
1478                         potential_hold = FALSE;
1479                         draw(cursor_x, cursor_y);
1480                 } else
1481                         return TRUE;
1482         }
1483
1484         cursor_x = x;
1485         cursor_y = y;
1486
1487         /* Record and draw new segment */
1488         if (drawing) {
1489                 draw(cursor_x, cursor_y);
1490                 render_segment(input, current_cell, input->len - 1,
1491                                input->strokes[input->len - 1]->len - 2, NULL);
1492         }
1493
1494         /* Erasing with the eraser. We get MOD5 rather than a button for the
1495            eraser being pressed on a Tablet PC. */
1496         else if (!invalid &&
1497                  (cross_out || (eraser && (state & ERASER_BUTTON_MASK))))
1498                 erase_cell(current_cell);
1499
1500         /* Plain motion restarts the finish countdown */
1501         start_timeout();
1502
1503         return TRUE;
1504 }
1505
1506 static void configure_keys(void)
1507 {
1508 }
1509
1510 static gboolean configure_event(void)
1511 /* Create a new backing pixmap of the appropriate size */
1512 {
1513         int new_cols;
1514
1515         /* Do nothing if we are not visible */
1516         if (!drawing_area || !drawing_area->window ||
1517             !GTK_WIDGET_VISIBLE(drawing_area))
1518                 return TRUE;
1519
1520         /* Backing pixmap */
1521         if (pixmap) {
1522                 int old_width, old_height;
1523
1524                 //return TRUE;
1525
1526                 g_object_unref(pixmap);
1527         }
1528         pixmap = gdk_pixmap_new(drawing_area->window,
1529                                 drawing_area->allocation.width,
1530                                 drawing_area->allocation.height, -1);
1531         trace("%dx%d", drawing_area->allocation.width,
1532               drawing_area->allocation.height);
1533
1534         /* GDK graphics context */
1535         if (pixmap_gc)
1536                 g_object_unref(pixmap_gc);
1537         pixmap_gc = gdk_gc_new(GDK_DRAWABLE(pixmap));
1538
1539         /* Cairo context */
1540         if (cairo)
1541                 cairo_destroy(cairo);
1542         cairo = gdk_cairo_create(GDK_DRAWABLE(pixmap));
1543
1544         /* Set font size */
1545         pango_font_description_set_absolute_size(pango_font_desc, PANGO_SCALE *
1546                                                  (cell_height -
1547                                                   CELL_BASELINE - 2));
1548
1549         /* Get the background color */
1550         color_bg = window->style->bg[0];
1551         color_bg_dark = window->style->bg[1];
1552
1553         /* Cursor */
1554         cell_widget_set_cursor(TRUE);
1555
1556         /* If the cell dimensions changed, repack */
1557         if (window_embedded) {
1558                 new_cols = (drawing_area->allocation.width -
1559                             cell_widget_scrollbar_width() - 6) / cell_width;
1560                 if (new_cols != cell_cols)
1561                         pack_cells(1, new_cols);
1562         }
1563
1564         /* If we are embedded we won't be able to resize the window so we
1565            can't honor the maximum rows preference */
1566         if (window_embedded)
1567                 cell_rows_pref = drawing_area->allocation.height / cell_height;
1568
1569         /* Update the key widget with new values */
1570         configure_keys();
1571
1572         /* Render the cells */
1573         cell_widget_render();
1574
1575         return TRUE;
1576 }
1577
1578 static gboolean expose_event(GtkWidget *widget, GdkEventExpose *event)
1579 /* Redraw the drawing area from the backing pixmap */
1580 {
1581         if (!pixmap)
1582                 return FALSE;
1583         gdk_draw_drawable(widget->window,
1584                           widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
1585                           pixmap, event->area.x, event->area.y, event->area.x,
1586                           event->area.y, event->area.width, event->area.height);
1587         return FALSE;
1588 }
1589
1590 static gboolean enter_notify_event(GtkWidget *widget, GdkEventCrossing *event)
1591 {
1592         check_cell(event->x, event->y, NULL);
1593         return FALSE;
1594 }
1595
1596 static gboolean leave_notify_event(GtkWidget *widget, GdkEventCrossing *event)
1597 {
1598         /* Tablet PC gets grab leave-notify event when starting to draw.
1599            Ignore this if we are still drawing. */
1600         if (event->mode == GDK_CROSSING_GRAB || drawing || cross_out)
1601                 return FALSE;
1602
1603         old_cc = current_cell;
1604         current_cell = -1;
1605         finish_cell(old_cc);
1606         if (inserting) {
1607                 inserting = FALSE;
1608                 dirty_cell(old_cc);
1609                 dirty_cell(old_cc - 1);
1610         }
1611         invalid = TRUE;
1612         cell_widget_set_cursor(FALSE);
1613         render_dirty();
1614         start_timeout();
1615         return FALSE;
1616 }
1617
1618 static void scrollbar_value_changed(void)
1619 /* The cell widget has been scrolled */
1620 {
1621         double value;
1622
1623         value = gtk_range_get_value(GTK_RANGE(scrollbar));
1624         if ((int)value == cell_row_view)
1625                 return;
1626         cell_row_view = value;
1627         cell_widget_render();
1628 }
1629
1630 /*
1631         Widget
1632 */
1633
1634 void cell_widget_enable_xinput(int on)
1635 /* Enable Xinput devices. We set everything to screen mode despite the fact
1636    that we actually want window coordinates. Window mode just seems to break
1637    everything and we get window coords with screen mode anyway! */
1638 {
1639         GList *list;
1640         GdkDevice *device;
1641         int i, mode;
1642
1643         gtk_widget_set_extension_events(drawing_area,
1644                                         on ? GDK_EXTENSION_EVENTS_ALL :
1645                                              GDK_EXTENSION_EVENTS_NONE);
1646         mode = on ? GDK_MODE_SCREEN : GDK_MODE_DISABLED;
1647         list = gdk_devices_list();
1648         for (i = 0; (device = (GdkDevice*)g_list_nth_data(list, i)); i++)
1649                 gdk_device_set_mode(device, mode);
1650         xinput_enabled = on;
1651         g_debug(on ? "Xinput events enabled" : "Xinput events disabled");
1652 }
1653
1654 int cell_widget_update_colors(void)
1655 {
1656         GdkColor old_active, old_inactive, old_ink, old_select;
1657
1658         old_active = color_active;
1659         old_inactive = color_inactive;
1660         old_ink = color_ink;
1661         old_select = color_select;
1662         color_active = custom_active_color;
1663         color_inactive = custom_inactive_color;
1664         color_ink = custom_ink_color;
1665         color_select = custom_select_color;
1666         if (style_colors) {
1667                 color_active = window->style->base[0];
1668                 color_ink = window->style->text[0];
1669                 color_inactive = window->style->bg[1];
1670         }
1671         return !gdk_colors_equal(&old_active, &color_active) ||
1672                !gdk_colors_equal(&old_inactive, &color_inactive) ||
1673                !gdk_colors_equal(&old_ink, &color_ink) ||
1674                !gdk_colors_equal(&old_select, &color_select);
1675 }
1676
1677 const char *cell_widget_word(void)
1678 /* Return the current word and the current cell's position in that word
1679    FIXME this function ignores wide chars */
1680 {
1681         static char buf[64];
1682         int i, min, max;
1683
1684         memset(buf, 0, sizeof (buf));
1685         if (cell_offscreen(old_cc))
1686                 return buf;
1687
1688         /* Find the start of the word */
1689         for (min = old_cc - 1; min >= 0 && cells[min].ch &&
1690              g_ascii_isalnum(cells[min].ch) && cells[min].ch < 0x7f; min--);
1691
1692         /* Find the end of the word */
1693         for (max = old_cc + 1; max < cell_rows * cell_cols && cells[max].ch &&
1694              g_ascii_isalnum(cells[max].ch) && cells[max].ch < 0x7f; max++);
1695
1696         /* Copy the word to a buffer */
1697         for (++min, i = 0; i < max - min && i < (int)sizeof (buf) - 1; i++)
1698                 buf[i] = cells[min + i].ch;
1699         buf[old_cc - min] = 0;
1700         buf[i] = 0;
1701
1702         return buf;
1703 }
1704
1705 void cell_widget_clear(void)
1706 {
1707         int resized;
1708
1709         stop_timeout();
1710         free_cells();
1711
1712         /* Restore cells if we just finished training */
1713         if (training) {
1714                 cells = cells_saved;
1715                 cell_rows = cell_rows_saved;
1716                 cell_cols = cell_cols_saved;
1717                 cell_row_view = cell_row_view_saved;
1718                 training = FALSE;
1719                 resized = pack_cells(cell_rows, cell_cols);
1720
1721                 /* Show the on-screen keyboard */
1722                 if (check_clear()) {
1723                         show_keys = keyboard_enabled;
1724                         is_clear = TRUE;
1725                 }
1726         }
1727
1728         /* Clear cells otherwise */
1729         else {
1730                 resized = pack_cells(1, cell_cols);
1731
1732                 /* Show the on-screen keyboard */
1733                 show_keys = keyboard_enabled;
1734                 is_clear = TRUE;
1735         }
1736
1737         /* Only re-render when we aren't going to get a configure event */
1738         if (!resized)
1739                 cell_widget_render();
1740 }
1741
1742 void cell_widget_train(void)
1743 {
1744         UnicodeBlock *block;
1745         int i, pos, range;
1746
1747         stop_timeout();
1748
1749         /* Save cells */
1750         if (!training) {
1751                 cells_saved = cells;
1752                 cell_rows_saved = cell_rows;
1753                 cell_cols_saved = cell_cols;
1754                 cell_row_view_saved = cell_row_view;
1755                 cells = NULL;
1756                 cell_row_view = 0;
1757         }
1758
1759         /* Clear if not training any block */
1760         if (training_block < 0) {
1761                 free_cells();
1762                 pack_cells(1, cell_cols);
1763                 cell_widget_render();
1764                 return;
1765         }
1766
1767         /* Pack the Unicode block's characters into the cell grid */
1768         block = unicode_blocks + training_block;
1769         range = block->end - block->start + 1;
1770         training = TRUE;
1771         pack_cells((range + cell_cols - 1) / cell_cols, cell_cols);
1772
1773         /* Preset all of the characters for training */
1774         for (i = 0, pos = 0; i < range; i++) {
1775                 short ch;
1776
1777                 ch = block->start + i;
1778                 if (char_disabled(ch))
1779                         continue;
1780                 cells[pos].ch = ch;
1781                 cells[pos].alts[0] = NULL;
1782                 cells[pos++].flags = 0;
1783         }
1784         range = pos;
1785         for (; pos < cell_rows * cell_cols; pos++)
1786                 clear_cell(pos);
1787         pack_cells(1, cell_cols);
1788
1789         unclear(FALSE);
1790         cell_widget_render();
1791 }
1792
1793 void cell_widget_load_string(const gchar *str){
1794
1795   int range = strlen(str);
1796   int i;
1797
1798         pack_cells((range + cell_cols - 1) / cell_cols, cell_cols);
1799
1800         /* Preset all of the characters for training */
1801         for (i = 0; i < range; i++) {
1802           cells[i].ch = str[i]; // todo: support utf-8
1803           cells[i].alts[0] = NULL;
1804           cells[i].flags = 0;
1805         }
1806         for (; i < cell_rows * cell_cols; i++)
1807                 clear_cell(i);
1808         pack_cells(1, cell_cols);
1809
1810         cell_widget_render();
1811
1812 }
1813
1814 void cell_widget_pack(void)
1815 {
1816         int cols;
1817
1818         if (training) {
1819                 cell_widget_train();
1820                 return;
1821         }
1822         cols = cell_cols_pref;
1823         if (window_docked) {
1824                 GdkScreen *screen;
1825
1826                 screen = gtk_window_get_screen(GTK_WINDOW(window));
1827                 cols = (gdk_screen_get_width(screen) -
1828                         cell_widget_scrollbar_width() - 6) / cell_width;
1829         }
1830         if (!pack_cells(0, cols))
1831                 set_size_request(TRUE);
1832         if (is_clear)
1833                 show_keys = keyboard_enabled;
1834
1835         /* Right-to-left mode may have changed so we need to reconfigure the
1836            on-screen keyboard */
1837         configure_keys();
1838
1839         cell_widget_render();
1840         trace("%dx%d, scrollbar %d",
1841                cell_cols, cell_rows, cell_widget_scrollbar_width());
1842 }
1843
1844
1845
1846 int cell_widget_insert(void)
1847 {
1848         gunichar2 *utf16;
1849         int i, j, slot, chars;
1850
1851         if (training)
1852                 return FALSE;
1853         chars = 0;
1854
1855         /* Prepare for sending key events */
1856         //key_event_update_mappings();
1857
1858         /* Need to send the keys out in reverse order for right_to_left mode
1859            because the cells are displayed with columns reversed */
1860         if (right_to_left)
1861                 for (i = cell_cols - 1; i < cell_rows * cell_cols; i--) {
1862                         if (cells[i].ch) {
1863                                 chars++;
1864                                 send_cell_key(i);
1865                         }
1866                         if (i % cell_cols == 0)
1867                                 i += cell_cols * 2;
1868                 }
1869
1870         else
1871                 for (i = 0; i < cell_rows * cell_cols; i++) {
1872                         if (!cells[i].ch)
1873                                 continue;
1874                         chars++;
1875                         send_cell_key(i);
1876                 }
1877
1878         /* If nothing was entered, send Enter key event */
1879         if (!chars) {
1880                 key_event_send_enter();
1881                 return FALSE;
1882         }
1883
1884         /* Create a UTF-16 string representation */
1885         utf16 = g_malloc(sizeof (**history) * (chars + 1));
1886         for (i = 0, j = 0; i < cell_rows * cell_cols; i++)
1887                 if (cells[i].ch)
1888                         utf16[j++] = cells[i].ch;
1889         utf16[j] = 0;
1890
1891         /* If this text has been entered before, consume that history slot */
1892         slot = HISTORY_MAX - 1;
1893         for (i = 0; i < slot && history[i]; i++)
1894                 for (j = 0; history[i][j] == utf16[j]; j++)
1895                         if (!utf16[j]) {
1896                                 slot = i;
1897                                 break;
1898                         }
1899
1900         /* Save entered text to history */
1901         g_free(history[slot]);
1902         memmove(history + 1, history, sizeof (*history) * slot);
1903         history[0] = utf16;
1904
1905         cell_widget_clear();
1906         return TRUE;
1907 }
1908
1909 static void buffer_menu_deactivate(GtkMenuShell *shell, GtkWidget *button)
1910 {
1911         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), FALSE);
1912 }
1913
1914 static void buffer_menu_item_activate(GtkWidget *widget, gunichar2 *history)
1915 {
1916         int i;
1917
1918         stop_timeout();
1919         free_cells();
1920         for (i = 0; history[i]; i++);
1921         cell_rows = i / cell_cols + 1;
1922         cell_cols = cell_cols;
1923         cells = g_malloc0(sizeof (*cells) * cell_cols * cell_rows);
1924         for (i = 0; history[i]; i++)
1925                 cells[i].ch = history[i];
1926         pack_cells(cell_rows, cell_cols);
1927         unclear(TRUE);
1928 }
1929
1930 static void buffer_menu_item_destroy(GtkWidget *widget, gchar *string)
1931 {
1932         g_free(string);
1933 }
1934
1935 static void buffer_menu_position_func(GtkMenu *menu, gint *x, gint *y,
1936                                       gboolean *push_in, GtkWidget *button)
1937 {
1938         gdk_window_get_origin(button->window, x, y);
1939         *x += button->allocation.x + button->allocation.width -
1940               GTK_WIDGET(menu)->requisition.width;
1941         *y += button->allocation.y + button->allocation.height;
1942         *push_in = TRUE;
1943 }
1944
1945 void cell_widget_show_buffer(GtkWidget *button)
1946 /* Show input back buffer menu */
1947 {
1948         static GtkWidget *menu;
1949         int i;
1950
1951         if (menu)
1952                 gtk_widget_destroy(GTK_WIDGET(menu));
1953         menu = gtk_menu_new();
1954         g_signal_connect(G_OBJECT(menu), "deactivate",
1955                          G_CALLBACK(buffer_menu_deactivate), button);
1956         for (i = 0; history[i] && i < HISTORY_MAX; i++) {
1957                 GtkWidget *item;
1958                 GError *error = NULL;
1959                 gchar *string;
1960
1961                 /* Convert string from a UTF-16 array to displayable UTF-8 */
1962                 string = g_utf16_to_utf8(history[i], -1, NULL, NULL, &error);
1963                 if (error) {
1964                         g_warning("g_utf16_to_utf8(): %s", error->message);
1965                         continue;
1966                 }
1967
1968                 /* Reverse the displayed string for right-to-left mode */
1969                 if (right_to_left) {
1970                         gchar *reversed;
1971
1972                         reversed = g_utf8_strreverse(string, -1);
1973                         g_free(string);
1974                         string = reversed;
1975                 }
1976
1977                 /* Create menu item */
1978                 item = gtk_menu_item_new_with_label(string);
1979                 g_signal_connect(G_OBJECT(item), "destroy",
1980                                  G_CALLBACK(buffer_menu_item_destroy), string);
1981                 g_signal_connect(G_OBJECT(item), "activate",
1982                                  G_CALLBACK(buffer_menu_item_activate),
1983                                  history[i]);
1984                 gtk_menu_attach(GTK_MENU(menu), item, 0, 1, i, i + 1);
1985         }
1986
1987         /* Show back buffer menu */
1988         gtk_widget_show_all(menu);
1989         gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
1990                        (GtkMenuPositionFunc)buffer_menu_position_func,
1991                        button, 0, gtk_get_current_event_time());
1992 }
1993
1994 int cell_widget_scrollbar_width(void)
1995 /* Gets the width of the scrollbar even if it is hidden */
1996 {
1997         GtkRequisition requisition;
1998
1999         if (scrollbar->requisition.width <= 1) {
2000                 gtk_widget_size_request(scrollbar, &requisition);
2001                 return requisition.width;
2002         }
2003         return scrollbar->requisition.width + 4;
2004 }
2005
2006 int cell_widget_get_height(void)
2007 {
2008         int rows;
2009
2010         rows = cell_rows > cell_rows_pref ? cell_rows_pref : cell_rows;
2011         return rows * cell_height + 2;
2012 }
2013
2014 GtkWidget *cell_widget_new(void)
2015 /* Creates the Cell widget. Should only be called once per program run! */
2016 {
2017         PangoCairoFontMap *font_map;
2018         GtkWidget *widget, *hbox;
2019
2020         /* Initial settings */
2021         cell_cols = cell_cols_pref;
2022
2023         /* Create drawing area */
2024         drawing_area = gtk_drawing_area_new();
2025         g_signal_connect(G_OBJECT(drawing_area), "expose_event",
2026                          G_CALLBACK(expose_event), NULL);
2027         g_signal_connect(G_OBJECT(drawing_area), "configure_event",
2028                          G_CALLBACK(configure_event), NULL);
2029         g_signal_connect(G_OBJECT(drawing_area), "show",
2030                          G_CALLBACK(configure_event), NULL);
2031         g_signal_connect(G_OBJECT(drawing_area), "button_press_event",
2032                          G_CALLBACK(button_press_event), NULL);
2033         g_signal_connect(G_OBJECT(drawing_area), "button_release_event",
2034                          G_CALLBACK(button_release_event), NULL);
2035         g_signal_connect(G_OBJECT(drawing_area), "motion_notify_event",
2036                          G_CALLBACK(motion_notify_event), NULL);
2037         g_signal_connect(G_OBJECT(drawing_area), "enter_notify_event",
2038                          G_CALLBACK(enter_notify_event), NULL);
2039         g_signal_connect(G_OBJECT(drawing_area), "leave_notify_event",
2040                          G_CALLBACK(leave_notify_event), NULL);
2041         g_signal_connect(G_OBJECT(drawing_area), "scroll_event",
2042                          G_CALLBACK(scroll_event), NULL);
2043         g_signal_connect(G_OBJECT(drawing_area), "style-set",
2044                          G_CALLBACK(cell_widget_update_colors), NULL);
2045         gtk_widget_set_events(drawing_area,
2046                               GDK_EXPOSURE_MASK |
2047                               GDK_BUTTON_PRESS_MASK |
2048                               GDK_BUTTON_RELEASE_MASK |
2049                               GDK_POINTER_MOTION_MASK |
2050                               GDK_POINTER_MOTION_HINT_MASK |
2051                               GDK_ENTER_NOTIFY_MASK |
2052                               GDK_LEAVE_NOTIFY_MASK |
2053                               GDK_SCROLL_MASK);
2054
2055         /* Update colors */
2056         cell_widget_update_colors();
2057
2058         /* Create training menu */
2059         training_menu = gtk_menu_new();
2060         widget = gtk_menu_item_new_with_label("Reset");
2061         g_signal_connect(G_OBJECT(widget), "activate",
2062                          G_CALLBACK(training_menu_reset), NULL);
2063         gtk_menu_attach(GTK_MENU(training_menu), widget, 0, 1, 0, 1);
2064         gtk_widget_show_all(training_menu);
2065
2066         /* Create scroll bar */
2067         scrollbar = gtk_vscrollbar_new(NULL);
2068         gtk_widget_set_no_show_all(scrollbar, TRUE);
2069         g_signal_connect(G_OBJECT(scrollbar), "value-changed",
2070                          G_CALLBACK(scrollbar_value_changed), NULL);
2071         g_signal_connect(G_OBJECT(scrollbar), "scroll_event",
2072                          G_CALLBACK(scrollbar_scroll_event), NULL);
2073         gtk_widget_add_events(drawing_area, GDK_SCROLL_MASK);
2074
2075         /* Box container */
2076         hbox = gtk_hbox_new(FALSE, 0);
2077         gtk_box_pack_start(GTK_BOX(hbox), drawing_area, TRUE, TRUE, 2);
2078         gtk_box_pack_start(GTK_BOX(hbox), scrollbar, FALSE, FALSE, 2);
2079
2080         /* Create Pango font description
2081            FIXME font characteristics, not family */
2082         pango_font_desc = pango_font_description_new();
2083         pango_font_description_set_family(pango_font_desc, "Monospace");
2084
2085         /* Pango context */
2086         font_map = PANGO_CAIRO_FONT_MAP(pango_cairo_font_map_new());
2087         pango = pango_cairo_font_map_create_context(font_map);
2088         g_object_unref(font_map);
2089
2090         /* Clear cells */
2091         cell_widget_clear();
2092
2093         /* Set Xinput state */
2094         cell_widget_enable_xinput(xinput_enabled);
2095
2096         /* Clear history */
2097         memset(history, 0, sizeof (history));
2098
2099         return hbox;
2100 }
2101
2102 void cell_widget_cleanup(void)
2103 {
2104         /* Freeing memory when closing is important when trying to sort
2105            legitimate memory leaks from left-over memory */
2106         if (pixmap)
2107                 g_object_unref(pixmap);
2108         if (pixmap_gc)
2109                 g_object_unref(pixmap_gc);
2110         if (cairo)
2111                 cairo_destroy(cairo);
2112         if (pango)
2113                 g_object_unref(pango);
2114 }
2115
2116 extern HildonIMUI *ui;
2117
2118 void unicode_to_utf8(unsigned int code, char *out);
2119 void cell_widget_insert_surrounding_string(){
2120   int i;
2121
2122   gchar *str = malloc(cell_cols * cell_rows * 2 + 1);
2123   gchar *s = str;
2124   *s = 0;
2125   for(i = 0; i < cell_cols * cell_rows; i++){
2126     if(!(cells[i].flags & CELL_DIRTY)){
2127       unicode_to_utf8(cells[i].ch, s);
2128       s += strlen(s);
2129     }
2130   }
2131
2132   hildon_im_ui_send_surrounding_content(ui, str);
2133 }