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