GnuCash c935c2f+
Loading...
Searching...
No Matches
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 */
60enum
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. */
72static QofLogModule log_module = G_LOG_DOMAIN;
73
76static void gnucash_sheet_start_editing_at_cursor (GnucashSheet *sheet);
77
78static gboolean gnucash_sheet_cursor_move (GnucashSheet *sheet,
79 VirtualLocation virt_loc);
80
81static void gnucash_sheet_deactivate_cursor_cell (GnucashSheet *sheet);
82static void gnucash_sheet_activate_cursor_cell (GnucashSheet *sheet,
83 gboolean changed_cells);
84static void gnucash_sheet_stop_editing (GnucashSheet *sheet);
85gboolean gnucash_sheet_draw_cb (GtkWidget *widget, cairo_t *cr,
86 G_GNUC_UNUSED gpointer data);
87
90G_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
100static inline void
101gnucash_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
108static inline void
109gnucash_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
115static inline void
116gnucash_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.
125static inline void
126gnucash_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
139static inline void
140gnucash_sheet_set_position (GnucashSheet* sheet, int pos)
141{
142 gnucash_sheet_set_position_and_selection (sheet, pos, pos, pos);
143}
144
145static inline void
146gnucash_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
162static inline gboolean
163gnucash_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
172static gboolean
173gnucash_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
195static void
196gnucash_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
217void
218gnucash_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
238void
239gnucash_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
252static void
253gnucash_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
262static void
263gnucash_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
284static void
285gnucash_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
299void
300gnucash_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
311gint
312gnucash_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
341static void
342gnucash_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
436static gboolean
437gnucash_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
488static gint
489gnucash_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
510void
511gnucash_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
554static void
555gnucash_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
614void
615gnucash_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
629void
630gnucash_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
697void
698gnucash_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
716static void
717gnucash_sheet_vadjustment_value_changed (GtkAdjustment *adj,
718 GnucashSheet *sheet)
719{
720 gnucash_sheet_compute_visible_range (sheet);
721}
722
723
724void
725gnucash_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
735void
736gnucash_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
744void
745gnucash_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
768gboolean
769gnucash_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
776void
777gnucash_sheet_set_has_focus (GnucashSheet *sheet, gboolean has_focus)
778{
779 sheet->sheet_has_focus = has_focus;
780}
781
782static void
783gnucash_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
804static GnucashSheet *
805gnucash_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
826static void
827gnucash_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 */
836static void
837gnucash_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
864const char *
865gnucash_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
908typedef struct
909{
910 GtkEditable *editable;
911 int start_sel;
912 int end_sel;
913
915
916static gboolean
917gnucash_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
959static inline void
960normalize_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
973static inline char*
974insert_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
985static char*
986make_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
1030static void
1031gnucash_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
1107static char*
1108delete_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
1136static void
1137gnucash_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
1180gboolean
1181gnucash_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
1201static void
1202gnucash_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
1248static gboolean
1249gnucash_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
1262static gboolean
1263gnucash_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
1275static void
1276gnucash_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
1304static gboolean
1305gnucash_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
1329static float
1330clamp_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}
1338static gboolean
1339gnucash_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
1395static void
1396gnucash_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
1420static gboolean
1421gnucash_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
1531void
1532gnucash_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
1569static gboolean
1570gnucash_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
1619static void
1620gnucash_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
1641static gboolean
1642process_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;
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;
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
1750static gboolean
1751pass_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
1765static gboolean
1766gnucash_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
1848static gboolean
1849gnucash_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
1859void
1860gnucash_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
1887SheetBlock *
1888gnucash_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
1898GncItemEdit *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
1910void 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. */
1924gboolean
1925gnucash_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
1961gint
1962gnucash_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
2041void
2042gnucash_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
2064static void
2065gnucash_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
2080static void
2081gnucash_sheet_block_construct (gpointer _block, gpointer user_data)
2082{
2083 SheetBlock *block = _block;
2084
2085 block->style = NULL;
2086 block->visible = TRUE;
2087}
2088
2089static void
2090gnucash_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
2105void
2106gnucash_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
2148void
2149gnucash_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
2209void
2210gnucash_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
2273static void
2274gnucash_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
2302static void
2303gnucash_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
2342static gboolean
2343gnucash_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
2403static void
2404dimensions_destroy (BlockDimensions *dimensions)
2405{
2406 if (dimensions)
2407 {
2408 g_table_destroy (dimensions->cell_dimensions);
2409 g_free (dimensions);
2410 }
2411}
2412
2413GtkWidget *
2414gnucash_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}
All type declarations for the whole Gnucash engine.
gtk helper routines.
Generic api to store and retrieve preferences.
Convenience wrapper around GdkRGBA for use in Register Gnome classes.
Public declarations for GnucashCursor class.
Public declarations for GnucashHeader class.
Public declarations for GncItemEdit class.
Private declarations for GnucashSheet class.
Public declarations of GnucashRegister class.
Styling functions for RegisterGnome.
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.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define LEAVE(format, args...)
Print a function exit debugging message.
Definition qoflog.h:282
#define ENTER(format, args...)
Print a function entry debugging message.
Definition qoflog.h:272
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
void g_table_destroy(GTable *gtable)
Free the table and all associated table elements.
Definition gtable.c:69
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
RegisterColor
Color definitions used for table elements.
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.
gpointer g_table_index(GTable *gtable, int row, int col)
Return the element at the given row and column.
Definition gtable.c:84
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 CURSOR_HEADER
Standard Cursor Names.
VirtualCell * gnc_table_get_virtual_cell(Table *table, VirtualCellLocation vcell_loc)
returns the virtual cell associated with a particular virtual location.
API for checkbook register display area.
SheetBlockStyle * style
The style for this block.
gboolean visible
y origin of block
gint origin_y
x origin of block
holds information about each virtual cell.
unsigned int visible
Used by higher-level code.