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