GnuCash  4.13-177-g21dd8aa057+
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 static GtkLayout *sheet_parent_class;
74 
75 
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 
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
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
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
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
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
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
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
156 gnucash_sheet_clear_selection (GnucashSheet *sheet)
157 {
158  gnucash_sheet_set_selection (sheet, sheet->pos, sheet->pos);
159 }
160 
161 static inline void
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
739 gnucash_sheet_vadjustment_value_changed (GtkAdjustment *adj,
740  GnucashSheet *sheet)
741 {
742  gnucash_sheet_compute_visible_range (sheet);
743 }
744 
745 
746 void
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
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
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
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
799 gnucash_sheet_set_has_focus (GnucashSheet *sheet, gboolean has_focus)
800 {
801  sheet->sheet_has_focus = has_focus;
802 }
803 
804 static void
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  g_object_unref (sheet->cursor);
821 
822  if (G_OBJECT_CLASS(sheet_parent_class)->finalize)
823  (*G_OBJECT_CLASS(sheet_parent_class)->finalize)(object);
824 }
825 
826 
827 static GnucashSheet *
828 gnucash_sheet_create (Table *table)
829 {
830  GnucashSheet *sheet;
831 
832  ENTER("table=%p", table);
833 
834  sheet = g_object_new (GNUCASH_TYPE_SHEET, NULL);
835  sheet->table = table;
836  sheet->entry = NULL;
837  sheet->vadj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE(sheet));
838  sheet->hadj = gtk_scrollable_get_hadjustment (GTK_SCROLLABLE(sheet));
839 
840  g_signal_connect (G_OBJECT(sheet->vadj), "value_changed",
841  G_CALLBACK(gnucash_sheet_vadjustment_value_changed), sheet);
842  g_signal_connect (G_OBJECT(sheet), "draw",
843  G_CALLBACK(gnucash_sheet_draw_cb), sheet);
844 
845  LEAVE("%p", sheet);
846  return sheet;
847 }
848 
849 static void
850 gnucash_sheet_get_preferred_width (G_GNUC_UNUSED GtkWidget *widget,
851  gint *minimal_width,
852  gint *natural_width)
853 {
854  *minimal_width = *natural_width = DEFAULT_SHEET_WIDTH;
855 }
856 
857 
858 /* Compute the height needed to show DEFAULT_REGISTER_INITIAL_ROWS rows */
859 static void
860 gnucash_sheet_get_preferred_height (G_GNUC_UNUSED GtkWidget *widget,
861  gint *minimal_width,
862  gint *natural_width)
863 {
864  GnucashSheet *sheet = GNUCASH_SHEET(widget);
865  SheetBlockStyle *style;
866  CellDimensions *cd;
867  gint row_height;
868 
869  *minimal_width = *natural_width = DEFAULT_SHEET_HEIGHT;
870 
871  if (!sheet)
872  return;
873 
874  style = gnucash_sheet_get_style_from_cursor (sheet, CURSOR_HEADER);
875  if (!style)
876  return;
877 
878  cd = gnucash_style_get_cell_dimensions (style, 0, 0);
879  if (cd == NULL)
880  return;
881 
882  row_height = cd->pixel_height;
883 
884  *minimal_width = *natural_width = row_height * DEFAULT_SHEET_INITIAL_ROWS;
885 }
886 
887 const char *
888 gnucash_sheet_modify_current_cell (GnucashSheet *sheet, const gchar *new_text)
889 {
890  GtkEditable *editable;
891  Table *table = sheet->table;
892  VirtualLocation virt_loc;
893  int new_text_len = 0;
894  const char *retval;
895  int cursor_position, start_sel, end_sel;
896 
897  gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
898 
899  if (!gnc_table_virtual_loc_valid (table, virt_loc, TRUE))
900  return NULL;
901 
902  if (gnc_table_model_read_only (table->model))
903  return NULL;
904 
905  editable = GTK_EDITABLE(sheet->entry);
906 
907  cursor_position = gtk_editable_get_position (editable);
908  gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel);
909 
910  if (new_text)
911  new_text_len = strlen (new_text);
912 
913  retval = gnc_table_modify_update (table, virt_loc,
914  new_text, new_text_len,
915  new_text, new_text_len,
916  &cursor_position,
917  &start_sel, &end_sel,
918  NULL);
919 
920 
921  if (retval)
922  {
923  DEBUG("%s", retval ? retval : "nothing");
924  gnucash_sheet_set_entry_value (sheet, retval);
925  gnucash_sheet_set_position_and_selection (sheet, cursor_position,
926  start_sel, end_sel);
927  }
928  return retval;
929 }
930 
931 typedef struct
932 {
933  GtkEditable *editable;
934  int start_sel;
935  int end_sel;
936 
937 } select_info;
938 
939 static gboolean
940 gnucash_sheet_direct_event (GnucashSheet *sheet, GdkEvent *event)
941 {
942  GtkEditable *editable;
943  Table *table = sheet->table;
944  VirtualLocation virt_loc;
945  gboolean result;
946  char *new_text = NULL;
947  int cursor_position, start_sel, end_sel;
948  int new_position, new_start, new_end;
949 
950  gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
951 
952  if (!gnc_table_virtual_loc_valid (table, virt_loc, TRUE))
953  return FALSE;
954 
955  if (gnc_table_model_read_only (table->model))
956  return FALSE;
957 
958  editable = GTK_EDITABLE(sheet->entry);
959 
960  cursor_position = gtk_editable_get_position (editable);
961  gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel);
962 
963  new_position = cursor_position;
964  new_start = start_sel;
965  new_end = end_sel;
966  result = gnc_table_direct_update (table, virt_loc,
967  &new_text,
968  &new_position,
969  &new_start, &new_end,
970  event);
971  if (result)
972  {
973  DEBUG("%s", new_text ? new_text : "nothing");
974  if (new_text != NULL)
975  gnucash_sheet_set_entry_value (sheet, new_text);
976  gnucash_sheet_set_position_and_selection (sheet, new_position,
977  new_start, new_end);
978  }
979  return result;
980 }
981 
982 static inline void
983 normalize_selection_bounds (int *pos, int *bound, int length)
984 {
985  *bound = *bound < 0 ? length : *bound;
986  *pos = *pos < 0 ? length : *pos;
987 
988  if (*pos > *bound)
989  {
990  int temp = *pos;
991  *pos = *bound;
992  *bound = temp;
993  }
994 }
995 
996 static inline char*
997 insert_text (const char* old_text, const char* new_text, int pos, int bound)
998 {
999  int old_len = g_utf8_strlen (old_text, -1);
1000  char* begin = g_utf8_substring (old_text, 0, pos);
1001  char* end = g_utf8_substring (old_text, bound, old_len);
1002  char *retval = g_strdup_printf ("%s%s%s", begin, new_text, end);
1003  g_free (begin);
1004  g_free (end);
1005  return retval;
1006 }
1007 
1008 static char*
1009 make_new_text (GnucashSheet *sheet, const char* new_text, int *position)
1010 {
1011  GtkEditable* editable = (GTK_EDITABLE(sheet->entry));
1012  int pos, bound;
1013  const char* old_text = gtk_entry_get_text (GTK_ENTRY(sheet->entry));
1014  int old_length = g_utf8_strlen (old_text, -1);
1015  int insert_length = g_utf8_strlen (new_text, -1);
1016 
1017  if (!old_text || old_length == 0)
1018  {
1019  *position = insert_length;
1020  return g_strdup (new_text);
1021  }
1022 
1023  gtk_editable_get_selection_bounds (editable, &bound, &pos);
1024  normalize_selection_bounds (&pos, &bound, old_length);
1025 
1026  if (*position != pos)
1027  bound = pos = *position;
1028 
1029  if (pos == 0 && bound == old_length) // Full replacement
1030  {
1031  *position = insert_length;
1032  return g_strdup (new_text);
1033  }
1034 
1035  if (pos == bound)
1036  {
1037  if (pos == 0) //prepend
1038  {
1039  *position = insert_length;
1040  return g_strdup_printf ("%s%s", new_text, old_text);
1041  }
1042  else if (pos == old_length) //append
1043  {
1044  *position = old_length + insert_length;
1045  return g_strdup_printf ("%s%s", old_text, new_text);
1046  }
1047  }
1048 
1049  *position = pos + insert_length;
1050  return insert_text (old_text, new_text, pos, bound);
1051 }
1052 
1053 static void
1054 gnucash_sheet_insert_cb (GtkEditable *editable,
1055  const gchar *insert_text,
1056  const gint insert_text_len,
1057  gint *position,
1058  GnucashSheet *sheet)
1059 {
1060 
1061  Table *table = sheet->table;
1062  VirtualLocation virt_loc;
1063  char *new_text = NULL;
1064  glong new_text_len = 0;
1065  const char *retval;
1066  int start_sel = 0, end_sel = 0;
1067  int old_position = *position;
1068  const char* old_text = gtk_entry_get_text (GTK_ENTRY(sheet->entry));
1069 
1070  g_assert (GTK_WIDGET(editable) == sheet->entry);
1071  if (sheet->input_cancelled)
1072  {
1073  g_signal_stop_emission_by_name (G_OBJECT(sheet->entry),
1074  "insert_text");
1075  return;
1076  }
1077 
1078  if (insert_text_len <= 0)
1079  return;
1080 
1081  gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
1082 
1083  if (!gnc_table_virtual_loc_valid (table, virt_loc, FALSE))
1084  return;
1085 
1086  if (gnc_table_model_read_only (table->model))
1087  return;
1088 
1089  new_text = make_new_text (sheet, insert_text, position);
1090  new_text_len = strlen (new_text);
1091 
1092 
1093  retval = gnc_table_modify_update (table, virt_loc,
1094  insert_text, insert_text_len,
1095  new_text, new_text_len,
1096  position, &start_sel, &end_sel,
1097  &sheet->input_cancelled);
1098 
1099  if (retval)
1100  {
1101  /* After the insert event the GtkEntry may handle signals from the
1102  * IMContext that would reset the selection, and we may need to keep it
1103  * so save it in the sheet values.
1104  */
1105  DEBUG("%s, got %s", new_text, retval);
1106  gnucash_sheet_set_position_and_selection (sheet, *position, start_sel,
1107  end_sel);
1108 
1109  if ((strcmp (retval, new_text) != 0) || (*position != old_position))
1110  {
1111  gnucash_sheet_set_entry_value (sheet, retval);
1112  g_signal_stop_emission_by_name (G_OBJECT(sheet->entry),
1113  "insert_text");
1114  }
1115  }
1116  else if (retval == NULL)
1117  {
1118  retval = old_text;
1119 
1120  /* reset IMContext if disallowed chars */
1121  gtk_entry_reset_im_context (GTK_ENTRY(sheet->entry));
1122  /* the entry was disallowed, so we stop the insert signal */
1123  g_signal_stop_emission_by_name (G_OBJECT(sheet->entry),
1124  "insert_text");
1125  }
1126 }
1127 
1128 static char*
1129 delete_text (GnucashSheet *sheet, int pos, int bound)
1130 {
1131  const char* old_text = gtk_entry_get_text (GTK_ENTRY(sheet->entry));
1132  int old_length = g_utf8_strlen (old_text, -1);
1133  char* begin, *end;
1134  char *retval = NULL;
1135 
1136  normalize_selection_bounds (&pos, &bound, old_length);
1137  if (pos == bound)
1138  return g_strdup (old_text); // Nothing to delete.
1139 
1140  if (pos == 0 && bound == old_length) // Full delete
1141  return g_strdup ("");
1142 
1143  if (bound == old_length)
1144  return g_utf8_substring (old_text, 0, pos);
1145 
1146  if (pos == 0)
1147  return g_utf8_substring (old_text, bound, old_length);
1148 
1149  begin = g_utf8_substring (old_text, 0, pos);
1150  end = g_utf8_substring (old_text, bound, old_length);
1151  retval = g_strdup_printf ("%s%s", begin, end);
1152  g_free (begin);
1153  g_free (end);
1154  return retval;
1155 }
1156 
1157 static void
1158 gnucash_sheet_delete_cb (GtkWidget *widget,
1159  const gint start_pos,
1160  const gint end_pos,
1161  GnucashSheet *sheet)
1162 {
1163  GtkEditable *editable;
1164  Table *table = sheet->table;
1165  VirtualLocation virt_loc;
1166  char *new_text = NULL;
1167  glong new_text_len;
1168  const char *retval;
1169  int cursor_position = start_pos;
1170  int start_sel, end_sel;
1171 
1172  gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
1173 
1174  if (!gnc_table_virtual_loc_valid (table, virt_loc, FALSE))
1175  return;
1176 
1177  if (gnc_table_model_read_only (table->model))
1178  return;
1179 
1180  new_text = delete_text (sheet, start_pos, end_pos);
1181  new_text_len = strlen (new_text);
1182  editable = GTK_EDITABLE(sheet->entry);
1183  gtk_editable_get_selection_bounds (editable, &start_sel, &end_sel);
1184  retval = gnc_table_modify_update (table, virt_loc,
1185  NULL, 0,
1186  new_text, new_text_len,
1187  &cursor_position,
1188  &start_sel, &end_sel,
1189  &sheet->input_cancelled);
1190 
1191  if (retval)
1192  gnucash_sheet_set_entry_value (sheet, retval);
1193 
1194  g_signal_stop_emission_by_name (G_OBJECT(sheet->entry), "delete_text");
1195 
1196  DEBUG("%s", retval ? retval : "nothing");
1197  gnucash_sheet_set_position_and_selection (sheet, cursor_position,
1198  start_sel, end_sel);
1199 }
1200 
1201 gboolean
1202 gnucash_sheet_draw_cb (GtkWidget *widget, cairo_t *cr, G_GNUC_UNUSED gpointer data)
1203 {
1204  GnucashSheet *sheet = GNUCASH_SHEET(widget);
1205  GtkStyleContext *context = gtk_widget_get_style_context (widget);
1206  GtkAllocation alloc;
1207 
1208  gtk_widget_get_allocation (widget, &alloc);
1209 
1210  gtk_style_context_save (context);
1211  gtk_style_context_add_class (context, GTK_STYLE_CLASS_BACKGROUND);
1212  gtk_render_background (context, cr, 0, 0, alloc.width, alloc.height);
1213  gtk_style_context_restore (context);
1214 
1215  gnucash_sheet_draw_internal (sheet, cr, &alloc);
1216  gnucash_sheet_draw_cursor (sheet->cursor, cr);
1217 
1218  return FALSE;
1219 }
1220 
1221 
1222 static void
1223 gnucash_sheet_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
1224 {
1225  GnucashSheet *sheet = GNUCASH_SHEET(widget);
1226 
1227  ENTER("widget=%p, allocation=%p", widget, allocation);
1228 
1229  if (GTK_WIDGET_CLASS(sheet_parent_class)->size_allocate)
1230  (*GTK_WIDGET_CLASS(sheet_parent_class)->size_allocate)
1231  (widget, allocation);
1232 
1233  if (allocation->height == sheet->window_height &&
1234  allocation->width == sheet->window_width)
1235  {
1236  LEAVE("size unchanged");
1237  return;
1238  }
1239 
1240  if (allocation->width != sheet->window_width)
1241  {
1242  gnucash_sheet_styles_set_dimensions (sheet, allocation->width);
1243  gnucash_sheet_recompute_block_offsets (sheet);
1244  }
1245 
1246  sheet->window_height = allocation->height;
1247  sheet->window_width = allocation->width;
1248 
1249  gnucash_cursor_configure (GNUCASH_CURSOR(sheet->cursor));
1250  gnc_header_reconfigure (GNC_HEADER(sheet->header_item));
1251  gnucash_sheet_set_scroll_region (sheet);
1252 
1253  gnc_item_edit_configure (GNC_ITEM_EDIT(sheet->item_editor));
1254  gnucash_sheet_update_adjustments (sheet);
1255 
1256  if (sheet->table)
1257  {
1258  VirtualLocation virt_loc;
1259 
1260  virt_loc = sheet->table->current_cursor_loc;
1261 
1262  if (gnucash_sheet_cell_valid (sheet, virt_loc))
1263  gnucash_sheet_show_row (sheet, virt_loc.vcell_loc.virt_row);
1264  }
1265  gnc_header_request_redraw (GNC_HEADER(sheet->header_item));
1266  LEAVE(" ");
1267 }
1268 
1269 static gboolean
1270 gnucash_sheet_focus_in_event (GtkWidget *widget, GdkEventFocus *event)
1271 {
1272  GnucashSheet *sheet = GNUCASH_SHEET(widget);
1273 
1274  if (GTK_WIDGET_CLASS(sheet_parent_class)->focus_in_event)
1275  (*GTK_WIDGET_CLASS(sheet_parent_class)->focus_in_event)
1276  (widget, event);
1277 
1278  gnc_item_edit_focus_in (GNC_ITEM_EDIT(sheet->item_editor));
1279 
1280  return FALSE;
1281 }
1282 
1283 static gboolean
1284 gnucash_sheet_focus_out_event (GtkWidget *widget, GdkEventFocus *event)
1285 {
1286  GnucashSheet *sheet = GNUCASH_SHEET(widget);
1287 
1288  if (GTK_WIDGET_CLASS(sheet_parent_class)->focus_out_event)
1289  (*GTK_WIDGET_CLASS(sheet_parent_class)->focus_out_event)
1290  (widget, event);
1291 
1292  gnc_item_edit_focus_out (GNC_ITEM_EDIT(sheet->item_editor));
1293  return FALSE;
1294 }
1295 
1296 static gboolean
1297 gnucash_sheet_check_direct_update_cell (GnucashSheet *sheet,
1298  const VirtualLocation virt_loc)
1299 {
1300  const gchar *type_name;
1301 
1302  type_name = gnc_table_get_cell_type_name (sheet->table, virt_loc);
1303 
1304  if ( (g_strcmp0 (type_name, DATE_CELL_TYPE_NAME) == 0)
1305  || (g_strcmp0 (type_name, COMBO_CELL_TYPE_NAME) == 0)
1306  || (g_strcmp0 (type_name, NUM_CELL_TYPE_NAME) == 0)
1307  || (g_strcmp0 (type_name, PRICE_CELL_TYPE_NAME) == 0)
1308  || (g_strcmp0 (type_name, FORMULA_CELL_TYPE_NAME) == 0)) return TRUE;
1309 
1310  return FALSE;
1311 }
1312 
1313 static void
1314 gnucash_sheet_start_editing_at_cursor (GnucashSheet *sheet)
1315 {
1316  const char *text;
1317  VirtualLocation virt_loc;
1318 
1319  g_return_if_fail (sheet != NULL);
1320  g_return_if_fail (GNUCASH_IS_SHEET(sheet));
1321 
1322  gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &virt_loc);
1323 
1324  text = gnc_table_get_entry (sheet->table, virt_loc);
1325 
1326  gnc_item_edit_configure (GNC_ITEM_EDIT(sheet->item_editor));
1327  gtk_widget_show (GTK_WIDGET(sheet->item_editor));
1328 
1329  gtk_entry_set_text (GTK_ENTRY(sheet->entry), text);
1330 
1331  sheet->editing = TRUE;
1332 
1333  /* set up the signals */
1334  sheet->insert_signal =
1335  g_signal_connect (G_OBJECT(sheet->entry), "insert_text",
1336  G_CALLBACK(gnucash_sheet_insert_cb), sheet);
1337  sheet->delete_signal =
1338  g_signal_connect (G_OBJECT(sheet->entry), "delete_text",
1339  G_CALLBACK(gnucash_sheet_delete_cb), sheet);
1340 }
1341 
1342 static gboolean
1343 gnucash_sheet_button_release_event (GtkWidget *widget, GdkEventButton *event)
1344 {
1345  GnucashSheet *sheet;
1346 
1347  g_return_val_if_fail (widget != NULL, TRUE);
1348  g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1349  g_return_val_if_fail (event != NULL, TRUE);
1350 
1351  sheet = GNUCASH_SHEET(widget);
1352 
1353  if (sheet->button != event->button)
1354  return FALSE;
1355 
1356  sheet->button = 0;
1357 
1358  if (event->button != 1)
1359  return FALSE;
1360 
1361  gtk_grab_remove (widget);
1362  sheet->grabbed = FALSE;
1363 
1364  return TRUE;
1365 }
1366 
1367 static float
1368 clamp_scrollable_value (float value, GtkAdjustment* adj)
1369 {
1370  float lower = gtk_adjustment_get_lower (adj);
1371  float upper = gtk_adjustment_get_upper (adj);
1372  float size = gtk_adjustment_get_page_size (adj);
1373  return CLAMP(value, lower, upper - size);
1374 
1375 }
1376 static gboolean
1377 gnucash_scroll_event (GtkWidget *widget, GdkEventScroll *event)
1378 {
1379  GnucashSheet *sheet;
1380  GtkAdjustment *vadj;
1381  gfloat h_value, v_value;
1382  int direction;
1383 
1384  g_return_val_if_fail (widget != NULL, TRUE);
1385  g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1386  g_return_val_if_fail (event != NULL, TRUE);
1387 
1388  sheet = GNUCASH_SHEET(widget);
1389  vadj = sheet->vadj;
1390  v_value = gtk_adjustment_get_value (vadj);
1391 
1392  switch (event->direction)
1393  {
1394  case GDK_SCROLL_UP:
1395  v_value -= gtk_adjustment_get_step_increment (vadj);
1396  break;
1397  case GDK_SCROLL_DOWN:
1398  v_value += gtk_adjustment_get_step_increment (vadj);
1399  break;
1400 /* GdkQuartz reserves GDK_SCROLL_SMOOTH for high-resolution touchpad
1401  * scrolling events, and in that case scrolling by line is much too
1402  * fast. Gdk/Wayland and Gdk/Win32 pass GDK_SCROLL_SMOOTH for all
1403  * scroll-wheel events and expect coarse resolution.
1404  */
1405  case GDK_SCROLL_SMOOTH:
1406  h_value = gtk_adjustment_get_value (sheet->hadj);
1407  h_value += event->delta_x;
1408  h_value = clamp_scrollable_value (h_value, sheet->hadj);
1409  gtk_adjustment_set_value (sheet->hadj, h_value);
1410 #if defined MAC_INTEGRATION
1411  v_value += event->delta_y;
1412 #else
1413  direction = event->delta_y > 0 ? 1 : event->delta_y < 0 ? -1 : 0;
1414  v_value += gtk_adjustment_get_step_increment (vadj) * direction;
1415 #endif
1416  break;
1417  default:
1418  return FALSE;
1419  }
1420  v_value = clamp_scrollable_value (v_value, vadj);
1421  gtk_adjustment_set_value (vadj, v_value);
1422 
1423  if (event->delta_y == 0)
1424  {
1425  /* There are problems with the slider not tracking the value so
1426  when delta_y is 0 hide and showing the scrollbar seems to fix it
1427  observed when using mouse wheel on sheet after a page-up or down */
1428  gtk_widget_hide (GTK_WIDGET(sheet->vscrollbar));
1429  gtk_widget_show (GTK_WIDGET(sheet->vscrollbar));
1430  }
1431  return TRUE;
1432 }
1433 
1434 static void
1435 gnucash_sheet_check_grab (GnucashSheet *sheet)
1436 {
1437  GdkModifierType mods;
1438  GdkDevice *device;
1439  GdkSeat *seat;
1440  GdkWindow *window;
1441 
1442  if (!sheet->grabbed)
1443  return;
1444 
1445  window = gtk_widget_get_window (GTK_WIDGET(sheet));
1446 
1447  seat = gdk_display_get_default_seat (gdk_window_get_display (window));
1448  device = gdk_seat_get_pointer (seat);
1449 
1450  gdk_device_get_state (device, window, 0, &mods);
1451 
1452  if (!(mods & GDK_BUTTON1_MASK))
1453  {
1454  gtk_grab_remove (GTK_WIDGET(sheet));
1455  sheet->grabbed = FALSE;
1456  }
1457 }
1458 
1459 static gboolean
1460 gnucash_sheet_button_press_event (GtkWidget *widget, GdkEventButton *event)
1461 {
1462  GnucashSheet *sheet;
1463  VirtualCell *vcell;
1464  VirtualLocation cur_virt_loc;
1465  VirtualLocation new_virt_loc;
1466  Table *table;
1467  gboolean abort_move;
1468  gboolean button_1;
1469  gboolean do_popup;
1470 
1471  g_return_val_if_fail (widget != NULL, TRUE);
1472  g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1473  g_return_val_if_fail (event != NULL, TRUE);
1474 
1475  sheet = GNUCASH_SHEET(widget);
1476  table = sheet->table;
1477 
1478  if (sheet->button && (sheet->button != event->button))
1479  return FALSE;
1480 
1481  sheet->button = event->button;
1482  if (sheet->button == 3)
1483  sheet->button = 0;
1484 
1485  if (!gtk_widget_has_focus (widget))
1486  gtk_widget_grab_focus (widget);
1487 
1488  button_1 = FALSE;
1489  do_popup = FALSE;
1490 
1491  switch (event->button)
1492  {
1493  case 1:
1494  button_1 = TRUE;
1495  break;
1496  case 2:
1497  if (event->type != GDK_BUTTON_PRESS)
1498  return FALSE;
1499  gnc_item_edit_paste_clipboard (GNC_ITEM_EDIT(sheet->item_editor));
1500  return TRUE;
1501  case 3:
1502  do_popup = (sheet->popup != NULL);
1503  break;
1504  default:
1505  return FALSE;
1506  }
1507 
1508  gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &cur_virt_loc);
1509 
1510  sheet->button_x = -1;
1511  sheet->button_y = -1;
1512 
1513  if (!gnucash_sheet_find_loc_by_pixel (sheet, event->x, event->y,
1514  &new_virt_loc))
1515  return TRUE;
1516 
1517  sheet->button_x = event->x;
1518  sheet->button_y = event->y;
1519 
1520  vcell = gnc_table_get_virtual_cell (table, new_virt_loc.vcell_loc);
1521  if (vcell == NULL)
1522  return TRUE;
1523 
1524  if (event->type != GDK_BUTTON_PRESS)
1525  return FALSE;
1526 
1527  if (button_1)
1528  {
1529  gtk_grab_add (widget);
1530  sheet->grabbed = TRUE;
1531  }
1532 
1533  if (virt_loc_equal (new_virt_loc, cur_virt_loc) &&
1534  sheet->editing && do_popup)
1535  {
1536  gtk_menu_popup_at_pointer (GTK_MENU(sheet->popup), (GdkEvent *) event);
1537  return TRUE;
1538  }
1539 
1540  /* and finally...process this as a POINTER_TRAVERSE */
1541  abort_move = gnc_table_traverse_update (table,
1542  cur_virt_loc,
1543  GNC_TABLE_TRAVERSE_POINTER,
1544  &new_virt_loc);
1545 
1546  if (button_1)
1547  gnucash_sheet_check_grab (sheet);
1548 
1549  if (abort_move)
1550  return TRUE;
1551 
1552  gnucash_sheet_cursor_move (sheet, new_virt_loc);
1553 
1554  // if clicked in document link cell, run call back
1555  if (g_strcmp0 (gnc_table_get_cell_name (table, new_virt_loc), DOCLINK_CELL) == 0)
1556  {
1557  if (sheet->open_doclink_cb)
1558  (sheet->open_doclink_cb)(sheet->open_doclink_cb_data, NULL);
1559  }
1560 
1561  if (button_1)
1562  gnucash_sheet_check_grab (sheet);
1563 
1564  if (do_popup)
1565  gtk_menu_popup_at_pointer (GTK_MENU(sheet->popup), (GdkEvent *) event);
1566 
1567  return button_1 || do_popup;
1568 }
1569 
1570 void
1571 gnucash_sheet_refresh_from_prefs (GnucashSheet *sheet)
1572 {
1573  GtkStyleContext *stylectxt;
1574  GncItemEdit *item_edit;
1575  GList *classes = NULL;
1576 
1577  g_return_if_fail (sheet != NULL);
1578  g_return_if_fail (GNUCASH_IS_SHEET(sheet));
1579 
1580  sheet->use_gnc_color_theme = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
1581  GNC_PREF_USE_GNUCASH_COLOR_THEME);
1582  sheet->use_horizontal_lines = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
1583  GNC_PREF_DRAW_HOR_LINES);
1584  sheet->use_vertical_lines = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL_REGISTER,
1585  GNC_PREF_DRAW_VERT_LINES);
1586 
1587  item_edit = GNC_ITEM_EDIT(sheet->item_editor);
1588 
1589  stylectxt = gtk_widget_get_style_context (GTK_WIDGET(item_edit->editor));
1590 
1591  // Get the CSS classes for the editor
1592  classes = gtk_style_context_list_classes (stylectxt);
1593 
1594  for (GList *l = classes; l; l = l->next)
1595  {
1596  if (g_str_has_prefix (l->data, "gnc-class-"))
1597  gtk_style_context_remove_class (stylectxt, l->data);
1598  }
1599  g_list_free (classes);
1600 
1601  gtk_style_context_remove_class (stylectxt, GTK_STYLE_CLASS_VIEW);
1602 
1603  // Note: COLOR_PRIMARY_ACTIVE, COLOR_SECONDARY_ACTIVE, COLOR_SPLIT_ACTIVE
1604  // all equate to *-cursor style class used for the editor
1605  gnucash_get_style_classes (sheet, stylectxt, COLOR_PRIMARY_ACTIVE, FALSE);
1606 }
1607 
1608 static gboolean
1609 gnucash_sheet_clipboard_event (GnucashSheet *sheet, GdkEventKey *event)
1610 {
1611  GncItemEdit *item_edit;
1612  gboolean handled = FALSE;
1613 
1614  item_edit = GNC_ITEM_EDIT(sheet->item_editor);
1615 
1616  switch (event->keyval)
1617  {
1618  case GDK_KEY_C:
1619  case GDK_KEY_c:
1620  if (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR)
1621  {
1622  gnc_item_edit_copy_clipboard (item_edit);
1623  handled = TRUE;
1624  }
1625  break;
1626  case GDK_KEY_X:
1627  case GDK_KEY_x:
1628  if (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR)
1629  {
1630  gnc_item_edit_cut_clipboard (item_edit);
1631  handled = TRUE;
1632  }
1633  break;
1634  case GDK_KEY_V:
1635  case GDK_KEY_v:
1636  if (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR)
1637  {
1638  gnc_item_edit_paste_clipboard (item_edit);
1639  handled = TRUE;
1640  }
1641  break;
1642  case GDK_KEY_Insert:
1643  if (event->state & GDK_SHIFT_MASK)
1644  {
1645  gnc_item_edit_paste_clipboard (item_edit);
1646  handled = TRUE;
1647  }
1648  else if (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR)
1649  {
1650  gnc_item_edit_copy_clipboard (item_edit);
1651  handled = TRUE;
1652  }
1653  break;
1654  }
1655  return handled;
1656 }
1657 
1658 static void
1659 gnucash_sheet_need_horizontal_scroll (GnucashSheet *sheet,
1660  VirtualLocation *new_virt_loc)
1661 {
1662  gint hscroll_val;
1663  gint cell_width = 0;
1664  gint offset;
1665 
1666  if (sheet->window_width == sheet->width)
1667  return;
1668 
1669  // get the horizontal scroll window value
1670  hscroll_val = (gint) gtk_adjustment_get_value (sheet->hadj);
1671 
1672  // offset is the start of the cell for column
1673  offset = gnc_header_get_cell_offset (GNC_HEADER(sheet->header_item),
1674  new_virt_loc->phys_col_offset, &cell_width);
1675 
1676  if (((offset + cell_width) > sheet->window_width) || (offset < hscroll_val))
1677  gtk_adjustment_set_value (sheet->hadj, offset);
1678 }
1679 
1680 static gboolean
1681 process_motion_keys (GnucashSheet *sheet, GdkEventKey *event, gboolean *pass_on,
1682  gncTableTraversalDir *direction,
1683  VirtualLocation* new_virt_loc)
1684 {
1685  int distance;
1686  VirtualLocation cur_virt_loc = *new_virt_loc;
1687 
1688  switch (event->keyval)
1689  {
1690  case GDK_KEY_Return:
1691  case GDK_KEY_KP_Enter:
1692  g_signal_emit_by_name (sheet->reg, "activate_cursor");
1693  /* Clear the saved selection. */
1694  sheet->pos = sheet->bound;
1695  return TRUE;
1696  break;
1697  case GDK_KEY_Tab:
1698  case GDK_KEY_ISO_Left_Tab:
1699  if (event->state & GDK_SHIFT_MASK)
1700  {
1701  *direction = GNC_TABLE_TRAVERSE_LEFT;
1702  gnc_table_move_tab (sheet->table, new_virt_loc, FALSE);
1703  }
1704  else
1705  {
1706  *direction = GNC_TABLE_TRAVERSE_RIGHT;
1707  gnc_table_move_tab (sheet->table, new_virt_loc, TRUE);
1708  }
1709  break;
1710  case GDK_KEY_KP_Page_Up:
1711  case GDK_KEY_Page_Up:
1712  *direction = GNC_TABLE_TRAVERSE_UP;
1713  new_virt_loc->phys_col_offset = 0;
1714  if (event->state & GDK_SHIFT_MASK)
1715  new_virt_loc->vcell_loc.virt_row = 1;
1716  else
1717  {
1718  distance = sheet->num_visible_phys_rows - 1;
1720  (sheet->table, new_virt_loc, -distance);
1721  }
1722  break;
1723  case GDK_KEY_KP_Page_Down:
1724  case GDK_KEY_Page_Down:
1725  *direction = GNC_TABLE_TRAVERSE_DOWN;
1726  new_virt_loc->phys_col_offset = 0;
1727  if (event->state & GDK_SHIFT_MASK)
1728  new_virt_loc->vcell_loc.virt_row =
1729  sheet->num_virt_rows - 1;
1730  else
1731  {
1732  distance = sheet->num_visible_phys_rows - 1;
1734  (sheet->table, new_virt_loc, distance);
1735  }
1736  break;
1737  case GDK_KEY_KP_Up:
1738  case GDK_KEY_Up:
1739  *direction = GNC_TABLE_TRAVERSE_UP;
1740  gnc_table_move_vertical_position (sheet->table,
1741  new_virt_loc, -1);
1742  break;
1743  case GDK_KEY_KP_Down:
1744  case GDK_KEY_Down:
1745  case GDK_KEY_Menu:
1746  if (event->keyval == GDK_KEY_Menu ||
1747  (event->state & GDK_MODIFIER_INTENT_PRIMARY_ACCELERATOR))
1748  {
1749  GncItemEdit *item_edit = GNC_ITEM_EDIT(sheet->item_editor);
1750 
1751  if (gnc_table_confirm_change (sheet->table, cur_virt_loc))
1752  gnc_item_edit_show_popup (item_edit);
1753 
1754  /* Clear the saved selection for the new cell. */
1755  sheet->pos = sheet->bound;
1756  return TRUE;
1757  }
1758 
1759  *direction = GNC_TABLE_TRAVERSE_DOWN;
1760  gnc_table_move_vertical_position (sheet->table,
1761  new_virt_loc, 1);
1762  break;
1763  case GDK_KEY_KP_Right:
1764  case GDK_KEY_Right:
1765  case GDK_KEY_KP_Left:
1766  case GDK_KEY_Left:
1767  case GDK_KEY_Home:
1768  case GDK_KEY_End:
1769  /* Clear the saved selection, we're not using it. */
1770  sheet->pos = sheet->bound;
1771  *pass_on = TRUE;
1772  break;
1773  default:
1774  if (gnucash_sheet_clipboard_event (sheet, event))
1775  {
1776  /* Clear the saved selection. */
1777  sheet->pos = sheet->bound;
1778  return TRUE;
1779  }
1780  *pass_on = TRUE;
1781  break;
1782  }
1783  // does the sheet need horizontal scrolling due to tab
1784  gnucash_sheet_need_horizontal_scroll (sheet, new_virt_loc);
1785 
1786  return FALSE;
1787 }
1788 
1789 static gboolean
1790 pass_to_entry_handler (GnucashSheet *sheet, GdkEventKey *event)
1791 {
1792  gboolean result = FALSE;
1793  GtkEditable *editable = GTK_EDITABLE(sheet->entry);
1794 
1795  // If sheet is readonly, entry is not realized
1796  if (gtk_widget_get_realized (GTK_WIDGET(editable)))
1797  {
1798  result = gtk_widget_event (GTK_WIDGET(editable), (GdkEvent*)event);
1799  gnucash_sheet_set_selection_from_entry (sheet);
1800  }
1801  return result;
1802 }
1803 
1804 static gint
1805 gnucash_sheet_key_press_event_internal (GtkWidget *widget, GdkEventKey *event)
1806 {
1807  Table *table;
1808  GnucashSheet *sheet;
1809  gboolean pass_on = FALSE;
1810  gboolean abort_move;
1811  VirtualLocation cur_virt_loc;
1812  VirtualLocation new_virt_loc;
1813  gncTableTraversalDir direction = 0;
1814  int distance;
1815  GdkModifierType modifiers = gtk_accelerator_get_default_mod_mask ();
1816 
1817  g_return_val_if_fail (widget != NULL, TRUE);
1818  g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1819  g_return_val_if_fail (event != NULL, TRUE);
1820 
1821  sheet = GNUCASH_SHEET(widget);
1822  table = sheet->table;
1823  /* Don't respond to stand-alone modifier keys. */
1824  if (event->is_modifier)
1825  return TRUE;
1826  /* Initially sync the selection, the user might have adjusted it with the
1827  * mouse.
1828  */
1829  gnucash_sheet_set_selection_from_entry (sheet);
1830  /* Direct_event gets first whack */
1831  if (gnucash_sheet_direct_event (sheet, (GdkEvent *) event))
1832  return TRUE;
1833  /* Followed by the input method */
1834  if (gtk_entry_im_context_filter_keypress (GTK_ENTRY(sheet->entry), event))
1835  {
1836 #if !(defined(__APPLE__) || defined(__WIN32__))
1837  /* There's sometimes a timing issue when running under KDE
1838  * Plasma where this call removes the selection. This 1ms
1839  * sleep prevents it.
1840  */
1841  usleep(1000);
1842 #endif
1843  /* Restore the saved cursor position in case GtkEntry's IMContext
1844  * handlers messed with it after we set it in our insert_cb.
1845  */
1846  gnucash_sheet_set_entry_selection (sheet);
1847  return TRUE;
1848  }
1849 
1850  gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &cur_virt_loc);
1851  new_virt_loc = cur_virt_loc;
1852 
1853  /* Don't process any keystrokes where a modifier key (Alt, Meta, etc.) is
1854  * being held down. This shouldn't include NUM LOCK.
1855  */
1856  if (event->state & modifiers & (GDK_MODIFIER_INTENT_DEFAULT_MOD_MASK))
1857  pass_on = TRUE;
1858  else if (process_motion_keys (sheet, event, &pass_on,
1859  &direction, &new_virt_loc)) //may set pass_on
1860  return TRUE;
1861 
1862  /* Forward the keystroke to the input line */
1863  if (pass_on)
1864  {
1865  return pass_to_entry_handler (sheet, event);
1866  }
1867 
1868  abort_move = gnc_table_traverse_update (table, cur_virt_loc,
1869  direction, &new_virt_loc);
1870 
1871  /* If that would leave the register, abort */
1872  if (abort_move)
1873  {
1874  // Make sure the sheet is the focus
1875  if (!gtk_widget_has_focus (GTK_WIDGET(sheet)))
1876  gtk_widget_grab_focus (GTK_WIDGET(sheet));
1877  return TRUE;
1878  }
1879 
1880  /* Clear the saved selection for the new cell. */
1881  sheet->pos = sheet->bound;
1882  gnucash_sheet_cursor_move (sheet, new_virt_loc);
1883 
1884  /* return true because we handled the key press */
1885  return TRUE;
1886 }
1887 
1888 static gint
1889 gnucash_sheet_key_press_event (GtkWidget *widget, GdkEventKey *event)
1890 {
1891  GnucashSheet *sheet;
1892  GtkEditable *editable = NULL;
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  sheet = GNUCASH_SHEET(widget);
1899  editable = GTK_EDITABLE(sheet->entry);
1900  /* bug#60582 comment#27 2
1901  save shift state to enable <shift minus> and <shift equal>
1902  bug#618434
1903  save keyval to handle GDK_KEY_KP_Decimal event
1904  */
1905 #ifdef G_OS_WIN32
1906  /* gdk never sends GDK_KEY_KP_Decimal on win32. See #486658 */
1907  if (event->hardware_keycode == VK_DECIMAL)
1908  event->keyval = GDK_KEY_KP_Decimal;
1909 #endif
1910  sheet->shift_state = event->state & GDK_SHIFT_MASK;
1911  sheet->keyval_state =
1912  (event->keyval == GDK_KEY_KP_Decimal) ? GDK_KEY_KP_Decimal : 0;
1913 
1914  return gnucash_sheet_key_press_event_internal (widget, event);
1915 }
1916 
1917 static gint
1918 gnucash_sheet_key_release_event (GtkWidget *widget, GdkEventKey *event)
1919 {
1920  GnucashSheet *sheet;
1921 
1922  g_return_val_if_fail (widget != NULL, TRUE);
1923  g_return_val_if_fail (GNUCASH_IS_SHEET(widget), TRUE);
1924  g_return_val_if_fail (event != NULL, TRUE);
1925 
1926  return FALSE;
1927 }
1928 
1929 
1930 void
1931 gnucash_sheet_goto_virt_loc (GnucashSheet *sheet, VirtualLocation virt_loc)
1932 {
1933  Table *table;
1934  gboolean abort_move;
1935  VirtualLocation cur_virt_loc;
1936 
1937  g_return_if_fail (GNUCASH_IS_SHEET(sheet));
1938 
1939  table = sheet->table;
1940 
1941  gnucash_cursor_get_virt (GNUCASH_CURSOR(sheet->cursor), &cur_virt_loc);
1942 
1943  /* It's not really a pointer traverse, but it seems the most
1944  * appropriate here. */
1945  abort_move = gnc_table_traverse_update (table, cur_virt_loc,
1946  GNC_TABLE_TRAVERSE_POINTER,
1947  &virt_loc);
1948 
1949  if (abort_move)
1950  return;
1951 
1952  // does the sheet need horizontal scrolling
1953  gnucash_sheet_need_horizontal_scroll (sheet, &virt_loc);
1954 
1955  gnucash_sheet_cursor_move (sheet, virt_loc);
1956 }
1957 
1958 SheetBlock *
1959 gnucash_sheet_get_block (GnucashSheet *sheet, VirtualCellLocation vcell_loc)
1960 {
1961  g_return_val_if_fail (sheet != NULL, NULL);
1962  g_return_val_if_fail (GNUCASH_IS_SHEET(sheet), NULL);
1963 
1964  return g_table_index (sheet->blocks,
1965  vcell_loc.virt_row,
1966  vcell_loc.virt_col);
1967 }
1968 
1969 GncItemEdit *gnucash_sheet_get_item_edit (GnucashSheet *sheet)
1970 {
1971  g_return_val_if_fail (sheet != NULL, NULL);
1972  g_return_val_if_fail (GNUCASH_IS_SHEET(sheet), NULL);
1973 
1974  if (sheet->item_editor == NULL)
1975  return NULL;
1976  else
1977  return GNC_ITEM_EDIT(sheet->item_editor);
1978 }
1979 
1980 
1981 void gnucash_sheet_set_window (GnucashSheet *sheet, GtkWidget *window)
1982 {
1983  g_return_if_fail (sheet != NULL);
1984  g_return_if_fail (GNUCASH_IS_SHEET(sheet));
1985 
1986  if (window)
1987  g_return_if_fail (GTK_IS_WIDGET(window));
1988 
1989  sheet->window = window;
1990 }
1991 
1992 
1993 /* This fills up a block from the table; it sets the style and returns
1994  * true if the style changed. */
1995 gboolean
1996 gnucash_sheet_block_set_from_table (GnucashSheet *sheet,
1997  VirtualCellLocation vcell_loc)
1998 {
1999  Table *table;
2000  SheetBlock *block;
2001  SheetBlockStyle *style;
2002  VirtualCell *vcell;
2003 
2004  block = gnucash_sheet_get_block (sheet, vcell_loc);
2005  style = gnucash_sheet_get_style_from_table (sheet, vcell_loc);
2006 
2007  if (!block)
2008  return FALSE;
2009 
2010  table = sheet->table;
2011 
2012  vcell = gnc_table_get_virtual_cell (table, vcell_loc);
2013 
2014  if (block->style && (block->style != style))
2015  {
2016  gnucash_sheet_style_unref (sheet, block->style);
2017  block->style = NULL;
2018  }
2019 
2020  block->visible = (vcell) ? vcell->visible : TRUE;
2021 
2022  if (block->style == NULL)
2023  {
2024  block->style = style;
2025  gnucash_sheet_style_ref (sheet, block->style);
2026  return TRUE;
2027  }
2028  return FALSE;
2029 }
2030 
2031 
2032 gint
2033 gnucash_sheet_col_max_width (GnucashSheet *sheet, gint virt_col, gint cell_col)
2034 {
2035  int virt_row;
2036  int cell_row;
2037  int max = 0;
2038  int width;
2039  SheetBlock *block;
2040  SheetBlockStyle *style;
2041  PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET (sheet), "");
2042  GncItemEdit *item_edit = GNC_ITEM_EDIT(sheet->item_editor);
2043  const gchar *type_name;
2044 
2045  g_return_val_if_fail (virt_col >= 0, 0);
2046  g_return_val_if_fail (virt_col < sheet->num_virt_cols, 0);
2047  g_return_val_if_fail (cell_col >= 0, 0);
2048 
2049  for (virt_row = 0; virt_row < sheet->num_virt_rows ; virt_row++)
2050  {
2051  VirtualCellLocation vcell_loc = { virt_row, virt_col };
2052 
2053  block = gnucash_sheet_get_block (sheet, vcell_loc);
2054  if (!block)
2055  continue;
2056 
2057  style = block->style;
2058 
2059  if (!style)
2060  continue;
2061 
2062  if (cell_col < style->ncols)
2063  {
2064  for (cell_row = 0; cell_row < style->nrows; cell_row++)
2065  {
2066  VirtualLocation virt_loc;
2067  const char *text;
2068 
2069  if (virt_row == 0)
2070  virt_loc.vcell_loc = sheet->table->current_cursor_loc.vcell_loc;
2071  else
2072  virt_loc.vcell_loc = vcell_loc;
2073 
2074  virt_loc.phys_row_offset = cell_row;
2075  virt_loc.phys_col_offset = cell_col;
2076 
2077  if (virt_row == 0)
2078  {
2079  text = gnc_table_get_label
2080  (sheet->table, virt_loc);
2081  }
2082  else
2083  {
2084  text = gnc_table_get_entry
2085  (sheet->table, virt_loc);
2086  }
2087 
2088  pango_layout_set_text (layout, text, strlen (text));
2089  pango_layout_get_pixel_size (layout, &width, NULL);
2090 
2091  width += (gnc_item_edit_get_margin (item_edit, left_right) +
2092  gnc_item_edit_get_padding_border (item_edit, left_right));
2093 
2094  // get the cell type so we can add the button width to the
2095  // text width if required.
2096  type_name = gnc_table_get_cell_type_name (sheet->table, virt_loc);
2097  if ((g_strcmp0 (type_name, DATE_CELL_TYPE_NAME) == 0)
2098  || (g_strcmp0 (type_name, COMBO_CELL_TYPE_NAME) == 0))
2099  {
2100  width += gnc_item_edit_get_button_width (item_edit) + 2; // add 2 for the button margin
2101  }
2102  max = MAX(max, width);
2103  }
2104  }
2105  }
2106 
2107  g_object_unref (layout);
2108 
2109  return max;
2110 }
2111 
2112 void
2113 gnucash_sheet_set_scroll_region (GnucashSheet *sheet)
2114 {
2115  guint new_h, new_w;
2116  GtkAllocation alloc;
2117  guint old_h, old_w;
2118 
2119  if (!sheet)
2120  return;
2121 
2122  if (!sheet->header_item || !GNC_HEADER(sheet->header_item)->style)
2123  return;
2124 
2125  gtk_layout_get_size (GTK_LAYOUT(sheet), &old_w, &old_h);
2126 
2127  gtk_widget_get_allocation (GTK_WIDGET(sheet), &alloc);
2128  new_h = MAX(sheet->height, alloc.height);
2129  new_w = MAX(sheet->width, alloc.width);
2130 
2131  if (new_w != old_w || new_h != old_h)
2132  gtk_layout_set_size (GTK_LAYOUT(sheet), new_w, new_h);
2133 }
2134 
2135 static void
2136 gnucash_sheet_block_destroy (gpointer _block, gpointer user_data)
2137 {
2138  SheetBlock *block = _block;
2139  GnucashSheet *sheet = GNUCASH_SHEET(user_data);
2140 
2141  if (block == NULL)
2142  return;
2143 
2144  if (block->style)
2145  {
2146  gnucash_sheet_style_unref (sheet, block->style);
2147  /* Don't free the block itself here. It's managed by the block table */
2148  }
2149 }
2150 
2151 static void
2152 gnucash_sheet_block_construct (gpointer _block, gpointer user_data)
2153 {
2154  SheetBlock *block = _block;
2155 
2156  block->style = NULL;
2157  block->visible = TRUE;
2158 }
2159 
2160 static void
2161 gnucash_sheet_resize (GnucashSheet *sheet)
2162 {
2163  g_return_if_fail (sheet != NULL);
2164  g_return_if_fail (GNUCASH_IS_SHEET(sheet));
2165 
2166  if (sheet->table->num_virt_cols > 1)
2167  g_warning ("num_virt_cols > 1");
2168 
2169  sheet->num_virt_cols = 1;
2170 
2171  g_table_resize (sheet->blocks, sheet->table->num_virt_rows, 1);
2172 
2173  sheet->num_virt_rows = sheet->table->num_virt_rows;
2174 }
2175 
2176 void
2177 gnucash_sheet_recompute_block_offsets (GnucashSheet *sheet)
2178 {
2179  Table *table;
2180  SheetBlock *block;
2181  gint i, j;
2182  gint height;
2183  gint width;
2184 
2185  g_return_if_fail (sheet != NULL);
2186  g_return_if_fail (GNUCASH_IS_SHEET(sheet));
2187  g_return_if_fail (sheet->table != NULL);
2188 
2189  table = sheet->table;
2190 
2191  height = 0;
2192  block = NULL;
2193  for (i = 0; i < table->num_virt_rows; i++)
2194  {
2195  width = 0;
2196 
2197  for (j = 0; j < table->num_virt_cols; j++)
2198  {
2199  VirtualCellLocation vcell_loc = { i, j };
2200 
2201  block = gnucash_sheet_get_block (sheet, vcell_loc);
2202 
2203  if (!block)
2204  continue;
2205 
2206  block->origin_x = width;
2207  block->origin_y = height;
2208 
2209  if (block->visible)
2210  width += block->style->dimensions->width;
2211  }
2212 
2213  if (i > 0 && block && block->visible)
2214  height += block->style->dimensions->height;
2215  }
2216  sheet->height = height;
2217 }
2218 
2219 void
2220 gnucash_sheet_table_load (GnucashSheet *sheet, gboolean do_scroll)
2221 {
2222  Table *table;
2223  gint num_header_phys_rows;
2224  gint i, j;
2225 
2226  g_return_if_fail (sheet != NULL);
2227  g_return_if_fail (GNUCASH_IS_SHEET(sheet));
2228  g_return_if_fail (sheet->table != NULL);
2229 
2230  table = sheet->table;
2231 
2232  gnucash_sheet_stop_editing (sheet);
2233 
2234  gnucash_sheet_resize (sheet);
2235 
2236  num_header_phys_rows = 0;
2237 
2238  /* fill it up */
2239  for (i = 0; i < table->num_virt_rows; i++)
2240  for (j = 0; j < table->num_virt_cols; j++)
2241  {
2242  VirtualCellLocation vcell_loc = { i, j };
2243  VirtualCell *vcell;
2244 
2245  gnucash_sheet_block_set_from_table (sheet, vcell_loc);
2246 
2247  vcell = gnc_table_get_virtual_cell (table, vcell_loc);
2248 
2249  num_header_phys_rows =
2250  MAX (num_header_phys_rows,
2251  vcell->cellblock->num_rows);
2252  }
2253 
2254  gnc_header_set_header_rows (GNC_HEADER(sheet->header_item),
2255  num_header_phys_rows);
2256  gnc_header_reconfigure (GNC_HEADER(sheet->header_item));
2257 
2258  gnucash_sheet_recompute_block_offsets (sheet);
2259 
2260  gnucash_sheet_set_scroll_region (sheet);
2261 
2262  if (do_scroll)
2263  {
2264  VirtualLocation virt_loc;
2265 
2266  virt_loc = table->current_cursor_loc;
2267 
2268  if (gnucash_sheet_cell_valid (sheet, virt_loc))
2269  gnucash_sheet_show_row (sheet,
2270  virt_loc.vcell_loc.virt_row);
2271  }
2272 
2273  gnucash_sheet_cursor_set_from_table (sheet, do_scroll);
2274  gnucash_sheet_activate_cursor_cell (sheet, TRUE);
2275 }
2276 
2277 /*************************************************************/
2278 
2280 void
2281 gnucash_get_style_classes (GnucashSheet *sheet, GtkStyleContext *stylectxt,
2282  RegisterColor field_type, gboolean use_neg_class)
2283 {
2284  gchar *full_class, *style_class = NULL;
2285 
2286  if (field_type >= COLOR_NEGATIVE) // Require a Negative fg color
2287  {
2288  if (use_neg_class)
2289  gtk_style_context_add_class (stylectxt, "gnc-class-negative-numbers");
2290  field_type -= COLOR_NEGATIVE;
2291  }
2292  else
2293  {
2294  if (sheet->use_gnc_color_theme) // only add this class if builtin colors used
2295  gtk_style_context_add_class (stylectxt, "gnc-class-register-foreground");
2296  }
2297 
2298  switch (field_type)
2299  {
2300  default:
2301  case COLOR_UNDEFINED:
2302  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_BACKGROUND);
2303  return;
2304 
2305  case COLOR_HEADER:
2306  style_class = "header";
2307  break;
2308 
2309  case COLOR_PRIMARY:
2310  style_class = "primary";
2311  break;
2312 
2313  case COLOR_PRIMARY_ACTIVE:
2314  case COLOR_SECONDARY_ACTIVE:
2315  case COLOR_SPLIT_ACTIVE:
2316  gtk_style_context_set_state (stylectxt, GTK_STATE_FLAG_SELECTED);
2317  style_class = "cursor";
2318  break;
2319 
2320  case COLOR_SECONDARY:
2321  style_class = "secondary";
2322  break;
2323 
2324  case COLOR_SPLIT:
2325  style_class = "split";
2326  break;
2327  }
2328 
2329  if (sheet->use_gnc_color_theme)
2330  full_class = g_strconcat ("gnc-class-register-", style_class, NULL);
2331  else
2332  {
2333  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_VIEW);
2334  full_class = g_strconcat ("gnc-class-user-register-", style_class, NULL);
2335  }
2336 
2337  gtk_style_context_add_class (stylectxt, full_class);
2338 
2339  g_free (full_class);
2340 }
2341 
2342 /*************************************************************/
2343 
2344 static void
2345 gnucash_sheet_class_init (GnucashSheetClass *klass)
2346 {
2347  GObjectClass *gobject_class;
2348  GtkWidgetClass *widget_class;
2349 
2350  gobject_class = G_OBJECT_CLASS(klass);
2351  widget_class = GTK_WIDGET_CLASS(klass);
2352 
2353  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS(klass), "gnc-id-sheet");
2354 
2355  sheet_parent_class = g_type_class_peek_parent (klass);
2356 
2357  /* Method override */
2358  gobject_class->finalize = gnucash_sheet_finalize;
2359 
2360  widget_class->get_preferred_width = gnucash_sheet_get_preferred_width;
2361  widget_class->get_preferred_height = gnucash_sheet_get_preferred_height;
2362  widget_class->size_allocate = gnucash_sheet_size_allocate;
2363 
2364  widget_class->focus_in_event = gnucash_sheet_focus_in_event;
2365  widget_class->focus_out_event = gnucash_sheet_focus_out_event;
2366 
2367  widget_class->key_press_event = gnucash_sheet_key_press_event;
2368  widget_class->key_release_event = gnucash_sheet_key_release_event;
2369  widget_class->button_press_event = gnucash_sheet_button_press_event;
2370  widget_class->button_release_event = gnucash_sheet_button_release_event;
2371  widget_class->scroll_event = gnucash_scroll_event;
2372 }
2373 
2374 
2375 static void
2376 gnucash_sheet_init (GnucashSheet *sheet)
2377 {
2378  gtk_widget_set_can_focus (GTK_WIDGET(sheet), TRUE);
2379  gtk_widget_set_can_default (GTK_WIDGET(sheet), TRUE);
2380 
2381  sheet->num_visible_blocks = 1;
2382  sheet->num_visible_phys_rows = 1;
2383 
2384  sheet->input_cancelled = FALSE;
2385 
2386  sheet->popup = NULL;
2387  sheet->num_virt_rows = 0;
2388  sheet->num_virt_cols = 0;
2389  sheet->item_editor = NULL;
2390  sheet->entry = NULL;
2391  sheet->editing = FALSE;
2392  sheet->button = 0;
2393  sheet->grabbed = FALSE;
2394  sheet->window_width = -1;
2395  sheet->window_height = -1;
2396  sheet->width = 0;
2397  sheet->height = 0;
2398 
2399  sheet->cursor_styles = g_hash_table_new (g_str_hash, g_str_equal);
2400 
2401  sheet->blocks = g_table_new (sizeof (SheetBlock),
2402  gnucash_sheet_block_construct,
2403  gnucash_sheet_block_destroy, sheet);
2404 
2405  gtk_widget_add_events (GTK_WIDGET(sheet),
2406  (GDK_EXPOSURE_MASK
2407  | GDK_BUTTON_PRESS_MASK
2408  | GDK_BUTTON_RELEASE_MASK
2409  | GDK_POINTER_MOTION_MASK
2410  | GDK_POINTER_MOTION_HINT_MASK));
2411 
2412  /* setup IMContext */
2413  sheet->direct_update_cell = FALSE;
2414  sheet->shift_state = 0;
2415  sheet->keyval_state = 0;
2416  sheet->bound = sheet->pos = 0;
2417 }
2418 
2419 
2420 GType
2421 gnucash_sheet_get_type (void)
2422 {
2423  static GType gnucash_sheet_type = 0;
2424 
2425  if (!gnucash_sheet_type)
2426  {
2427  static const GTypeInfo gnucash_sheet_info =
2428  {
2429  sizeof (GnucashSheetClass),
2430  NULL, /* base_init */
2431  NULL, /* base_finalize */
2432  (GClassInitFunc) gnucash_sheet_class_init,
2433  NULL, /* class_finalize */
2434  NULL, /* class_data */
2435  sizeof (GnucashSheet),
2436  0, /* n_preallocs */
2437  (GInstanceInitFunc) gnucash_sheet_init
2438  };
2439 
2440  gnucash_sheet_type =
2441  g_type_register_static (GTK_TYPE_LAYOUT,
2442  "GnucashSheet",
2443  &gnucash_sheet_info, 0);
2444  }
2445 
2446  return gnucash_sheet_type;
2447 }
2448 
2449 
2450 static gboolean
2451 gnucash_sheet_tooltip (GtkWidget *widget, gint x, gint y,
2452  gboolean keyboard_mode,
2453  GtkTooltip *tooltip,
2454  gpointer user_data)
2455 {
2456  GnucashSheet *sheet = GNUCASH_SHEET(widget);
2457  Table *table = sheet->table;
2458  VirtualLocation virt_loc;
2459  gchar *tooltip_text;
2460  gint cx, cy, cw, ch;
2461  GdkRectangle rect;
2462  SheetBlock *block;
2463  gint bx, by;
2464  gint hscroll_val, vscroll_val;
2465 
2466  if (keyboard_mode)
2467  return FALSE;
2468 
2469  // get the scroll window values
2470  hscroll_val = (gint) gtk_adjustment_get_value (sheet->hadj);
2471  vscroll_val = (gint) gtk_adjustment_get_value (sheet->vadj);
2472 
2473  if (!gnucash_sheet_find_loc_by_pixel (sheet, x + hscroll_val, y + vscroll_val, &virt_loc))
2474  return FALSE;
2475 
2476  tooltip_text = gnc_table_get_tooltip (table, virt_loc);
2477 
2478  // if tooltip_text empty, clear tooltip and return FALSE
2479  if (!tooltip_text || (g_strcmp0 (tooltip_text,"") == 0))
2480  {
2481  gtk_tooltip_set_text (tooltip, NULL);
2482  return FALSE;
2483  }
2484 
2485  block = gnucash_sheet_get_block (sheet, virt_loc.vcell_loc);
2486  if (!block)
2487  {
2488  g_free (tooltip_text);
2489  return FALSE;
2490  }
2491 
2492  bx = block->origin_x;
2493  by = block->origin_y;
2494 
2495  // get the cell location and dimensions
2496  gnucash_sheet_style_get_cell_pixel_rel_coords (block->style,
2497  virt_loc.phys_row_offset, virt_loc.phys_col_offset,
2498  &cx, &cy, &cw, &ch);
2499 
2500  rect.x = cx + bx - hscroll_val;
2501  rect.y = cy + by - vscroll_val;
2502  rect.width = cw;
2503  rect.height = ch;
2504 
2505  gtk_tooltip_set_tip_area (tooltip, &rect);
2506  gtk_tooltip_set_text (tooltip, tooltip_text);
2507  g_free (tooltip_text);
2508  return TRUE;
2509 }
2510 
2511 
2512 GtkWidget *
2513 gnucash_sheet_new (Table *table)
2514 {
2515  GnucashSheet *sheet;
2516 
2517  g_return_val_if_fail (table != NULL, NULL);
2518 
2519  sheet = gnucash_sheet_create (table);
2520 
2521  /* on create, the sheet can grab the focus */
2522  sheet->sheet_has_focus = TRUE;
2523 
2524  /* The cursor */
2525  sheet->cursor = gnucash_cursor_new (sheet);
2526 
2527  /* set up the editor */
2528  sheet->item_editor = gnc_item_edit_new (sheet);
2529 
2530  /* some register data */
2531  sheet->dimensions_hash_table = g_hash_table_new_full (g_int_hash,
2532  g_int_equal,
2533  g_free, g_free);
2534 
2535  /* add tooltips to sheet */
2536  gtk_widget_set_has_tooltip (GTK_WIDGET(sheet), TRUE);
2537  g_signal_connect (G_OBJECT(sheet), "query-tooltip",
2538  G_CALLBACK(gnucash_sheet_tooltip), NULL);
2539 
2540  gnucash_sheet_refresh_from_prefs (sheet);
2541 
2542  return GTK_WIDGET(sheet);
2543 }
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:55
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:50
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:53