1 /********************************************************************\
2  * This program is free software; you can redistribute it and/or    *
3  * modify it under the terms of the GNU General Public License as   *
4  * published by the Free Software Foundation; either version 2 of   *
5  * the License, or (at your option) any later version.              *
6  *                                                                  *
7  * This program is distributed in the hope that it will be useful,  *
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
10  * GNU General Public License for more details.                     *
11  *                                                                  *
12  * You should have received a copy of the GNU General Public License*
13  * along with this program; if not, contact:                        *
14  *                                                                  *
15  * Free Software Foundation           Voice:  +1-617-542-5942       *
16  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
17  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
18  *                                                                  *
19 \********************************************************************/
20 
21 /*
22  * The Gnucash Sheet widget
23  *
24  *  Based heavily on the Gnumeric Sheet widget.
25  *
26  * Authors:
27  *     Heath Martin <martinh@pegasus.cc.ucf.edu>
28  *     Dave Peticolas <dave@krondo.com>
29  */
30 
31 #include <config.h>
32 #include <glib.h>
33 #include <gdk/gdkkeysyms.h>
34 
35 #include "gnucash-sheet.h"
36 #include "gnucash-sheetP.h"
37 
38 #include "dialog-utils.h"
39 #include "gnc-gtk-utils.h"
40 #include "gnc-prefs.h"
41 #include "gnucash-color.h"
42 #include "gnucash-cursor.h"
43 #include "gnucash-style.h"
44 #include "gnucash-header.h"
45 #include "gnucash-item-edit.h"
46 #include "split-register.h"
47 #include "gnc-engine.h"     // For debugging, e.g. ENTER(), LEAVE()
48 
49 #ifdef G_OS_WIN32
50 # include <gdk/gdkwin32.h>
51 #endif
52 
53 #define DEFAULT_SHEET_HEIGHT 400
54 #define DEFAULT_SHEET_WIDTH  400
55 /* Used to calculate the minimum preferred height of the sheet layout: */
56 #define DEFAULT_SHEET_INITIAL_ROWS 10
57 
58 
59 /* Register signals */
60 enum
61 {
62     ACTIVATE_CURSOR,
63     REDRAW_ALL,
64     REDRAW_HELP,
65     LAST_SIGNAL
66 };
67 
68 
69 /** Static Globals *****************************************************/
70 
71 /* This static indicates the debugging module that this .o belongs to. */
72 static QofLogModule log_module = G_LOG_DOMAIN;
73 static GtkLayout *sheet_parent_class;
74 
75 
76 /** Prototypes *********************************************************/
77 
78 static void gnucash_sheet_start_editing_at_cursor (GnucashSheet *sheet);
79 
80 static gboolean gnucash_sheet_cursor_move (GnucashSheet *sheet,
81                                            VirtualLocation virt_loc);
82 
83 static void gnucash_sheet_deactivate_cursor_cell (GnucashSheet *sheet);
84 static void gnucash_sheet_activate_cursor_cell (GnucashSheet *sheet,
85                                                 gboolean changed_cells);
86 static void gnucash_sheet_stop_editing (GnucashSheet *sheet);
87 static gboolean gnucash_sheet_check_direct_update_cell (GnucashSheet *sheet,
88                                                         const VirtualLocation virt_loc);
89 gboolean gnucash_sheet_draw_cb (GtkWidget *widget, cairo_t *cr,
90                                 G_GNUC_UNUSED gpointer data);
91 
92 /** Implementation *****************************************************/
93 
94 
95 /* gtk_editable_set_position sets both current_pos and selection_bound to the
96  * supplied value. gtk_editable_select_region(start, end) sets current_pos to
97  * end and selection_bound to start; if either is < 0 it's changed to length.
98  *
99  * That's a bit orthogonal to the way GncTable sees things, so the following
100  * functions translate between the two.
101  */
102 
103 static inline void
gnucash_sheet_set_entry_selection(GnucashSheet * sheet)104 gnucash_sheet_set_entry_selection (GnucashSheet *sheet)
105 {
106     DEBUG("Set entry selection to sheet: %d:%d", sheet->bound, sheet->pos);
107     gtk_editable_select_region (GTK_EDITABLE(sheet->entry),
108                                 sheet->bound, sheet->pos);
109 }
110 
111 static inline void
gnucash_sheet_set_selection_from_entry(GnucashSheet * sheet)112 gnucash_sheet_set_selection_from_entry (GnucashSheet *sheet)
113 {
114     gtk_editable_get_selection_bounds (GTK_EDITABLE(sheet->entry),
115                                        &sheet->bound, &sheet->pos);
116 }
117 
118 static inline void
gnucash_sheet_set_selection(GnucashSheet * sheet,int pos,int bound)119 gnucash_sheet_set_selection (GnucashSheet *sheet, int pos, int bound)
120 {
121     DEBUG("Set sheet selection %d:%d", bound, pos);
122     sheet->pos = pos;
123     sheet->bound = bound;
124     gnucash_sheet_set_entry_selection (sheet);
125 }
126 
127 // The variable names here are intended to match the GncTable usage.
128 static inline void
gnucash_sheet_set_position_and_selection(GnucashSheet * sheet,int pos,int start,int end)129 gnucash_sheet_set_position_and_selection (GnucashSheet* sheet, int pos,
130                                           int start, int end)
131 {
132     if (pos == end || start == -1)
133         gnucash_sheet_set_selection (sheet, pos, start);
134     else if (pos == start || end == -1)
135         gnucash_sheet_set_selection (sheet, start, end);
136     else if (start == end)
137         gnucash_sheet_set_selection (sheet, pos, pos);
138     else
139         gnucash_sheet_set_selection (sheet, pos, end);
140 }
141 
142 static inline void
gnucash_sheet_set_position(GnucashSheet * sheet,int pos)143 gnucash_sheet_set_position (GnucashSheet* sheet, int pos)
144 {
145     gnucash_sheet_set_position_and_selection (sheet, pos, pos, pos);
146 }
147 
148 static inline void
gnucash_sheet_get_selection(GnucashSheet * sheet,int * start,int * end)149 gnucash_sheet_get_selection (GnucashSheet *sheet, int *start, int *end)
150 {
151     *start = sheet->pos;
152     *end = sheet->bound;
153 }
154 
155 static inline void
gnucash_sheet_clear_selection(GnucashSheet * sheet)156 gnucash_sheet_clear_selection (GnucashSheet *sheet)
157 {
158     gnucash_sheet_set_selection (sheet, sheet->pos, sheet->pos);
159 }
160 
161 static inline void
gnucash_sheet_set_entry_value(GnucashSheet * sheet,const char * value)162 gnucash_sheet_set_entry_value (GnucashSheet *sheet, const char* value)
163 {
164     g_signal_handler_block (G_OBJECT(sheet->entry),
165                             sheet->insert_signal);
166     g_signal_handler_block (G_OBJECT(sheet->entry),
167                             sheet->delete_signal);
168 
169     gtk_entry_set_text (GTK_ENTRY(sheet->entry), value);
170 
171     g_signal_handler_unblock (G_OBJECT(sheet->entry),
172                               sheet->delete_signal);
173     g_signal_handler_unblock (G_OBJECT(sheet->entry),
174                               sheet->insert_signal);
175 
176 }
177 
178 static inline gboolean
gnucash_sheet_virt_cell_out_of_bounds(GnucashSheet * sheet,VirtualCellLocation vcell_loc)179 gnucash_sheet_virt_cell_out_of_bounds (GnucashSheet *sheet,
180                                        VirtualCellLocation vcell_loc)
181 {
182     return (vcell_loc.virt_row < 1 ||
183             vcell_loc.virt_row >= sheet->num_virt_rows ||
184             vcell_loc.virt_col < 0 ||
185             vcell_loc.virt_col >= sheet->num_virt_cols);
186 }
187 
188 static gboolean
gnucash_sheet_cell_valid(GnucashSheet * sheet,VirtualLocation virt_loc)189 gnucash_sheet_cell_valid (GnucashSheet *sheet, VirtualLocation virt_loc)
190 {
191     gboolean valid;
192     SheetBlockStyle *style;
193 
194     valid = !gnucash_sheet_virt_cell_out_of_bounds (sheet,
195                                                     virt_loc.vcell_loc);
196 
197     if (valid)
198     {
199         style = gnucash_sheet_get_style (sheet, virt_loc.vcell_loc);
200 
201         valid = (virt_loc.phys_row_offset >= 0 &&
202                  virt_loc.phys_row_offset < style->nrows &&
203                  virt_loc.phys_col_offset >= 0 &&
204                  virt_loc.phys_col_offset < style->ncols);
205     }
206 
207     return valid;
208 }
209 
210 
211 static void
gnucash_sheet_cursor_set(GnucashSheet * sheet,VirtualLocation virt_loc)212 gnucash_sheet_cursor_set (GnucashSheet *sheet, VirtualLocation virt_loc)
213 {
214     g_return_if_fail (sheet != NULL);
215     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
216 
217     g_return_if_fail (virt_loc.vcell_loc.virt_row >= 0 ||
218                       virt_loc.vcell_loc.virt_row <= sheet->num_virt_rows);
219     g_return_if_fail (virt_loc.vcell_loc.virt_col >= 0 ||
220                       virt_loc.vcell_loc.virt_col <= sheet->num_virt_cols);
221 
222     gtk_widget_queue_draw_area (GTK_WIDGET(sheet),
223                                 sheet->cursor->x, sheet->cursor->y,
224                                 sheet->cursor->w, sheet->cursor->h);
225 
226     gnucash_cursor_set (GNUCASH_CURSOR(sheet->cursor), virt_loc);
227 
228     gtk_widget_queue_draw_area (GTK_WIDGET(sheet),
229                                 sheet->cursor->x, sheet->cursor->y,
230                                 sheet->cursor->w, sheet->cursor->h);
231 }
232 
233 void
gnucash_sheet_cursor_set_from_table(GnucashSheet * sheet,gboolean do_scroll)234 gnucash_sheet_cursor_set_from_table (GnucashSheet *sheet, gboolean do_scroll)
235 {
236     Table *table;
237     VirtualLocation v_loc;
238 
239     g_return_if_fail (sheet != NULL);
240     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
241 
242     table = sheet->table;
243     v_loc = table->current_cursor_loc;
244 
245     g_return_if_fail (gnucash_sheet_cell_valid (sheet, v_loc));
246 
247     gnucash_sheet_cursor_set (sheet, v_loc);
248 
249     if (do_scroll)
250         gnucash_sheet_make_cell_visible (sheet, v_loc);
251 }
252 
253 
254 void
gnucash_sheet_set_popup(GnucashSheet * sheet,GtkWidget * popup,gpointer data)255 gnucash_sheet_set_popup (GnucashSheet *sheet, GtkWidget *popup, gpointer data)
256 {
257     if (popup)
258         g_object_ref (popup);
259 
260     if (sheet->popup)
261         g_object_unref (sheet->popup);
262 
263     sheet->popup = popup;
264     sheet->popup_data = data;
265 }
266 
267 
268 static void
gnucash_sheet_hide_editing_cursor(GnucashSheet * sheet)269 gnucash_sheet_hide_editing_cursor (GnucashSheet *sheet)
270 {
271     if (sheet->item_editor == NULL)
272         return;
273 
274     gtk_widget_hide (sheet->item_editor);
275     gnc_item_edit_hide_popup (GNC_ITEM_EDIT(sheet->item_editor));
276 }
277 
278 static void
gnucash_sheet_stop_editing(GnucashSheet * sheet)279 gnucash_sheet_stop_editing (GnucashSheet *sheet)
280 {
281     /* Rollback an uncommitted string if it exists   *
282      * *before* disconnecting signal handlers.       */
283 
284     if (sheet->insert_signal != 0)
285         g_signal_handler_disconnect (G_OBJECT(sheet->entry),
286                                      sheet->insert_signal);
287     if (sheet->delete_signal != 0)
288         g_signal_handler_disconnect (G_OBJECT(sheet->entry),
289                                      sheet->delete_signal);
290     sheet->insert_signal = 0;
291     sheet->delete_signal = 0;
292     sheet->direct_update_cell = FALSE;
293 
294     gnucash_sheet_hide_editing_cursor (sheet);
295 
296     sheet->editing = FALSE;
297     sheet->input_cancelled = FALSE;
298 }
299 
300 
301 static void
gnucash_sheet_deactivate_cursor_cell(GnucashSheet * sheet)302 gnucash_sheet_deactivate_cursor_cell (GnucashSheet *sheet)
303 {
304     VirtualLocation virt_loc;
305 
306     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
307 
308     gnucash_sheet_stop_editing (sheet);
309 
310     if (!gnc_table_model_read_only (sheet->table->model))
311         gnc_table_leave_update (sheet->table, virt_loc);
312 
313     gnucash_sheet_redraw_block (sheet, virt_loc.vcell_loc);
314 }
315 
316 void
gnucash_sheet_set_text_bounds(GnucashSheet * sheet,GdkRectangle * rect,gint x,gint y,gint width,gint height)317 gnucash_sheet_set_text_bounds (GnucashSheet *sheet, GdkRectangle *rect,
318                                gint x, gint y, gint width, gint height)
319 {
320     GncItemEdit *item_edit = GNC_ITEM_EDIT(sheet->item_editor);
321 
322     rect->x = x + gnc_item_edit_get_margin (item_edit, left);
323     rect->y = y + gnc_item_edit_get_margin (item_edit, top);
324     rect->width = MAX (0, width - gnc_item_edit_get_margin (item_edit, left_right));
325     rect->height = height - gnc_item_edit_get_margin (item_edit, top_bottom);
326 }
327 
328 gint
gnucash_sheet_get_text_offset(GnucashSheet * sheet,const VirtualLocation virt_loc,gint rect_width,gint logical_width)329 gnucash_sheet_get_text_offset (GnucashSheet *sheet, const VirtualLocation virt_loc,
330                                gint rect_width, gint logical_width)
331 {
332     GncItemEdit *item_edit = GNC_ITEM_EDIT(sheet->item_editor);
333     Table *table = sheet->table;
334     gint x_offset = 0;
335 
336     // Get the alignment of the cell
337     switch (gnc_table_get_align (table, virt_loc))
338     {
339     default:
340     case CELL_ALIGN_LEFT:
341         x_offset = gnc_item_edit_get_padding_border (item_edit, left);
342         break;
343 
344     case CELL_ALIGN_RIGHT:
345         x_offset = rect_width - gnc_item_edit_get_padding_border (item_edit, right) - logical_width - 1;
346         break;
347 
348     case CELL_ALIGN_CENTER:
349         if (logical_width > rect_width)
350             x_offset = 0;
351         else
352             x_offset = (rect_width - logical_width) / 2;
353         break;
354     }
355     return x_offset;
356 }
357 
358 static void
gnucash_sheet_activate_cursor_cell(GnucashSheet * sheet,gboolean changed_cells)359 gnucash_sheet_activate_cursor_cell (GnucashSheet *sheet,
360                                     gboolean changed_cells)
361 {
362     Table *table = sheet->table;
363     VirtualLocation virt_loc;
364     SheetBlockStyle *style;
365     GtkEditable *editable;
366     int cursor_pos, start_sel, end_sel;
367     gboolean allow_edits;
368 
369     /* Sanity check */
370     if (sheet->editing)
371         gnucash_sheet_deactivate_cursor_cell (sheet);
372 
373     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
374 
375     /* This should be a no-op */
376     gnc_table_wrap_verify_cursor_position (table, virt_loc);
377 
378     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
379 
380     if (!gnc_table_virtual_loc_valid (table, virt_loc, TRUE))
381         return;
382 
383     style = gnucash_sheet_get_style (sheet, virt_loc.vcell_loc);
384     if (strcmp (style->cursor->cursor_name, CURSOR_HEADER) == 0)
385         return;
386 
387     editable = GTK_EDITABLE(sheet->entry);
388 
389     cursor_pos = -1;
390     start_sel = 0;
391     end_sel = 0;
392 
393     if (gnc_table_model_read_only (table->model))
394         allow_edits = FALSE;
395     else
396         allow_edits = gnc_table_enter_update (table, virt_loc,
397                                               &cursor_pos,
398                                               &start_sel, &end_sel);
399 
400     if (!allow_edits)
401         gnucash_sheet_redraw_block (sheet, virt_loc.vcell_loc);
402     else
403     {
404         gtk_entry_reset_im_context (GTK_ENTRY (sheet->entry));
405         gnucash_sheet_start_editing_at_cursor (sheet);
406 
407         // Came here by keyboard, select text, otherwise text cursor to
408         // mouse position
409         if (sheet->button != 1)
410         {
411             gnucash_sheet_set_position_and_selection (sheet, cursor_pos,
412                                                       start_sel, end_sel);
413         }
414         else
415         {
416             GncItemEdit *item_edit = GNC_ITEM_EDIT(sheet->item_editor);
417             Table *table = sheet->table;
418             const char *text = gnc_table_get_entry (table, virt_loc);
419             PangoLayout *layout;
420             PangoRectangle logical_rect;
421             GdkRectangle rect;
422             gint x, y, width, height;
423             gint index = 0, trailing = 0;
424             gboolean result;
425             gint x_offset = 0;
426 
427             if (text && *text)
428             {
429                 // Get the item_edit position
430                 gnc_item_edit_get_pixel_coords (item_edit, &x, &y,
431                                                 &width, &height);
432                 layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet),
433                                                          text);
434                 // We don't need word wrap or line wrap
435                 pango_layout_set_width (layout, -1);
436                 pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
437                 gnucash_sheet_set_text_bounds (sheet, &rect, x, y,
438                                                width, height);
439                 x_offset = gnucash_sheet_get_text_offset (sheet, virt_loc,
440                                                           rect.width,
441                                                           logical_rect.width);
442                 pango_layout_xy_to_index (layout,
443                                           PANGO_SCALE * (sheet->button_x - rect.x - x_offset),
444                                           PANGO_SCALE * (height/2), &index, &trailing);
445                 g_object_unref (layout);
446             }
447             gnucash_sheet_set_position (sheet, index + trailing);
448         }
449         sheet->direct_update_cell = gnucash_sheet_check_direct_update_cell (sheet, virt_loc);
450     }
451     // when a gui refresh is called, we end up here so only grab the focus
452     // if the sheet is showing on the current plugin_page
453     if (sheet->sheet_has_focus)
454         gtk_widget_grab_focus (GTK_WIDGET(sheet));
455 }
456 
457 
458 static gboolean
gnucash_sheet_cursor_move(GnucashSheet * sheet,VirtualLocation virt_loc)459 gnucash_sheet_cursor_move (GnucashSheet *sheet, VirtualLocation virt_loc)
460 {
461     VirtualLocation old_virt_loc;
462     gboolean changed_cells;
463     Table *table;
464 
465     table = sheet->table;
466 
467     /* Get the old cursor position */
468     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &old_virt_loc);
469 
470     /* Turn off the editing controls */
471     gnucash_sheet_deactivate_cursor_cell (sheet);
472 
473     /* Do the move. This may result in table restructuring due to
474      * commits, auto modes, etc. */
475     gnc_table_wrap_verify_cursor_position (table, virt_loc);
476 
477     /* A complete reload can leave us with editing back on */
478     if (sheet->editing)
479         gnucash_sheet_deactivate_cursor_cell (sheet);
480 
481     /* Find out where we really landed. We have to get the new
482      * physical position as well, as the table may have been
483      * restructured. */
484     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
485 
486     gnucash_sheet_cursor_set (sheet, virt_loc);
487 
488     /* We should be at our new location now. Show it on screen and
489      * configure the cursor. */
490     gnucash_sheet_make_cell_visible (sheet, virt_loc);
491 
492     changed_cells = !virt_loc_equal (virt_loc, old_virt_loc);
493 
494     /* If we've changed cells, redraw the headers and sheet */
495     if (changed_cells)
496     {
497         gnc_header_request_redraw (GNC_HEADER(sheet->header_item));
498         gtk_widget_queue_draw (GTK_WIDGET(sheet));
499     }
500 
501     /* Now turn on the editing controls. */
502     gnucash_sheet_activate_cursor_cell (sheet, changed_cells);
503 
504     if (sheet->moved_cb)
505         (sheet->moved_cb)(sheet, sheet->moved_cb_data);
506     return changed_cells;
507 }
508 
509 
510 static gint
gnucash_sheet_y_pixel_to_block(GnucashSheet * sheet,int y)511 gnucash_sheet_y_pixel_to_block (GnucashSheet *sheet, int y)
512 {
513     VirtualCellLocation vcell_loc = { 1, 0 };
514 
515     for (;
516             vcell_loc.virt_row < sheet->num_virt_rows;
517             vcell_loc.virt_row++)
518     {
519         SheetBlock *block;
520 
521         block = gnucash_sheet_get_block (sheet, vcell_loc);
522         if (!block || !block->visible)
523             continue;
524 
525         if (block->origin_y + block->style->dimensions->height > y)
526             break;
527     }
528     return vcell_loc.virt_row;
529 }
530 
531 
532 void
gnucash_sheet_compute_visible_range(GnucashSheet * sheet)533 gnucash_sheet_compute_visible_range (GnucashSheet *sheet)
534 {
535     VirtualCellLocation vcell_loc;
536     GtkAllocation alloc;
537     GtkAdjustment *adj;
538     gint height;
539     gint cy;
540     gint top_block;
541 
542     g_return_if_fail (sheet != NULL);
543     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
544 
545     gtk_widget_get_allocation (GTK_WIDGET(sheet), &alloc);
546     height = alloc.height;
547 
548     adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE(sheet));
549     cy = gtk_adjustment_get_value (adj);
550 
551     top_block = gnucash_sheet_y_pixel_to_block (sheet, cy);
552 
553     sheet->num_visible_blocks = 0;
554     sheet->num_visible_phys_rows = 0;
555 
556     for (vcell_loc.virt_row = top_block, vcell_loc.virt_col = 0;
557          vcell_loc.virt_row < sheet->num_virt_rows;
558          vcell_loc.virt_row++)
559     {
560         SheetBlock *block;
561 
562         block = gnucash_sheet_get_block (sheet, vcell_loc);
563         if (!block->visible)
564             continue;
565 
566         sheet->num_visible_blocks++;
567         sheet->num_visible_phys_rows += block->style->nrows;
568 
569         if (block->origin_y - cy + block->style->dimensions->height
570                 >= height)
571             break;
572     }
573 }
574 
575 
576 static void
gnucash_sheet_show_row(GnucashSheet * sheet,gint virt_row)577 gnucash_sheet_show_row (GnucashSheet *sheet, gint virt_row)
578 {
579     VirtualCellLocation vcell_loc = { virt_row, 0 };
580     SheetBlock *block;
581     GtkAllocation alloc;
582     GtkAdjustment *adj;
583     gint block_height;
584     gint height;
585     gint cx, cy;
586     gint x, y;
587 
588     g_return_if_fail (virt_row >= 0);
589     g_return_if_fail (sheet != NULL);
590     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
591 
592     vcell_loc.virt_row = MAX (vcell_loc.virt_row, 1);
593     vcell_loc.virt_row = MIN (vcell_loc.virt_row,
594                               sheet->num_virt_rows - 1);
595 
596     adj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE(sheet));
597     cx = gtk_adjustment_get_value (adj);
598     adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE(sheet));
599     cy = gtk_adjustment_get_value (adj);
600     x = cx;
601 
602     gtk_widget_get_allocation (GTK_WIDGET(sheet), &alloc);
603     height = alloc.height;
604 
605     block = gnucash_sheet_get_block (sheet, vcell_loc);
606     if (!block)
607         return;
608     y = block->origin_y;
609     block_height = block->style->dimensions->height;
610 
611     if ((cy <= y) && (cy + height >= y + block_height))
612     {
613         gnucash_sheet_compute_visible_range (sheet);
614         return;
615     }
616 
617     if (y > cy)
618         y -= height - MIN (block_height, height);
619 
620     if ((sheet->height - y) < height)
621         y = sheet->height - height;
622 
623     if (y < 0)
624         y = 0;
625 
626     if (y != cy)
627         gtk_adjustment_set_value (sheet->vadj, y);
628     if (x != cx)
629         gtk_adjustment_set_value (sheet->hadj, x);
630 
631     gnucash_sheet_compute_visible_range (sheet);
632     gnucash_sheet_update_adjustments (sheet);
633 }
634 
635 
636 void
gnucash_sheet_make_cell_visible(GnucashSheet * sheet,VirtualLocation virt_loc)637 gnucash_sheet_make_cell_visible (GnucashSheet *sheet, VirtualLocation virt_loc)
638 {
639     g_return_if_fail (sheet != NULL);
640     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
641 
642     if (!gnucash_sheet_cell_valid (sheet, virt_loc))
643         return;
644 
645     gnucash_sheet_show_row (sheet, virt_loc.vcell_loc.virt_row);
646 
647     gnucash_sheet_update_adjustments (sheet);
648 }
649 
650 
651 void
gnucash_sheet_show_range(GnucashSheet * sheet,VirtualCellLocation start_loc,VirtualCellLocation end_loc)652 gnucash_sheet_show_range (GnucashSheet *sheet,
653                           VirtualCellLocation start_loc,
654                           VirtualCellLocation end_loc)
655 {
656     SheetBlock *start_block;
657     SheetBlock *end_block;
658     GtkAllocation alloc;
659     GtkAdjustment *adj;
660     gint block_height;
661     gint height;
662     gint cx, cy;
663     gint x, y;
664 
665     g_return_if_fail (sheet != NULL);
666     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
667 
668     start_loc.virt_row = MAX(start_loc.virt_row, 1);
669     start_loc.virt_row = MIN(start_loc.virt_row,
670                              sheet->num_virt_rows - 1);
671 
672     end_loc.virt_row = MAX(end_loc.virt_row, 1);
673     end_loc.virt_row = MIN(end_loc.virt_row,
674                            sheet->num_virt_rows - 1);
675 
676     adj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE(sheet));
677     cx = gtk_adjustment_get_value (adj);
678     adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE(sheet));
679     cy = gtk_adjustment_get_value (adj);
680     x = cx;
681 
682     gtk_widget_get_allocation (GTK_WIDGET(sheet), &alloc);
683     height = alloc.height;
684 
685     start_block = gnucash_sheet_get_block (sheet, start_loc);
686     end_block = gnucash_sheet_get_block (sheet, end_loc);
687     if (!(start_block && end_block))
688         return;
689 
690     y = start_block->origin_y;
691     block_height = (end_block->origin_y +
692                     end_block->style->dimensions->height) - y;
693 
694     if ((cy <= y) && (cy + height >= y + block_height))
695     {
696         gnucash_sheet_compute_visible_range (sheet);
697         return;
698     }
699 
700     if (y > cy)
701         y -= height - MIN(block_height, height);
702 
703     if ((sheet->height - y) < height)
704         y = sheet->height - height;
705 
706     if (y < 0)
707         y = 0;
708 
709     if (y != cy)
710         gtk_adjustment_set_value (sheet->vadj, y);
711     if (x != cx)
712         gtk_adjustment_set_value (sheet->hadj, x);
713 
714     gnucash_sheet_compute_visible_range (sheet);
715     gnucash_sheet_update_adjustments (sheet);
716 }
717 
718 
719 void
gnucash_sheet_update_adjustments(GnucashSheet * sheet)720 gnucash_sheet_update_adjustments (GnucashSheet *sheet)
721 {
722     GtkAdjustment *vadj;
723 
724     g_return_if_fail (sheet != NULL);
725     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
726     g_return_if_fail (sheet->vadj != NULL);
727 
728     vadj = sheet->vadj;
729 
730     if (sheet->num_visible_blocks > 0)
731         gtk_adjustment_set_step_increment (vadj,
732             gtk_adjustment_get_page_size (vadj) / sheet->num_visible_blocks);
733     else
734         gtk_adjustment_set_step_increment (vadj, 0);
735 }
736 
737 
738 static void
gnucash_sheet_vadjustment_value_changed(GtkAdjustment * adj,GnucashSheet * sheet)739 gnucash_sheet_vadjustment_value_changed (GtkAdjustment *adj,
740                                          GnucashSheet *sheet)
741 {
742     gnucash_sheet_compute_visible_range (sheet);
743 }
744 
745 
746 void
gnucash_sheet_redraw_all(GnucashSheet * sheet)747 gnucash_sheet_redraw_all (GnucashSheet *sheet)
748 {
749     g_return_if_fail (sheet != NULL);
750     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
751 
752     gtk_widget_queue_draw (GTK_WIDGET(sheet));
753 
754     g_signal_emit_by_name (sheet->reg, "redraw_all");
755 }
756 
757 void
gnucash_sheet_redraw_help(GnucashSheet * sheet)758 gnucash_sheet_redraw_help (GnucashSheet *sheet)
759 {
760     g_return_if_fail (sheet != NULL);
761     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
762 
763     g_signal_emit_by_name (sheet->reg, "redraw_help");
764 }
765 
766 void
gnucash_sheet_redraw_block(GnucashSheet * sheet,VirtualCellLocation vcell_loc)767 gnucash_sheet_redraw_block (GnucashSheet *sheet, VirtualCellLocation vcell_loc)
768 {
769     gint x, y, w, h;
770     SheetBlock *block;
771     GtkAllocation alloc;
772 
773     g_return_if_fail (sheet != NULL);
774     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
775 
776     block = gnucash_sheet_get_block (sheet, vcell_loc);
777     if (!block || !block->style)
778         return;
779 
780     x = block->origin_x;
781     y = block->origin_y;
782 
783     gtk_widget_get_allocation (GTK_WIDGET(sheet), &alloc);
784     h = block->style->dimensions->height;
785     w = MIN(block->style->dimensions->width, alloc.width);
786 
787     gtk_widget_queue_draw_area (GTK_WIDGET(sheet), x, y, w + 1, h + 1);
788 }
789 
790 gboolean
gnucash_sheet_is_read_only(GnucashSheet * sheet)791 gnucash_sheet_is_read_only (GnucashSheet *sheet)
792 {
793     g_return_val_if_fail (sheet != NULL, TRUE);
794     g_return_val_if_fail (GNUCASH_IS_SHEET(sheet), TRUE);
795     return gnc_table_model_read_only (sheet->table->model);
796 }
797 
798 void
gnucash_sheet_set_has_focus(GnucashSheet * sheet,gboolean has_focus)799 gnucash_sheet_set_has_focus (GnucashSheet *sheet, gboolean has_focus)
800 {
801     sheet->sheet_has_focus = has_focus;
802 }
803 
804 static void
gnucash_sheet_finalize(GObject * object)805 gnucash_sheet_finalize (GObject *object)
806 {
807     GnucashSheet *sheet;
808 
809     sheet = GNUCASH_SHEET(object);
810 
811     g_table_resize (sheet->blocks, 0, 0);
812     g_table_destroy (sheet->blocks);
813     sheet->blocks = NULL;
814 
815     gnucash_sheet_clear_styles (sheet);
816 
817     g_hash_table_destroy (sheet->cursor_styles);
818     g_hash_table_destroy (sheet->dimensions_hash_table);
819 
820     if (G_OBJECT_CLASS(sheet_parent_class)->finalize)
821         (*G_OBJECT_CLASS(sheet_parent_class)->finalize)(object);
822 }
823 
824 
825 static GnucashSheet *
gnucash_sheet_create(Table * table)826 gnucash_sheet_create (Table *table)
827 {
828     GnucashSheet *sheet;
829 
830     ENTER("table=%p", table);
831 
832     sheet = g_object_new (GNUCASH_TYPE_SHEET, NULL);
833     sheet->table = table;
834     sheet->entry = NULL;
835     sheet->vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE(sheet));
836     sheet->hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE(sheet));
837 
838     g_signal_connect (G_OBJECT(sheet->vadj), "value_changed",
839                       G_CALLBACK(gnucash_sheet_vadjustment_value_changed), sheet);
840     g_signal_connect (G_OBJECT(sheet), "draw",
841                       G_CALLBACK(gnucash_sheet_draw_cb), sheet);
842 
843     LEAVE("%p", sheet);
844     return sheet;
845 }
846 
847 static void
gnucash_sheet_get_preferred_width(G_GNUC_UNUSED GtkWidget * widget,gint * minimal_width,gint * natural_width)848 gnucash_sheet_get_preferred_width (G_GNUC_UNUSED GtkWidget *widget,
849                                    gint *minimal_width,
850                                    gint *natural_width)
851 {
852     *minimal_width = *natural_width = DEFAULT_SHEET_WIDTH;
853 }
854 
855 
856 /* Compute the height needed to show DEFAULT_REGISTER_INITIAL_ROWS rows */
857 static void
gnucash_sheet_get_preferred_height(G_GNUC_UNUSED GtkWidget * widget,gint * minimal_width,gint * natural_width)858 gnucash_sheet_get_preferred_height (G_GNUC_UNUSED GtkWidget *widget,
859                                     gint *minimal_width,
860                                     gint *natural_width)
861 {
862     GnucashSheet *sheet = GNUCASH_SHEET(widget);
863     SheetBlockStyle *style;
864     CellDimensions *cd;
865     gint row_height;
866 
867     *minimal_width = *natural_width = DEFAULT_SHEET_HEIGHT;
868 
869     if (!sheet)
870         return;
871 
872     style = gnucash_sheet_get_style_from_cursor (sheet, CURSOR_HEADER);
873     if (!style)
874         return;
875 
876     cd = gnucash_style_get_cell_dimensions (style, 0, 0);
877     if (cd == NULL)
878         return;
879 
880     row_height = cd->pixel_height;
881 
882     *minimal_width = *natural_width =  row_height * DEFAULT_SHEET_INITIAL_ROWS;
883 }
884 
885 const char *
gnucash_sheet_modify_current_cell(GnucashSheet * sheet,const gchar * new_text)886 gnucash_sheet_modify_current_cell (GnucashSheet *sheet, const gchar *new_text)
887 {
888     GtkEditable *editable;
889     Table *table = sheet->table;
890     VirtualLocation virt_loc;
891     int new_text_len = 0;
892     const char *retval;
893     int cursor_position, start_sel, end_sel;
894 
895     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
896 
897     if (!gnc_table_virtual_loc_valid (table, virt_loc, TRUE))
898         return NULL;
899 
900     if (gnc_table_model_read_only (table->model))
901         return NULL;
902 
903     editable = GTK_EDITABLE(sheet->entry);
904 
905     cursor_position = gtk_editable_get_position (editable);
906     gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel);
907 
908     if (new_text)
909          new_text_len = strlen (new_text);
910 
911     retval = gnc_table_modify_update (table, virt_loc,
912                                       new_text, new_text_len,
913                                       new_text, new_text_len,
914                                       &cursor_position,
915                                       &start_sel, &end_sel,
916                                       NULL);
917 
918 
919     if (retval)
920     {
921         DEBUG("%s", retval ? retval : "nothing");
922         gnucash_sheet_set_entry_value (sheet, retval);
923         gnucash_sheet_set_position_and_selection (sheet, cursor_position,
924                                                   start_sel, end_sel);
925     }
926     return retval;
927 }
928 
929 typedef struct
930 {
931     GtkEditable *editable;
932     int start_sel;
933     int end_sel;
934 
935 } select_info;
936 
937 static gboolean
gnucash_sheet_direct_event(GnucashSheet * sheet,GdkEvent * event)938 gnucash_sheet_direct_event (GnucashSheet *sheet, GdkEvent *event)
939 {
940     GtkEditable *editable;
941     Table *table = sheet->table;
942     VirtualLocation virt_loc;
943     gboolean result;
944     char *new_text = NULL;
945     int cursor_position, start_sel, end_sel;
946     int new_position, new_start, new_end;
947 
948     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
949 
950     if (!gnc_table_virtual_loc_valid (table, virt_loc, TRUE))
951         return FALSE;
952 
953     if (gnc_table_model_read_only (table->model))
954         return FALSE;
955 
956     editable = GTK_EDITABLE(sheet->entry);
957 
958     cursor_position = gtk_editable_get_position (editable);
959     gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel);
960 
961     new_position = cursor_position;
962     new_start = start_sel;
963     new_end = end_sel;
964     result = gnc_table_direct_update (table, virt_loc,
965                                       &new_text,
966                                       &new_position,
967                                       &new_start, &new_end,
968                                       event);
969     if (result)
970     {
971         DEBUG("%s", new_text ? new_text : "nothing");
972         if (new_text != NULL)
973             gnucash_sheet_set_entry_value (sheet, new_text);
974         gnucash_sheet_set_position_and_selection (sheet, new_position,
975                                                   new_start, new_end);
976     }
977     return result;
978 }
979 
980 static inline void
normalize_selection_bounds(int * pos,int * bound,int length)981 normalize_selection_bounds (int *pos, int *bound, int length)
982 {
983     *bound = *bound < 0 ? length : *bound;
984     *pos = *pos < 0 ? length : *pos;
985 
986     if (*pos > *bound)
987     {
988         int temp = *pos;
989         *pos = *bound;
990         *bound = temp;
991     }
992 }
993 
994 static inline char*
insert_text(const char * old_text,const char * new_text,int pos,int bound)995 insert_text (const char* old_text, const char* new_text, int pos, int bound)
996 {
997     int old_len = g_utf8_strlen (old_text, -1);
998     char* begin = g_utf8_substring (old_text, 0, pos);
999     char* end = g_utf8_substring (old_text, bound, old_len);
1000     char *retval = g_strdup_printf ("%s%s%s", begin, new_text, end);
1001     g_free (begin);
1002     g_free (end);
1003     return retval;
1004 }
1005 
1006 static char*
make_new_text(GnucashSheet * sheet,const char * new_text,int * position)1007 make_new_text (GnucashSheet *sheet, const char* new_text, int *position)
1008 {
1009     GtkEditable* editable = (GTK_EDITABLE(sheet->entry));
1010     int pos, bound;
1011     const char* old_text = gtk_entry_get_text (GTK_ENTRY(sheet->entry));
1012     int old_length = g_utf8_strlen (old_text, -1);
1013     int insert_length = g_utf8_strlen (new_text, -1);
1014 
1015     if (!old_text || old_length == 0)
1016     {
1017         *position = insert_length;
1018         return g_strdup (new_text);
1019     }
1020 
1021     gtk_editable_get_selection_bounds (editable, &bound, &pos);
1022     normalize_selection_bounds (&pos, &bound, old_length);
1023 
1024     if (*position != pos)
1025         bound = pos = *position;
1026 
1027     if (pos == 0 && bound == old_length) // Full replacement
1028     {
1029         *position = insert_length;
1030         return g_strdup (new_text);
1031     }
1032 
1033     if (pos == bound)
1034     {
1035         if (pos == 0) //prepend
1036         {
1037             *position = insert_length;
1038             return g_strdup_printf ("%s%s", new_text, old_text);
1039         }
1040         else if (pos == old_length) //append
1041         {
1042             *position = old_length + insert_length;
1043             return g_strdup_printf ("%s%s", old_text, new_text);
1044         }
1045     }
1046 
1047     *position = pos + insert_length;
1048     return insert_text (old_text, new_text, pos, bound);
1049 }
1050 
1051 static void
gnucash_sheet_insert_cb(GtkEditable * editable,const gchar * insert_text,const gint insert_text_len,gint * position,GnucashSheet * sheet)1052 gnucash_sheet_insert_cb (GtkEditable *editable,
1053                          const gchar *insert_text,
1054                          const gint insert_text_len,
1055                          gint *position,
1056                          GnucashSheet *sheet)
1057 {
1058 
1059     Table *table = sheet->table;
1060     VirtualLocation virt_loc;
1061     char *new_text = NULL;
1062     glong new_text_len = 0;
1063     const char *retval;
1064     int start_sel = 0, end_sel = 0;
1065     int old_position = *position;
1066     const char* old_text = gtk_entry_get_text (GTK_ENTRY(sheet->entry));
1067 
1068     g_assert (GTK_WIDGET(editable) == sheet->entry);
1069     if (sheet->input_cancelled)
1070     {
1071         g_signal_stop_emission_by_name (G_OBJECT(sheet->entry),
1072                                         "insert_text");
1073         return;
1074     }
1075 
1076     if (insert_text_len <= 0)
1077         return;
1078 
1079     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
1080 
1081     if (!gnc_table_virtual_loc_valid (table, virt_loc, FALSE))
1082         return;
1083 
1084     if (gnc_table_model_read_only (table->model))
1085         return;
1086 
1087     new_text = make_new_text (sheet, insert_text, position);
1088     new_text_len = strlen (new_text);
1089 
1090 
1091     retval = gnc_table_modify_update (table, virt_loc,
1092                                       insert_text, insert_text_len,
1093                                       new_text, new_text_len,
1094                                       position, &start_sel, &end_sel,
1095                                       &sheet->input_cancelled);
1096 
1097     if (retval)
1098     {
1099         /* After the insert event the GtkEntry may handle signals from the
1100          * IMContext that would reset the selection, and we may need to keep it
1101          * so save it in the sheet values.
1102          */
1103         DEBUG("%s, got %s", new_text, retval);
1104         gnucash_sheet_set_position_and_selection (sheet, *position, start_sel,
1105                                                   end_sel);
1106 
1107         if ((strcmp (retval, new_text) != 0) || (*position != old_position))
1108         {
1109             gnucash_sheet_set_entry_value (sheet, retval);
1110             g_signal_stop_emission_by_name (G_OBJECT(sheet->entry),
1111                                             "insert_text");
1112         }
1113     }
1114     else if (retval == NULL)
1115     {
1116         retval = old_text;
1117 
1118         /* reset IMContext if disallowed chars */
1119         gtk_entry_reset_im_context (GTK_ENTRY(sheet->entry));
1120         /* the entry was disallowed, so we stop the insert signal */
1121         g_signal_stop_emission_by_name (G_OBJECT(sheet->entry),
1122                                         "insert_text");
1123     }
1124 }
1125 
1126 static char*
delete_text(GnucashSheet * sheet,int pos,int bound)1127 delete_text (GnucashSheet *sheet, int pos, int bound)
1128 {
1129     const char* old_text = gtk_entry_get_text (GTK_ENTRY(sheet->entry));
1130     int old_length = g_utf8_strlen (old_text, -1);
1131     char* begin, *end;
1132     char *retval = NULL;
1133 
1134     normalize_selection_bounds (&pos, &bound, old_length);
1135     if (pos == bound)
1136         return g_strdup (old_text); // Nothing to delete.
1137 
1138     if (pos == 0 && bound == old_length) // Full delete
1139         return g_strdup ("");
1140 
1141     if (bound == old_length)
1142         return g_utf8_substring (old_text, 0, pos);
1143 
1144     if (pos == 0)
1145         return g_utf8_substring (old_text, bound, old_length);
1146 
1147     begin = g_utf8_substring (old_text, 0, pos);
1148     end = g_utf8_substring (old_text, bound, old_length);
1149     retval = g_strdup_printf ("%s%s", begin, end);
1150     g_free (begin);
1151     g_free (end);
1152     return retval;
1153 }
1154 
1155 static void
gnucash_sheet_delete_cb(GtkWidget * widget,const gint start_pos,const gint end_pos,GnucashSheet * sheet)1156 gnucash_sheet_delete_cb (GtkWidget *widget,
1157                          const gint start_pos,
1158                          const gint end_pos,
1159                          GnucashSheet *sheet)
1160 {
1161     GtkEditable *editable;
1162     Table *table = sheet->table;
1163     VirtualLocation virt_loc;
1164     char *new_text = NULL;
1165     glong new_text_len;
1166     const char *retval;
1167     int cursor_position = start_pos;
1168     int start_sel, end_sel;
1169 
1170     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
1171 
1172     if (!gnc_table_virtual_loc_valid (table, virt_loc, FALSE))
1173         return;
1174 
1175     if (gnc_table_model_read_only (table->model))
1176         return;
1177 
1178     new_text = delete_text (sheet, start_pos, end_pos);
1179     new_text_len = strlen (new_text);
1180     editable = GTK_EDITABLE(sheet->entry);
1181     gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel);
1182     retval = gnc_table_modify_update (table, virt_loc,
1183                                       NULL, 0,
1184                                       new_text, new_text_len,
1185                                       &cursor_position,
1186                                       &start_sel, &end_sel,
1187                                       &sheet->input_cancelled);
1188 
1189     if (retval)
1190         gnucash_sheet_set_entry_value (sheet, retval);
1191 
1192     g_signal_stop_emission_by_name (G_OBJECT(sheet->entry), "delete_text");
1193 
1194     DEBUG("%s", retval ? retval : "nothing");
1195     gnucash_sheet_set_position_and_selection (sheet, cursor_position,
1196                                               start_sel, end_sel);
1197 }
1198 
1199 gboolean
gnucash_sheet_draw_cb(GtkWidget * widget,cairo_t * cr,G_GNUC_UNUSED gpointer data)1200 gnucash_sheet_draw_cb (GtkWidget *widget, cairo_t *cr, G_GNUC_UNUSED gpointer data)
1201 {
1202     GnucashSheet *sheet = GNUCASH_SHEET(widget);
1203     GtkStyleContext *context = gtk_widget_get_style_context (widget);
1204     GtkAllocation alloc;
1205 
1206     gtk_widget_get_allocation (widget, &alloc);
1207 
1208     gtk_style_context_save (context);
1209     gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
1210     gtk_render_background (context, cr, 0, 0, alloc.width, alloc.height);
1211     gtk_style_context_restore (context);
1212 
1213     gnucash_sheet_draw_internal (sheet, cr, &alloc);
1214     gnucash_sheet_draw_cursor (sheet->cursor, cr);
1215 
1216     return FALSE;
1217 }
1218 
1219 
1220 static void
gnucash_sheet_size_allocate(GtkWidget * widget,GtkAllocation * allocation)1221 gnucash_sheet_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
1222 {
1223     GnucashSheet *sheet = GNUCASH_SHEET(widget);
1224 
1225     ENTER("widget=%p, allocation=%p", widget, allocation);
1226 
1227     if (GTK_WIDGET_CLASS(sheet_parent_class)->size_allocate)
1228         (*GTK_WIDGET_CLASS(sheet_parent_class)->size_allocate)
1229         (widget, allocation);
1230 
1231     if (allocation->height == sheet->window_height &&
1232             allocation->width == sheet->window_width)
1233     {
1234         LEAVE("size unchanged");
1235         return;
1236     }
1237 
1238     if (allocation->width != sheet->window_width)
1239     {
1240         gnucash_sheet_styles_set_dimensions (sheet, allocation->width);
1241         gnucash_sheet_recompute_block_offsets (sheet);
1242     }
1243 
1244     sheet->window_height = allocation->height;
1245     sheet->window_width  = allocation->width;
1246 
1247     gnucash_cursor_configure (GNUCASH_CURSOR(sheet->cursor));
1248     gnc_header_reconfigure (GNC_HEADER(sheet->header_item));
1249     gnucash_sheet_set_scroll_region (sheet);
1250 
1251     gnc_item_edit_configure (GNC_ITEM_EDIT(sheet->item_editor));
1252     gnucash_sheet_update_adjustments (sheet);
1253 
1254     if (sheet->table)
1255     {
1256         VirtualLocation virt_loc;
1257 
1258         virt_loc = sheet->table->current_cursor_loc;
1259 
1260         if (gnucash_sheet_cell_valid (sheet, virt_loc))
1261             gnucash_sheet_show_row (sheet, virt_loc.vcell_loc.virt_row);
1262     }
1263     gnc_header_request_redraw (GNC_HEADER(sheet->header_item));
1264     LEAVE(" ");
1265 }
1266 
1267 static gboolean
gnucash_sheet_focus_in_event(GtkWidget * widget,GdkEventFocus * event)1268 gnucash_sheet_focus_in_event (GtkWidget *widget, GdkEventFocus *event)
1269 {
1270     GnucashSheet *sheet = GNUCASH_SHEET(widget);
1271 
1272     if (GTK_WIDGET_CLASS(sheet_parent_class)->focus_in_event)
1273         (*GTK_WIDGET_CLASS(sheet_parent_class)->focus_in_event)
1274         (widget, event);
1275 
1276     gnc_item_edit_focus_in (GNC_ITEM_EDIT(sheet->item_editor));
1277 
1278     return FALSE;
1279 }
1280 
1281 static gboolean
gnucash_sheet_focus_out_event(GtkWidget * widget,GdkEventFocus * event)1282 gnucash_sheet_focus_out_event (GtkWidget *widget, GdkEventFocus *event)
1283 {
1284     GnucashSheet *sheet = GNUCASH_SHEET(widget);
1285 
1286     if (GTK_WIDGET_CLASS(sheet_parent_class)->focus_out_event)
1287         (*GTK_WIDGET_CLASS(sheet_parent_class)->focus_out_event)
1288         (widget, event);
1289 
1290     gnc_item_edit_focus_out (GNC_ITEM_EDIT(sheet->item_editor));
1291     return FALSE;
1292 }
1293 
1294 static gboolean
gnucash_sheet_check_direct_update_cell(GnucashSheet * sheet,const VirtualLocation virt_loc)1295 gnucash_sheet_check_direct_update_cell (GnucashSheet *sheet,
1296                                         const VirtualLocation virt_loc)
1297 {
1298     const gchar *type_name;
1299 
1300     type_name = gnc_table_get_cell_type_name (sheet->table, virt_loc);
1301 
1302     if ( (g_strcmp0 (type_name, DATE_CELL_TYPE_NAME) == 0)
1303             || (g_strcmp0 (type_name, COMBO_CELL_TYPE_NAME) == 0)
1304             || (g_strcmp0 (type_name, NUM_CELL_TYPE_NAME) == 0)
1305             || (g_strcmp0 (type_name, PRICE_CELL_TYPE_NAME) == 0)
1306             || (g_strcmp0 (type_name, FORMULA_CELL_TYPE_NAME) == 0)) return TRUE;
1307 
1308     return FALSE;
1309 }
1310 
1311 static void
gnucash_sheet_start_editing_at_cursor(GnucashSheet * sheet)1312 gnucash_sheet_start_editing_at_cursor (GnucashSheet *sheet)
1313 {
1314     const char *text;
1315     VirtualLocation virt_loc;
1316 
1317     g_return_if_fail (sheet != NULL);
1318     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
1319 
1320     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
1321 
1322     text = gnc_table_get_entry (sheet->table, virt_loc);
1323 
1324     gnc_item_edit_configure (GNC_ITEM_EDIT(sheet->item_editor));
1325     gtk_widget_show (GTK_WIDGET(sheet->item_editor));
1326 
1327     gtk_entry_set_text (GTK_ENTRY(sheet->entry), text);
1328 
1329     sheet->editing = TRUE;
1330 
1331     /* set up the signals */
1332     sheet->insert_signal =
1333         g_signal_connect (G_OBJECT(sheet->entry), "insert_text",
1334                           G_CALLBACK(gnucash_sheet_insert_cb), sheet);
1335     sheet->delete_signal =
1336         g_signal_connect (G_OBJECT(sheet->entry), "delete_text",
1337                           G_CALLBACK(gnucash_sheet_delete_cb), sheet);
1338 }
1339 
1340 static gboolean
gnucash_sheet_button_release_event(GtkWidget * widget,GdkEventButton * event)1341 gnucash_sheet_button_release_event (GtkWidget *widget, GdkEventButton *event)
1342 {
1343     GnucashSheet *sheet;
1344 
1345     g_return_val_if_fail (widget != NULL, TRUE);
1346     g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1347     g_return_val_if_fail (event != NULL, TRUE);
1348 
1349     sheet = GNUCASH_SHEET(widget);
1350 
1351     if (sheet->button != event->button)
1352         return FALSE;
1353 
1354     sheet->button = 0;
1355 
1356     if (event->button != 1)
1357         return FALSE;
1358 
1359     gtk_grab_remove (widget);
1360     sheet->grabbed = FALSE;
1361 
1362     return TRUE;
1363 }
1364 
1365 static gboolean
gnucash_scroll_event(GtkWidget * widget,GdkEventScroll * event)1366 gnucash_scroll_event (GtkWidget *widget, GdkEventScroll *event)
1367 {
1368     GnucashSheet *sheet;
1369     GtkAdjustment *vadj;
1370     gfloat v_value;
1371 
1372     g_return_val_if_fail (widget != NULL, TRUE);
1373     g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1374     g_return_val_if_fail (event != NULL, TRUE);
1375 
1376     sheet = GNUCASH_SHEET(widget);
1377     vadj = sheet->vadj;
1378     v_value = gtk_adjustment_get_value (vadj);
1379 
1380     switch (event->direction)
1381     {
1382     case GDK_SCROLL_UP:
1383         v_value -= gtk_adjustment_get_step_increment (vadj);
1384         break;
1385     case GDK_SCROLL_DOWN:
1386         v_value += gtk_adjustment_get_step_increment (vadj);
1387         break;
1388     case GDK_SCROLL_SMOOTH:
1389         if (event->delta_y < 0)
1390             v_value -= gtk_adjustment_get_step_increment (vadj);
1391         if (event->delta_y > 0)
1392             v_value += gtk_adjustment_get_step_increment (vadj);
1393         break;
1394     default:
1395         return FALSE;
1396     }
1397     v_value = CLAMP(v_value, gtk_adjustment_get_lower (vadj),
1398               gtk_adjustment_get_upper (vadj) - gtk_adjustment_get_page_size (vadj));
1399 
1400     gtk_adjustment_set_value (vadj, v_value);
1401 
1402     if (event->delta_y == 0)
1403     {
1404         /* There are problems with the slider not tracking the value so
1405            when delta_y is 0 hide and showing the scrollbar seems to fix it
1406            observed when using mouse wheel on sheet after a page-up or down */
1407         gtk_widget_hide (GTK_WIDGET(sheet->vscrollbar));
1408         gtk_widget_show (GTK_WIDGET(sheet->vscrollbar));
1409     }
1410     return TRUE;
1411 }
1412 
1413 static void
gnucash_sheet_check_grab(GnucashSheet * sheet)1414 gnucash_sheet_check_grab (GnucashSheet *sheet)
1415 {
1416     GdkModifierType mods;
1417     GdkDevice *device;
1418     GdkSeat *seat;
1419     GdkWindow *window;
1420 
1421     if (!sheet->grabbed)
1422         return;
1423 
1424     window = gtk_widget_get_window (GTK_WIDGET(sheet));
1425 
1426     seat = gdk_display_get_default_seat (gdk_window_get_display (window));
1427     device = gdk_seat_get_pointer (seat);
1428 
1429     gdk_device_get_state (device, window, 0, &mods);
1430 
1431     if (!(mods & GDK_BUTTON1_MASK))
1432     {
1433         gtk_grab_remove (GTK_WIDGET(sheet));
1434         sheet->grabbed = FALSE;
1435     }
1436 }
1437 
1438 static gboolean
gnucash_sheet_button_press_event(GtkWidget * widget,GdkEventButton * event)1439 gnucash_sheet_button_press_event (GtkWidget *widget, GdkEventButton *event)
1440 {
1441     GnucashSheet *sheet;
1442     VirtualCell *vcell;
1443     VirtualLocation cur_virt_loc;
1444     VirtualLocation new_virt_loc;
1445     Table *table;
1446     gboolean abort_move;
1447     gboolean button_1;
1448     gboolean do_popup;
1449 
1450     g_return_val_if_fail (widget != NULL, TRUE);
1451     g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1452     g_return_val_if_fail (event != NULL, TRUE);
1453 
1454     sheet = GNUCASH_SHEET(widget);
1455     table = sheet->table;
1456 
1457     if (sheet->button && (sheet->button != event->button))
1458         return FALSE;
1459 
1460     sheet->button = event->button;
1461     if (sheet->button == 3)
1462         sheet->button = 0;
1463 
1464     if (!gtk_widget_has_focus (widget))
1465         gtk_widget_grab_focus (widget);
1466 
1467     button_1 = FALSE;
1468     do_popup = FALSE;
1469 
1470     switch (event->button)
1471     {
1472     case 1:
1473         button_1 = TRUE;
1474         break;
1475     case 2:
1476         if (event->type != GDK_BUTTON_PRESS)
1477             return FALSE;
1478         gnc_item_edit_paste_clipboard (GNC_ITEM_EDIT(sheet->item_editor));
1479         return TRUE;
1480     case 3:
1481         do_popup = (sheet->popup != NULL);
1482         break;
1483     default:
1484         return FALSE;
1485     }
1486 
1487     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &cur_virt_loc);
1488 
1489     sheet->button_x = -1;
1490     sheet->button_y = -1;
1491 
1492     if (!gnucash_sheet_find_loc_by_pixel (sheet, event->x, event->y,
1493                                           &new_virt_loc))
1494         return TRUE;
1495 
1496     sheet->button_x = event->x;
1497     sheet->button_y = event->y;
1498 
1499     vcell = gnc_table_get_virtual_cell (table, new_virt_loc.vcell_loc);
1500     if (vcell == NULL)
1501         return TRUE;
1502 
1503     if (event->type != GDK_BUTTON_PRESS)
1504         return FALSE;
1505 
1506     if (button_1)
1507     {
1508         gtk_grab_add (widget);
1509         sheet->grabbed = TRUE;
1510     }
1511 
1512     if (virt_loc_equal (new_virt_loc, cur_virt_loc) &&
1513         sheet->editing && do_popup)
1514     {
1515         gtk_menu_popup_at_pointer (GTK_MENU(sheet->popup), (GdkEvent *) event);
1516         return TRUE;
1517     }
1518 
1519     /* and finally...process this as a POINTER_TRAVERSE */
1520     abort_move = gnc_table_traverse_update (table,
1521                                             cur_virt_loc,
1522                                             GNC_TABLE_TRAVERSE_POINTER,
1523                                             &new_virt_loc);
1524 
1525     if (button_1)
1526         gnucash_sheet_check_grab (sheet);
1527 
1528     if (abort_move)
1529         return TRUE;
1530 
1531     gnucash_sheet_cursor_move (sheet, new_virt_loc);
1532 
1533     // if clicked in document link cell, run call back
1534     if (g_strcmp0 (gnc_table_get_cell_name (table, new_virt_loc), DOCLINK_CELL) == 0)
1535     {
1536         if (sheet->open_doclink_cb)
1537             (sheet->open_doclink_cb)(sheet->open_doclink_cb_data, NULL);
1538     }
1539 
1540     if (button_1)
1541         gnucash_sheet_check_grab (sheet);
1542 
1543     if (do_popup)
1544         gtk_menu_popup_at_pointer (GTK_MENU(sheet->popup), (GdkEvent *) event);
1545 
1546     return button_1 || do_popup;
1547 }
1548 
1549 void
gnucash_sheet_refresh_from_prefs(GnucashSheet * sheet)1550 gnucash_sheet_refresh_from_prefs (GnucashSheet *sheet)
1551 {
1552     GtkStyleContext *stylectxt;
1553     GncItemEdit *item_edit;
1554     GList *classes = NULL;
1555 
1556     g_return_if_fail (sheet != NULL);
1557     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
1558 
1559     sheet->use_gnc_color_theme = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
1560                                                      GNC_PREF_USE_GNUCASH_COLOR_THEME);
1561     sheet->use_horizontal_lines = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
1562                                                       GNC_PREF_DRAW_HOR_LINES);
1563     sheet->use_vertical_lines = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
1564                                                     GNC_PREF_DRAW_VERT_LINES);
1565 
1566     item_edit = GNC_ITEM_EDIT(sheet->item_editor);
1567 
1568     stylectxt = gtk_widget_get_style_context (GTK_WIDGET(item_edit->editor));
1569 
1570     // Get the CSS classes for the editor
1571     classes = gtk_style_context_list_classes (stylectxt);
1572 
1573     for (GList *l = classes; l; l = l->next)
1574     {
1575         if (g_str_has_prefix (l->data, "gnc-class-"))
1576             gtk_style_context_remove_class (stylectxt, l->data);
1577     }
1578     g_list_free (classes);
1579 
1580     gtk_style_context_remove_class (stylectxt, GTK_STYLE_CLASS_VIEW);
1581 
1582     // Note: COLOR_PRIMARY_ACTIVE, COLOR_SECONDARY_ACTIVE, COLOR_SPLIT_ACTIVE
1583     // all equate to *-cursor style class used for the editor
1584     gnucash_get_style_classes (sheet, stylectxt, COLOR_PRIMARY_ACTIVE, FALSE);
1585 }
1586 
1587 static gboolean
gnucash_sheet_clipboard_event(GnucashSheet * sheet,GdkEventKey * event)1588 gnucash_sheet_clipboard_event (GnucashSheet *sheet, GdkEventKey *event)
1589 {
1590     GncItemEdit *item_edit;
1591     gboolean handled = FALSE;
1592 
1593     item_edit = GNC_ITEM_EDIT(sheet->item_editor);
1594 
1595     switch (event->keyval)
1596     {
1597     case GDK_KEY_C:
1598     case GDK_KEY_c:
1599         if (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR)
1600         {
1601             gnc_item_edit_copy_clipboard (item_edit);
1602             handled = TRUE;
1603         }
1604         break;
1605     case GDK_KEY_X:
1606     case GDK_KEY_x:
1607         if (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR)
1608         {
1609             gnc_item_edit_cut_clipboard (item_edit);
1610             handled = TRUE;
1611         }
1612         break;
1613     case GDK_KEY_V:
1614     case GDK_KEY_v:
1615         if (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR)
1616         {
1617             gnc_item_edit_paste_clipboard (item_edit);
1618             handled = TRUE;
1619         }
1620         break;
1621     case GDK_KEY_Insert:
1622         if (event->state & GDK_SHIFT_MASK)
1623         {
1624             gnc_item_edit_paste_clipboard (item_edit);
1625             handled = TRUE;
1626         }
1627         else if (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR)
1628         {
1629             gnc_item_edit_copy_clipboard (item_edit);
1630             handled = TRUE;
1631         }
1632         break;
1633     }
1634     return handled;
1635 }
1636 
1637 static void
gnucash_sheet_need_horizontal_scroll(GnucashSheet * sheet,VirtualLocation * new_virt_loc)1638 gnucash_sheet_need_horizontal_scroll (GnucashSheet *sheet,
1639                                       VirtualLocation *new_virt_loc)
1640 {
1641     gint hscroll_val;
1642     gint cell_width = 0;
1643     gint offset;
1644 
1645     if (sheet->window_width == sheet->width)
1646         return;
1647 
1648     // get the horizontal scroll window value
1649     hscroll_val = (gint) gtk_adjustment_get_value (sheet->hadj);
1650 
1651     // offset is the start of the cell for column
1652     offset = gnc_header_get_cell_offset (GNC_HEADER(sheet->header_item),
1653                                          new_virt_loc->phys_col_offset, &cell_width);
1654 
1655     if (((offset + cell_width) > sheet->window_width) || (offset < hscroll_val))
1656         gtk_adjustment_set_value (sheet->hadj, offset);
1657 }
1658 
1659 static gboolean
process_motion_keys(GnucashSheet * sheet,GdkEventKey * event,gboolean * pass_on,gncTableTraversalDir * direction,VirtualLocation * new_virt_loc)1660 process_motion_keys (GnucashSheet *sheet, GdkEventKey *event, gboolean *pass_on,
1661                      gncTableTraversalDir *direction,
1662                      VirtualLocation* new_virt_loc)
1663 {
1664     int distance;
1665     VirtualLocation cur_virt_loc = *new_virt_loc;
1666 
1667     switch (event->keyval)
1668     {
1669         case GDK_KEY_Return:
1670         case GDK_KEY_KP_Enter:
1671             g_signal_emit_by_name (sheet->reg, "activate_cursor");
1672             /* Clear the saved selection. */
1673             sheet->pos = sheet->bound;
1674             return TRUE;
1675             break;
1676         case GDK_KEY_Tab:
1677         case GDK_KEY_ISO_Left_Tab:
1678             if (event->state & GDK_SHIFT_MASK)
1679             {
1680                 *direction = GNC_TABLE_TRAVERSE_LEFT;
1681                 gnc_table_move_tab (sheet->table, new_virt_loc, FALSE);
1682             }
1683             else
1684             {
1685                 *direction = GNC_TABLE_TRAVERSE_RIGHT;
1686                 gnc_table_move_tab (sheet->table, new_virt_loc, TRUE);
1687             }
1688             break;
1689         case GDK_KEY_KP_Page_Up:
1690         case GDK_KEY_Page_Up:
1691             *direction = GNC_TABLE_TRAVERSE_UP;
1692             new_virt_loc->phys_col_offset = 0;
1693             if (event->state & GDK_SHIFT_MASK)
1694                 new_virt_loc->vcell_loc.virt_row = 1;
1695             else
1696             {
1697                 distance = sheet->num_visible_phys_rows - 1;
1698                 gnc_table_move_vertical_position
1699                     (sheet->table, new_virt_loc, -distance);
1700             }
1701             break;
1702         case GDK_KEY_KP_Page_Down:
1703         case GDK_KEY_Page_Down:
1704             *direction = GNC_TABLE_TRAVERSE_DOWN;
1705             new_virt_loc->phys_col_offset = 0;
1706             if (event->state & GDK_SHIFT_MASK)
1707                 new_virt_loc->vcell_loc.virt_row =
1708                     sheet->num_virt_rows - 1;
1709             else
1710             {
1711                 distance = sheet->num_visible_phys_rows - 1;
1712                 gnc_table_move_vertical_position
1713                     (sheet->table, new_virt_loc, distance);
1714             }
1715             break;
1716         case GDK_KEY_KP_Up:
1717         case GDK_KEY_Up:
1718             *direction = GNC_TABLE_TRAVERSE_UP;
1719             gnc_table_move_vertical_position (sheet->table,
1720                                               new_virt_loc, -1);
1721             break;
1722         case GDK_KEY_KP_Down:
1723         case GDK_KEY_Down:
1724         case GDK_KEY_Menu:
1725             if (event->keyval == GDK_KEY_Menu ||
1726                 (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR))
1727             {
1728                 GncItemEdit *item_edit = GNC_ITEM_EDIT(sheet->item_editor);
1729 
1730                 if (gnc_table_confirm_change (sheet->table, cur_virt_loc))
1731                     gnc_item_edit_show_popup (item_edit);
1732 
1733                 /* Clear the saved selection for the new cell. */
1734                 sheet->pos = sheet->bound;
1735                 return TRUE;
1736             }
1737 
1738             *direction = GNC_TABLE_TRAVERSE_DOWN;
1739             gnc_table_move_vertical_position (sheet->table,
1740                                               new_virt_loc, 1);
1741             break;
1742         case GDK_KEY_KP_Right:
1743         case GDK_KEY_Right:
1744         case GDK_KEY_KP_Left:
1745         case GDK_KEY_Left:
1746         case GDK_KEY_Home:
1747         case GDK_KEY_End:
1748             /* Clear the saved selection, we're not using it. */
1749             sheet->pos = sheet->bound;
1750             *pass_on = TRUE;
1751             break;
1752         default:
1753             if (gnucash_sheet_clipboard_event (sheet, event))
1754             {
1755                 /* Clear the saved selection. */
1756                 sheet->pos = sheet->bound;
1757                 return TRUE;
1758             }
1759             *pass_on = TRUE;
1760             break;
1761     }
1762     // does the sheet need horizontal scrolling due to tab
1763     gnucash_sheet_need_horizontal_scroll (sheet, new_virt_loc);
1764 
1765     return FALSE;
1766 }
1767 
1768 static gboolean
pass_to_entry_handler(GnucashSheet * sheet,GdkEventKey * event)1769 pass_to_entry_handler (GnucashSheet *sheet, GdkEventKey *event)
1770 {
1771     gboolean result = FALSE;
1772     GtkEditable *editable = GTK_EDITABLE(sheet->entry);
1773 
1774     // If sheet is readonly, entry is not realized
1775     if (gtk_widget_get_realized (GTK_WIDGET(editable)))
1776     {
1777         result = gtk_widget_event (GTK_WIDGET(editable), (GdkEvent*)event);
1778         gnucash_sheet_set_selection_from_entry (sheet);
1779     }
1780     return result;
1781 }
1782 
1783 static gint
gnucash_sheet_key_press_event_internal(GtkWidget * widget,GdkEventKey * event)1784 gnucash_sheet_key_press_event_internal (GtkWidget *widget, GdkEventKey *event)
1785 {
1786     Table *table;
1787     GnucashSheet *sheet;
1788     gboolean pass_on = FALSE;
1789     gboolean abort_move;
1790     VirtualLocation cur_virt_loc;
1791     VirtualLocation new_virt_loc;
1792     gncTableTraversalDir direction = 0;
1793     int distance;
1794     GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask ();
1795 
1796     g_return_val_if_fail (widget != NULL, TRUE);
1797     g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1798     g_return_val_if_fail (event != NULL, TRUE);
1799 
1800     sheet = GNUCASH_SHEET(widget);
1801     table = sheet->table;
1802     /* Don't respond to stand-alone modifier keys. */
1803     if (event->is_modifier)
1804         return TRUE;
1805     /* Initially sync the selection, the user might have adjusted it with the
1806      * mouse.
1807      */
1808     gnucash_sheet_set_selection_from_entry (sheet);
1809     /* Direct_event gets first whack */
1810     if (gnucash_sheet_direct_event (sheet, (GdkEvent *) event))
1811         return TRUE;
1812     /* Followed by the input method */
1813     if (gtk_entry_im_context_filter_keypress (GTK_ENTRY(sheet->entry), event))
1814     {
1815         /* Restore the saved cursor position in case GtkEntry's IMContext
1816          * handlers messed with it after we set it in our insert_cb.
1817          */
1818         gnucash_sheet_set_entry_selection (sheet);
1819         return TRUE;
1820     }
1821 
1822     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &cur_virt_loc);
1823     new_virt_loc = cur_virt_loc;
1824 
1825     /* Don't process any keystrokes where a modifier key (Alt, Meta, etc.) is
1826      * being held down.  This shouldn't include NUM LOCK.
1827      */
1828     if (event->state & modifiers & (GDK_MODIFIER_INTENT_DEFAULT_MOD_MASK))
1829         pass_on = TRUE;
1830     else if (process_motion_keys (sheet, event, &pass_on,
1831                                   &direction, &new_virt_loc)) //may set pass_on
1832             return TRUE;
1833 
1834     /* Forward the keystroke to the input line */
1835     if (pass_on)
1836     {
1837         return pass_to_entry_handler (sheet, event);
1838     }
1839 
1840     abort_move = gnc_table_traverse_update (table, cur_virt_loc,
1841                                             direction, &new_virt_loc);
1842 
1843     /* If that would leave the register, abort */
1844     if (abort_move)
1845     {
1846         // Make sure the sheet is the focus
1847         if (!gtk_widget_has_focus (GTK_WIDGET(sheet)))
1848             gtk_widget_grab_focus (GTK_WIDGET(sheet));
1849         return TRUE;
1850     }
1851 
1852     /* Clear the saved selection for the new cell. */
1853     sheet->pos = sheet->bound;
1854     gnucash_sheet_cursor_move (sheet, new_virt_loc);
1855 
1856     /* return true because we handled the key press */
1857     return TRUE;
1858 }
1859 
1860 static gint
gnucash_sheet_key_press_event(GtkWidget * widget,GdkEventKey * event)1861 gnucash_sheet_key_press_event (GtkWidget *widget, GdkEventKey *event)
1862 {
1863     GnucashSheet *sheet;
1864     GtkEditable *editable = NULL;
1865 
1866     g_return_val_if_fail (widget != NULL, TRUE);
1867     g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1868     g_return_val_if_fail (event != NULL, TRUE);
1869 
1870     sheet = GNUCASH_SHEET(widget);
1871     editable = GTK_EDITABLE(sheet->entry);
1872     /* bug#60582 comment#27 2
1873            save shift state to enable <shift minus> and <shift equal>
1874        bug#618434
1875            save keyval to handle GDK_KEY_KP_Decimal event
1876      */
1877 #ifdef G_OS_WIN32
1878     /* gdk never sends GDK_KEY_KP_Decimal on win32. See #486658 */
1879     if (event->hardware_keycode == VK_DECIMAL)
1880         event->keyval = GDK_KEY_KP_Decimal;
1881 #endif
1882     sheet->shift_state = event->state & GDK_SHIFT_MASK;
1883     sheet->keyval_state =
1884         (event->keyval == GDK_KEY_KP_Decimal) ? GDK_KEY_KP_Decimal : 0;
1885 
1886     return gnucash_sheet_key_press_event_internal (widget, event);
1887 }
1888 
1889 static gint
gnucash_sheet_key_release_event(GtkWidget * widget,GdkEventKey * event)1890 gnucash_sheet_key_release_event (GtkWidget *widget, GdkEventKey *event)
1891 {
1892     GnucashSheet *sheet;
1893 
1894     g_return_val_if_fail (widget != NULL, TRUE);
1895     g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1896     g_return_val_if_fail (event != NULL, TRUE);
1897 
1898     return FALSE;
1899 }
1900 
1901 
1902 void
gnucash_sheet_goto_virt_loc(GnucashSheet * sheet,VirtualLocation virt_loc)1903 gnucash_sheet_goto_virt_loc (GnucashSheet *sheet, VirtualLocation virt_loc)
1904 {
1905     Table *table;
1906     gboolean abort_move;
1907     VirtualLocation cur_virt_loc;
1908 
1909     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
1910 
1911     table = sheet->table;
1912 
1913     gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &cur_virt_loc);
1914 
1915     /* It's not really a pointer traverse, but it seems the most
1916      * appropriate here. */
1917     abort_move = gnc_table_traverse_update (table, cur_virt_loc,
1918                                             GNC_TABLE_TRAVERSE_POINTER,
1919                                             &virt_loc);
1920 
1921     if (abort_move)
1922         return;
1923 
1924     // does the sheet need horizontal scrolling
1925     gnucash_sheet_need_horizontal_scroll (sheet, &virt_loc);
1926 
1927     gnucash_sheet_cursor_move (sheet, virt_loc);
1928 }
1929 
1930 SheetBlock *
gnucash_sheet_get_block(GnucashSheet * sheet,VirtualCellLocation vcell_loc)1931 gnucash_sheet_get_block (GnucashSheet *sheet, VirtualCellLocation vcell_loc)
1932 {
1933     g_return_val_if_fail (sheet != NULL, NULL);
1934     g_return_val_if_fail (GNUCASH_IS_SHEET(sheet), NULL);
1935 
1936     return g_table_index (sheet->blocks,
1937                           vcell_loc.virt_row,
1938                           vcell_loc.virt_col);
1939 }
1940 
gnucash_sheet_get_item_edit(GnucashSheet * sheet)1941 GncItemEdit *gnucash_sheet_get_item_edit (GnucashSheet *sheet)
1942 {
1943     g_return_val_if_fail (sheet != NULL, NULL);
1944     g_return_val_if_fail (GNUCASH_IS_SHEET(sheet), NULL);
1945 
1946     if (sheet->item_editor == NULL)
1947         return NULL;
1948     else
1949         return GNC_ITEM_EDIT(sheet->item_editor);
1950 }
1951 
1952 
gnucash_sheet_set_window(GnucashSheet * sheet,GtkWidget * window)1953 void gnucash_sheet_set_window (GnucashSheet *sheet, GtkWidget *window)
1954 {
1955     g_return_if_fail (sheet != NULL);
1956     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
1957 
1958     if (window)
1959         g_return_if_fail (GTK_IS_WIDGET(window));
1960 
1961     sheet->window = window;
1962 }
1963 
1964 
1965 /* This fills up a block from the table; it sets the style and returns
1966  * true if the style changed. */
1967 gboolean
gnucash_sheet_block_set_from_table(GnucashSheet * sheet,VirtualCellLocation vcell_loc)1968 gnucash_sheet_block_set_from_table (GnucashSheet *sheet,
1969                                     VirtualCellLocation vcell_loc)
1970 {
1971     Table *table;
1972     SheetBlock *block;
1973     SheetBlockStyle *style;
1974     VirtualCell *vcell;
1975 
1976     block = gnucash_sheet_get_block (sheet, vcell_loc);
1977     style = gnucash_sheet_get_style_from_table (sheet, vcell_loc);
1978 
1979     if (!block)
1980         return FALSE;
1981 
1982     table = sheet->table;
1983 
1984     vcell = gnc_table_get_virtual_cell (table, vcell_loc);
1985 
1986     if (block->style && (block->style != style))
1987     {
1988         gnucash_sheet_style_unref (sheet, block->style);
1989         block->style = NULL;
1990     }
1991 
1992     block->visible = (vcell) ? vcell->visible : TRUE;
1993 
1994     if (block->style == NULL)
1995     {
1996         block->style = style;
1997         gnucash_sheet_style_ref (sheet, block->style);
1998         return TRUE;
1999     }
2000     return FALSE;
2001 }
2002 
2003 
2004 gint
gnucash_sheet_col_max_width(GnucashSheet * sheet,gint virt_col,gint cell_col)2005 gnucash_sheet_col_max_width (GnucashSheet *sheet, gint virt_col, gint cell_col)
2006 {
2007     int virt_row;
2008     int cell_row;
2009     int max = 0;
2010     int width;
2011     SheetBlock *block;
2012     SheetBlockStyle *style;
2013     PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), "");
2014     GncItemEdit *item_edit = GNC_ITEM_EDIT(sheet->item_editor);
2015     const gchar *type_name;
2016 
2017     g_return_val_if_fail (virt_col >= 0, 0);
2018     g_return_val_if_fail (virt_col < sheet->num_virt_cols, 0);
2019     g_return_val_if_fail (cell_col >= 0, 0);
2020 
2021     for (virt_row = 0; virt_row < sheet->num_virt_rows ; virt_row++)
2022     {
2023         VirtualCellLocation vcell_loc = { virt_row, virt_col };
2024 
2025         block = gnucash_sheet_get_block (sheet, vcell_loc);
2026         if (!block)
2027             continue;
2028 
2029         style = block->style;
2030 
2031         if (!style)
2032             continue;
2033 
2034         if (cell_col < style->ncols)
2035         {
2036             for (cell_row = 0; cell_row < style->nrows; cell_row++)
2037             {
2038                 VirtualLocation virt_loc;
2039                 const char *text;
2040 
2041                 if (virt_row == 0)
2042                     virt_loc.vcell_loc = sheet->table->current_cursor_loc.vcell_loc;
2043                 else
2044                     virt_loc.vcell_loc = vcell_loc;
2045 
2046                 virt_loc.phys_row_offset = cell_row;
2047                 virt_loc.phys_col_offset = cell_col;
2048 
2049                 if (virt_row == 0)
2050                 {
2051                     text = gnc_table_get_label
2052                            (sheet->table, virt_loc);
2053                 }
2054                 else
2055                 {
2056                     text = gnc_table_get_entry
2057                            (sheet->table, virt_loc);
2058                 }
2059 
2060                 pango_layout_set_text (layout, text, strlen (text));
2061                 pango_layout_get_pixel_size (layout, &width, NULL);
2062 
2063                 width += (gnc_item_edit_get_margin (item_edit, left_right) +
2064                           gnc_item_edit_get_padding_border (item_edit, left_right));
2065 
2066                 // get the cell type so we can add the button width to the
2067                 // text width if required.
2068                 type_name = gnc_table_get_cell_type_name (sheet->table, virt_loc);
2069                 if ((g_strcmp0 (type_name, DATE_CELL_TYPE_NAME) == 0)
2070                     || (g_strcmp0 (type_name, COMBO_CELL_TYPE_NAME) == 0))
2071                 {
2072                     width += gnc_item_edit_get_button_width (item_edit) + 2; // add 2 for the button margin
2073                 }
2074                 max = MAX(max, width);
2075             }
2076         }
2077     }
2078 
2079     g_object_unref (layout);
2080 
2081     return max;
2082 }
2083 
2084 void
gnucash_sheet_set_scroll_region(GnucashSheet * sheet)2085 gnucash_sheet_set_scroll_region (GnucashSheet *sheet)
2086 {
2087     guint new_h, new_w;
2088     GtkAllocation alloc;
2089     guint old_h, old_w;
2090 
2091     if (!sheet)
2092         return;
2093 
2094     if (!sheet->header_item || !GNC_HEADER(sheet->header_item)->style)
2095         return;
2096 
2097     gtk_layout_get_size (GTK_LAYOUT(sheet), &old_w, &old_h);
2098 
2099     gtk_widget_get_allocation (GTK_WIDGET(sheet), &alloc);
2100     new_h = MAX(sheet->height, alloc.height);
2101     new_w  = MAX(sheet->width, alloc.width);
2102 
2103     if (new_w != old_w || new_h != old_h)
2104         gtk_layout_set_size (GTK_LAYOUT(sheet), new_w, new_h);
2105 }
2106 
2107 static void
gnucash_sheet_block_destroy(gpointer _block,gpointer user_data)2108 gnucash_sheet_block_destroy (gpointer _block, gpointer user_data)
2109 {
2110     SheetBlock *block = _block;
2111     GnucashSheet *sheet = GNUCASH_SHEET(user_data);
2112 
2113     if (block == NULL)
2114         return;
2115 
2116     if (block->style)
2117     {
2118         gnucash_sheet_style_unref (sheet, block->style);
2119         /* Don't free the block itself here. It's managed by the block table */
2120     }
2121 }
2122 
2123 static void
gnucash_sheet_block_construct(gpointer _block,gpointer user_data)2124 gnucash_sheet_block_construct (gpointer _block, gpointer user_data)
2125 {
2126     SheetBlock *block = _block;
2127 
2128     block->style = NULL;
2129     block->visible = TRUE;
2130 }
2131 
2132 static void
gnucash_sheet_resize(GnucashSheet * sheet)2133 gnucash_sheet_resize (GnucashSheet *sheet)
2134 {
2135     g_return_if_fail (sheet != NULL);
2136     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
2137 
2138     if (sheet->table->num_virt_cols > 1)
2139         g_warning ("num_virt_cols > 1");
2140 
2141     sheet->num_virt_cols = 1;
2142 
2143     g_table_resize (sheet->blocks, sheet->table->num_virt_rows, 1);
2144 
2145     sheet->num_virt_rows = sheet->table->num_virt_rows;
2146 }
2147 
2148 void
gnucash_sheet_recompute_block_offsets(GnucashSheet * sheet)2149 gnucash_sheet_recompute_block_offsets (GnucashSheet *sheet)
2150 {
2151     Table *table;
2152     SheetBlock *block;
2153     gint i, j;
2154     gint height;
2155     gint width;
2156 
2157     g_return_if_fail (sheet != NULL);
2158     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
2159     g_return_if_fail (sheet->table != NULL);
2160 
2161     table = sheet->table;
2162 
2163     height = 0;
2164     block = NULL;
2165     for (i = 0; i < table->num_virt_rows; i++)
2166     {
2167         width = 0;
2168 
2169         for (j = 0; j < table->num_virt_cols; j++)
2170         {
2171             VirtualCellLocation vcell_loc = { i, j };
2172 
2173             block = gnucash_sheet_get_block (sheet, vcell_loc);
2174 
2175             if (!block)
2176                 continue;
2177 
2178             block->origin_x = width;
2179             block->origin_y = height;
2180 
2181             if (block->visible)
2182                 width += block->style->dimensions->width;
2183         }
2184 
2185         if (i > 0 && block && block->visible)
2186             height += block->style->dimensions->height;
2187     }
2188     sheet->height = height;
2189 }
2190 
2191 void
gnucash_sheet_table_load(GnucashSheet * sheet,gboolean do_scroll)2192 gnucash_sheet_table_load (GnucashSheet *sheet, gboolean do_scroll)
2193 {
2194     Table *table;
2195     gint num_header_phys_rows;
2196     gint i, j;
2197 
2198     g_return_if_fail (sheet != NULL);
2199     g_return_if_fail (GNUCASH_IS_SHEET(sheet));
2200     g_return_if_fail (sheet->table != NULL);
2201 
2202     table = sheet->table;
2203 
2204     gnucash_sheet_stop_editing (sheet);
2205 
2206     gnucash_sheet_resize (sheet);
2207 
2208     num_header_phys_rows = 0;
2209 
2210     /* fill it up */
2211     for (i = 0; i < table->num_virt_rows; i++)
2212         for (j = 0; j < table->num_virt_cols; j++)
2213         {
2214             VirtualCellLocation vcell_loc = { i, j };
2215             VirtualCell *vcell;
2216 
2217             gnucash_sheet_block_set_from_table (sheet, vcell_loc);
2218 
2219             vcell = gnc_table_get_virtual_cell (table, vcell_loc);
2220 
2221             num_header_phys_rows =
2222                 MAX (num_header_phys_rows,
2223                      vcell->cellblock->num_rows);
2224         }
2225 
2226     gnc_header_set_header_rows (GNC_HEADER(sheet->header_item),
2227                                 num_header_phys_rows);
2228     gnc_header_reconfigure (GNC_HEADER(sheet->header_item));
2229 
2230     gnucash_sheet_recompute_block_offsets (sheet);
2231 
2232     gnucash_sheet_set_scroll_region (sheet);
2233 
2234     if (do_scroll)
2235     {
2236         VirtualLocation virt_loc;
2237 
2238         virt_loc = table->current_cursor_loc;
2239 
2240         if (gnucash_sheet_cell_valid (sheet, virt_loc))
2241             gnucash_sheet_show_row (sheet,
2242                                     virt_loc.vcell_loc.virt_row);
2243     }
2244 
2245     gnucash_sheet_cursor_set_from_table (sheet, do_scroll);
2246     gnucash_sheet_activate_cursor_cell (sheet, TRUE);
2247 }
2248 
2249 /*************************************************************/
2250 
2251 /** Map a cell color type to a css style class. */
2252 void
gnucash_get_style_classes(GnucashSheet * sheet,GtkStyleContext * stylectxt,RegisterColor field_type,gboolean use_neg_class)2253 gnucash_get_style_classes (GnucashSheet *sheet, GtkStyleContext *stylectxt,
2254                            RegisterColor field_type, gboolean use_neg_class)
2255 {
2256     gchar *full_class, *style_class = NULL;
2257 
2258     if (field_type >= COLOR_NEGATIVE) // Require a Negative fg color
2259     {
2260         if (use_neg_class)
2261             gtk_style_context_add_class (stylectxt, "gnc-class-negative-numbers");
2262         field_type -= COLOR_NEGATIVE;
2263     }
2264     else
2265     {
2266         if (sheet->use_gnc_color_theme) // only add this class if builtin colors used
2267             gtk_style_context_add_class (stylectxt, "gnc-class-register-foreground");
2268     }
2269 
2270     switch (field_type)
2271     {
2272     default:
2273     case COLOR_UNDEFINED:
2274         gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_BACKGROUND);
2275         return;
2276 
2277     case COLOR_HEADER:
2278         style_class = "header";
2279         break;
2280 
2281     case COLOR_PRIMARY:
2282         style_class = "primary";
2283         break;
2284 
2285     case COLOR_PRIMARY_ACTIVE:
2286     case COLOR_SECONDARY_ACTIVE:
2287     case COLOR_SPLIT_ACTIVE:
2288         gtk_style_context_set_state (stylectxt, GTK_STATE_FLAG_SELECTED);
2289         style_class = "cursor";
2290         break;
2291 
2292     case COLOR_SECONDARY:
2293         style_class = "secondary";
2294         break;
2295 
2296     case COLOR_SPLIT:
2297         style_class = "split";
2298         break;
2299     }
2300 
2301     if (sheet->use_gnc_color_theme)
2302         full_class = g_strconcat ("gnc-class-register-", style_class, NULL);
2303     else
2304     {
2305         gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_VIEW);
2306         full_class = g_strconcat ("gnc-class-user-register-", style_class, NULL);
2307     }
2308 
2309     gtk_style_context_add_class (stylectxt, full_class);
2310 
2311     g_free (full_class);
2312 }
2313 
2314 /*************************************************************/
2315 
2316 static void
gnucash_sheet_class_init(GnucashSheetClass * klass)2317 gnucash_sheet_class_init (GnucashSheetClass *klass)
2318 {
2319     GObjectClass *gobject_class;
2320     GtkWidgetClass *widget_class;
2321 
2322     gobject_class = G_OBJECT_CLASS(klass);
2323     widget_class = GTK_WIDGET_CLASS(klass);
2324 
2325     gtk_widget_class_set_css_name (GTK_WIDGET_CLASS(klass), "gnc-id-sheet");
2326 
2327     sheet_parent_class = g_type_class_peek_parent (klass);
2328 
2329     /* Method override */
2330     gobject_class->finalize = gnucash_sheet_finalize;
2331 
2332     widget_class->get_preferred_width = gnucash_sheet_get_preferred_width;
2333     widget_class->get_preferred_height = gnucash_sheet_get_preferred_height;
2334     widget_class->size_allocate = gnucash_sheet_size_allocate;
2335 
2336     widget_class->focus_in_event = gnucash_sheet_focus_in_event;
2337     widget_class->focus_out_event = gnucash_sheet_focus_out_event;
2338 
2339     widget_class->key_press_event = gnucash_sheet_key_press_event;
2340     widget_class->key_release_event = gnucash_sheet_key_release_event;
2341     widget_class->button_press_event = gnucash_sheet_button_press_event;
2342     widget_class->button_release_event = gnucash_sheet_button_release_event;
2343     widget_class->scroll_event = gnucash_scroll_event;
2344 }
2345 
2346 
2347 static void
gnucash_sheet_init(GnucashSheet * sheet)2348 gnucash_sheet_init (GnucashSheet *sheet)
2349 {
2350     gtk_widget_set_can_focus (GTK_WIDGET(sheet), TRUE);
2351     gtk_widget_set_can_default (GTK_WIDGET(sheet), TRUE);
2352 
2353     sheet->num_visible_blocks = 1;
2354     sheet->num_visible_phys_rows = 1;
2355 
2356     sheet->input_cancelled = FALSE;
2357 
2358     sheet->popup = NULL;
2359     sheet->num_virt_rows = 0;
2360     sheet->num_virt_cols = 0;
2361     sheet->item_editor = NULL;
2362     sheet->entry = NULL;
2363     sheet->editing = FALSE;
2364     sheet->button = 0;
2365     sheet->grabbed = FALSE;
2366     sheet->window_width = -1;
2367     sheet->window_height = -1;
2368     sheet->width = 0;
2369     sheet->height = 0;
2370 
2371     sheet->cursor_styles = g_hash_table_new (g_str_hash, g_str_equal);
2372 
2373     sheet->blocks = g_table_new (sizeof (SheetBlock),
2374                                  gnucash_sheet_block_construct,
2375                                  gnucash_sheet_block_destroy, sheet);
2376 
2377     gtk_widget_add_events (GTK_WIDGET(sheet),
2378                           (GDK_EXPOSURE_MASK
2379                           | GDK_BUTTON_PRESS_MASK
2380                           | GDK_BUTTON_RELEASE_MASK
2381                           | GDK_POINTER_MOTION_MASK
2382                           | GDK_POINTER_MOTION_HINT_MASK));
2383 
2384     /* setup IMContext */
2385     sheet->direct_update_cell = FALSE;
2386     sheet->shift_state = 0;
2387     sheet->keyval_state = 0;
2388     sheet->bound = sheet->pos = 0;
2389 }
2390 
2391 
2392 GType
gnucash_sheet_get_type(void)2393 gnucash_sheet_get_type (void)
2394 {
2395     static GType gnucash_sheet_type = 0;
2396 
2397     if (!gnucash_sheet_type)
2398     {
2399         static const GTypeInfo gnucash_sheet_info =
2400         {
2401             sizeof (GnucashSheetClass),
2402             NULL,       /* base_init */
2403             NULL,       /* base_finalize */
2404             (GClassInitFunc) gnucash_sheet_class_init,
2405             NULL,       /* class_finalize */
2406             NULL,       /* class_data */
2407             sizeof (GnucashSheet),
2408             0,      /* n_preallocs */
2409             (GInstanceInitFunc) gnucash_sheet_init
2410         };
2411 
2412         gnucash_sheet_type =
2413             g_type_register_static (GTK_TYPE_LAYOUT,
2414                                     "GnucashSheet",
2415                                     &gnucash_sheet_info, 0);
2416     }
2417 
2418     return gnucash_sheet_type;
2419 }
2420 
2421 
2422 static gboolean
gnucash_sheet_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,gpointer user_data)2423 gnucash_sheet_tooltip (GtkWidget  *widget, gint x, gint y,
2424                        gboolean    keyboard_mode,
2425                        GtkTooltip *tooltip,
2426                        gpointer    user_data)
2427 {
2428     GnucashSheet *sheet = GNUCASH_SHEET(widget);
2429     Table *table = sheet->table;
2430     VirtualLocation virt_loc;
2431     gchar *tooltip_text;
2432     gint cx, cy, cw, ch;
2433     GdkRectangle rect;
2434     SheetBlock *block;
2435     gint bx, by;
2436     gint hscroll_val, vscroll_val;
2437 
2438     if (keyboard_mode)
2439         return FALSE;
2440 
2441     // get the scroll window values
2442     hscroll_val = (gint) gtk_adjustment_get_value (sheet->hadj);
2443     vscroll_val = (gint) gtk_adjustment_get_value (sheet->vadj);
2444 
2445     if (!gnucash_sheet_find_loc_by_pixel (sheet, x + hscroll_val, y + vscroll_val, &virt_loc))
2446         return FALSE;
2447 
2448     tooltip_text = gnc_table_get_tooltip (table, virt_loc);
2449 
2450     // if tooltip_text empty, clear tooltip and return FALSE
2451     if (!tooltip_text || (g_strcmp0 (tooltip_text,"") == 0))
2452     {
2453         gtk_tooltip_set_text (tooltip, NULL);
2454         return FALSE;
2455     }
2456 
2457     block = gnucash_sheet_get_block (sheet, virt_loc.vcell_loc);
2458     if (!block)
2459     {
2460         g_free (tooltip_text);
2461         return FALSE;
2462     }
2463 
2464     bx = block->origin_x;
2465     by = block->origin_y;
2466 
2467     // get the cell location and dimensions
2468     gnucash_sheet_style_get_cell_pixel_rel_coords (block->style,
2469             virt_loc.phys_row_offset, virt_loc.phys_col_offset,
2470             &cx, &cy, &cw, &ch);
2471 
2472     rect.x = cx + bx - hscroll_val;
2473     rect.y = cy + by - vscroll_val;
2474     rect.width = cw;
2475     rect.height = ch;
2476 
2477     gtk_tooltip_set_tip_area (tooltip, &rect);
2478     gtk_tooltip_set_text (tooltip, tooltip_text);
2479     g_free (tooltip_text);
2480     return TRUE;
2481 }
2482 
2483 
2484 GtkWidget *
gnucash_sheet_new(Table * table)2485 gnucash_sheet_new (Table *table)
2486 {
2487     GnucashSheet *sheet;
2488 
2489     g_return_val_if_fail (table != NULL, NULL);
2490 
2491     sheet = gnucash_sheet_create (table);
2492 
2493     /* on create, the sheet can grab the focus */
2494     sheet->sheet_has_focus = TRUE;
2495 
2496     /* The cursor */
2497     sheet->cursor = gnucash_cursor_new (sheet);
2498 
2499     /* set up the editor */
2500     sheet->item_editor = gnc_item_edit_new (sheet);
2501 
2502     /* some register data */
2503     sheet->dimensions_hash_table = g_hash_table_new_full (g_int_hash,
2504                                    g_int_equal,
2505                                    g_free, g_free);
2506 
2507     /* add tooltips to sheet */
2508     gtk_widget_set_has_tooltip (GTK_WIDGET(sheet), TRUE);
2509     g_signal_connect (G_OBJECT(sheet), "query-tooltip",
2510                       G_CALLBACK(gnucash_sheet_tooltip), NULL);
2511 
2512     gnucash_sheet_refresh_from_prefs (sheet);
2513 
2514     return GTK_WIDGET(sheet);
2515 }
2516