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