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