GnuCash c935c2f+
Loading...
Searching...
No Matches
gnucash-header.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 Header Canvas
23 *
24 * Authors:
25 * Heath Martin <martinh@pegasus.cc.ucf.edu>
26 * Dave Peticolas <dave@krondo.com>
27 */
28
29#include <config.h>
30
31#include <string.h>
32
33#include "gnucash-sheet.h"
34#include "gnucash-sheetP.h"
35#include "gnucash-color.h"
36#include "gnucash-style.h"
37#include "gnucash-cursor.h"
38#include "gnucash-item-edit.h"
39#include "gnc-gtk-utils.h"
40
41#include "gnucash-header.h"
42
43enum
44{
45 PROP_0,
46 PROP_SHEET, /* the sheet this header is associated with */
47 PROP_CURSOR_NAME, /* the name of the current cursor */
48};
49
50G_DEFINE_TYPE (GncHeader, gnc_header, GTK_TYPE_LAYOUT)
51
52static void
53gnc_header_draw_offscreen (GncHeader *header)
54{
55 g_return_if_fail (GTK_IS_WIDGET(header));
56
57 if (!gtk_widget_get_realized (GTK_WIDGET(header)))
58 return;
59
60 SheetBlockStyle *style = header->style;
61 GncItemEdit *item_edit = GNC_ITEM_EDIT(header->sheet->item_editor);
62 Table *table = header->sheet->table;
63 VirtualLocation virt_loc;
64 VirtualCell *vcell;
65 guint32 color_type;
66 GtkStyleContext *stylectxt = gtk_widget_get_style_context (GTK_WIDGET(header));
67 GdkRGBA color;
68 int row_offset;
69 CellBlock *cb;
70 int i;
71 int scale;
72 cairo_t *cr;
73
74 virt_loc.vcell_loc.virt_row = 0;
75 virt_loc.vcell_loc.virt_col = 0;
76 virt_loc.phys_row_offset = 0;
77 virt_loc.phys_col_offset = 0;
78
79 gtk_style_context_save (stylectxt);
80
81 // Get the color type and apply the css class
82 color_type = gnc_table_get_color (table, virt_loc, NULL);
83 gnucash_get_style_classes (header->sheet, stylectxt, color_type, FALSE);
84
85 if (header->surface)
86 cairo_surface_destroy (header->surface);
87 scale = gtk_widget_get_scale_factor (GTK_WIDGET(header));
88 if (scale < 1)
89 scale = 1;
90 header->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
91 header->width * scale,
92 header->height * scale);
93 cairo_surface_set_device_scale (header->surface, scale, scale);
94
95 cr = cairo_create (header->surface);
96
97 // Fill background color of header
98 gtk_render_background (stylectxt, cr, 0, 0, header->width, header->height);
99
100 gdk_rgba_parse (&color, "black");
101 cairo_set_source_rgb (cr, color.red, color.green, color.blue);
102 cairo_rectangle (cr, 0.5, 0.5, header->width - 1.0, header->height - 1.0);
103 cairo_set_line_width (cr, 1.0);
104 cairo_stroke (cr);
105
106 // Draw bottom horizontal line, makes bottom line thicker
107 cairo_move_to (cr, 0.5, header->height - 1.5);
108 cairo_line_to (cr, header->width - 1.0, header->height - 1.5);
109 cairo_set_line_cap (cr, CAIRO_LINE_CAP_SQUARE);
110 cairo_set_line_width (cr, 1.0);
111 cairo_stroke (cr);
112
113 /*font = gnucash_register_font;*/
114
116 (table, table->current_cursor_loc.vcell_loc);
117 cb = vcell ? vcell->cellblock : NULL;
118 row_offset = 0;
119
120 for (i = 0; i < style->nrows; i++)
121 {
122 int col_offset = 0;
123 int height = 0, j;
124 virt_loc.phys_row_offset = i;
125
126 /* TODO: This routine is duplicated in several places.
127 Can we abstract at least the cell drawing routine?
128 That way we'll be sure everything is drawn
129 consistently, and cut down on maintenance issues. */
130
131 for (j = 0; j < style->ncols; j++)
132 {
133 CellDimensions *cd;
134 BasicCell *cell;
135 const char *text;
136 int width;
137 PangoLayout *layout;
138 PangoRectangle logical_rect;
139 GdkRectangle rect;
140 int x_offset;
141
142 virt_loc.phys_col_offset = j;
143
144 cd = gnucash_style_get_cell_dimensions (style, i, j);
145 if (!cd) continue;
146
147 height = cd->pixel_height;
148 if (header->in_resize && (j == header->resize_col))
149 width = header->resize_col_width;
150 else
151 width = cd->pixel_width;
152
153 cell = gnc_cellblock_get_cell (cb, i, j);
154 if (!cell || !cell->cell_name)
155 {
156 col_offset += width;
157 continue;
158 }
159
160 cairo_rectangle (cr, col_offset - 0.5, row_offset + 0.5, width, height);
161 cairo_set_line_width (cr, 1.0);
162 cairo_stroke (cr);
163
164 virt_loc.vcell_loc =
165 table->current_cursor_loc.vcell_loc;
166 text = gnc_table_get_label (table, virt_loc);
167 if (!text)
168 text = "";
169
170 layout = gtk_widget_create_pango_layout (GTK_WIDGET(header->sheet), text);
171
172 pango_layout_get_pixel_extents (layout, NULL, &logical_rect);
173
174 gnucash_sheet_set_text_bounds (header->sheet, &rect,
175 col_offset, row_offset, width, height);
176
177 cairo_save (cr);
178 cairo_rectangle (cr, rect.x, rect.y, rect.width, rect.height);
179 cairo_clip (cr);
180
181 x_offset = gnucash_sheet_get_text_offset (header->sheet, virt_loc,
182 rect.width, logical_rect.width);
183
184 gtk_render_layout (stylectxt, cr, rect.x + x_offset,
185 rect.y + gnc_item_edit_get_padding_border (item_edit, top), layout);
186
187 cairo_restore (cr);
188 g_object_unref (layout);
189
190 col_offset += width;
191 }
192 row_offset += height;
193 }
194 gtk_style_context_restore (stylectxt);
195
196 cairo_destroy (cr);
197}
198
199
200gint
201gnc_header_get_cell_offset (GncHeader *header, gint col, gint *cell_width)
202{
203 SheetBlockStyle *style = header->style;
204 gint j;
205 gint offset = 0;
206
207 for (j = 0; j < style->ncols; j++)
208 {
209 CellDimensions *cd;
210
211 cd = gnucash_style_get_cell_dimensions (style, 0, j);
212 if (!cd) continue;
213
214 if (j == col)
215 {
216 *cell_width = cd->pixel_width;
217 break;
218 }
219 offset = offset + cd->pixel_width;
220 }
221 return offset;
222}
223
224
225static gboolean
226gnc_header_draw (GtkWidget *header, cairo_t *cr)
227{
228 GnucashSheet *sheet = GNC_HEADER(header)->sheet;
229 GdkWindow *sheet_layout_win = gtk_layout_get_bin_window (GTK_LAYOUT(sheet));
230 gint x, y;
231
232 // use this to get the scroll x value to align the header
233 gdk_window_get_position (sheet_layout_win, &x, &y);
234
235 // if the register page is moved to another window, the surface is
236 // not created so test for a surface and create one if null
237 if (GNC_HEADER(header)->surface == NULL)
238 gnc_header_draw_offscreen (GNC_HEADER(header));
239
240 cairo_set_source_surface (cr, GNC_HEADER(header)->surface, x, 0);
241 cairo_paint (cr);
242
243 return TRUE;
244}
245
246
247void
248gnc_header_request_redraw (GncHeader *header)
249{
250 if (!header->style)
251 return;
252
253 gnc_header_draw_offscreen (header);
254 gtk_widget_queue_draw (GTK_WIDGET(header));
255}
256
257
258static void
259gnc_header_unrealize (GtkWidget *widget)
260{
261 GncHeader *header = GNC_HEADER(widget);
262 if (header->surface)
263 cairo_surface_destroy (header->surface);
264 header->surface = NULL;
265
266 if (header->resize_cursor)
267 g_object_unref (header->resize_cursor);
268 header->resize_cursor = NULL;
269
270 if (header->normal_cursor)
271 g_object_unref (header->normal_cursor);
272 header->normal_cursor = NULL;
273
274 if (GTK_WIDGET_CLASS(gnc_header_parent_class)->unrealize)
275 GTK_WIDGET_CLASS(gnc_header_parent_class)->unrealize (GTK_WIDGET(header));
276}
277
278
279static void
280gnc_header_finalize (GObject *object)
281{
282 GncHeader *header;
283
284 header = GNC_HEADER(object);
285
286 g_free (header->cursor_name);
287 header->cursor_name = NULL;
288
289 G_OBJECT_CLASS(gnc_header_parent_class)->finalize (object);
290}
291
292
293void
294gnc_header_reconfigure (GncHeader *header)
295{
296 GnucashSheet *sheet;
297 SheetBlockStyle *old_style;
298 int w, h;
299
300 g_return_if_fail (header != NULL);
301 g_return_if_fail (GNC_IS_HEADER(header));
302
303 sheet = GNUCASH_SHEET(header->sheet);
304 old_style = header->style;
305
306 header->style = gnucash_sheet_get_style_from_cursor
307 (sheet, header->cursor_name);
308
309 if (header->style == NULL)
310 return;
311
312 sheet->width = header->style->dimensions->width;
313
314 w = header->style->dimensions->width;
315 h = header->style->dimensions->height;
316 h *= header->num_phys_rows;
317 h /= header->style->nrows;
318 h += 2;
319
320 if (header->height != h ||
321 header->width != w ||
322 header->style != old_style)
323 {
324 header->height = h;
325 header->width = w;
326 gtk_layout_set_size (GTK_LAYOUT(header), w, h);
327 gtk_widget_set_size_request (GTK_WIDGET(header), -1, h);
328 gnc_header_request_redraw (header);
329 }
330}
331
332void
333gnc_header_set_header_rows (GncHeader *header,
334 int num_phys_rows)
335{
336 g_return_if_fail (header != NULL);
337 g_return_if_fail (GNC_IS_HEADER(header));
338
339 header->num_phys_rows = num_phys_rows;
340}
341
342/*
343 * Returns FALSE if pointer not on a resize line, else returns
344 * TRUE. Returns the index of the column to the left in the col
345 * argument.
346 */
347static gboolean
348pointer_on_resize_line (GncHeader *header, int x, G_GNUC_UNUSED int y, int *col)
349{
350 SheetBlockStyle *style = header->style;
351 gboolean on_the_line = FALSE;
352 CellDimensions *cd;
353 int pixels = 0;
354 int j;
355
356 for (j = 0; j < style->ncols; j++)
357 {
358 cd = gnucash_style_get_cell_dimensions (style, 0, j);
359 if (!cd) continue;
360
361 pixels += cd->pixel_width;
362 if (x >= pixels - 1 && x <= pixels + 1)
363 on_the_line = TRUE;
364 if (x <= pixels + 1)
365 break;
366 }
367
368 if (col != NULL)
369 *col = j;
370
371 return on_the_line;
372}
373
374static int
375find_resize_col (GncHeader *header, int col)
376{
377 SheetBlockStyle *style = header->style;
378 CellDimensions *cd;
379 int start = col;
380
381 if (col < 0 || col >= style->ncols)
382 return -1;
383
384 /* skip to the right over zero-width columns */
385 while ((col + 1 < style->ncols) &&
386 (cd = gnucash_style_get_cell_dimensions (style, 0, col + 1)) &&
387 cd && (cd->pixel_width == 0))
388 ++col;
389
390 /* now go back left till we have a resizable column */
391 while (col >= start)
392 {
393 if (gnucash_style_col_is_resizable (style, col))
394 return col;
395 else
396 col--;
397 }
398
399 /* didn't find a resizable column to the right of col */
400 return -1;
401}
402
403static void
404gnc_header_resize_column (GncHeader *header, gint col, gint width)
405{
406 GnucashSheet *sheet = header->sheet;
407
408 gnucash_sheet_set_col_width (sheet, col, width);
409
410 gnucash_cursor_configure (GNUCASH_CURSOR(sheet->cursor));
411 gnc_item_edit_configure (gnucash_sheet_get_item_edit (sheet));
412
413 gnc_header_reconfigure (header);
414
415 gnucash_sheet_set_scroll_region (sheet);
416 gnucash_sheet_update_adjustments (sheet);
417
418 gnc_header_request_redraw (header);
419 gnucash_sheet_redraw_all (sheet);
420}
421
422static void
423gnc_header_auto_resize_column (GncHeader *header, gint col)
424{
425 int width;
426
427 width = gnucash_sheet_col_max_width (header->sheet, 0, col);
428
429 gnc_header_resize_column (header, col, width);
430}
431
432static gint
433gnc_header_event (GtkWidget *widget, GdkEvent *event)
434{
435 GncHeader *header = GNC_HEADER(widget);
436 GdkWindow *window = gtk_widget_get_window (widget);
437 int x, y;
438 int col;
439
440 if (!header->resize_cursor)
441 header->resize_cursor = gdk_cursor_new_for_display (gdk_window_get_display (window),
442 GDK_SB_H_DOUBLE_ARROW);
443
444 switch (event->type)
445 {
446 case GDK_MOTION_NOTIFY:
447 x = event->motion.x;
448 y = event->motion.y;
449
450 if (header->in_resize)
451 {
452 int change = x - header->resize_x;
453 int new_width = header->resize_col_width + change;
454
455 if (new_width >= 0)
456 {
457 header->resize_x = x;
458 header->resize_col_width = new_width;
459 gnc_header_request_redraw (header);
460 }
461
462 break;
463 }
464
465 if (pointer_on_resize_line (header, x, y, &col) &&
466 gnucash_style_col_is_resizable (header->style, col))
467 gdk_window_set_cursor (window, header->resize_cursor);
468 else
469 gdk_window_set_cursor (window, header->normal_cursor);
470 break;
471
472 case GDK_BUTTON_PRESS:
473 {
474 int col;
475
476 if (event->button.button != 1)
477 break;
478
479 x = event->button.x;
480 y = event->button.y;
481
482 if (pointer_on_resize_line (header, x, y, &col))
483 col = find_resize_col (header, col);
484 else
485 col = -1;
486
487 if (col > -1)
488 {
489 CellDimensions *cd;
490
491 cd = gnucash_style_get_cell_dimensions
492 (header->style, 0, col);
493 if (!cd) break;
494
495 header->in_resize = TRUE;
496 header->resize_col = col;
497 header->resize_col_width = cd->pixel_width;
498 header->resize_x = x;
499 }
500 break;
501 }
502 case GDK_BUTTON_RELEASE:
503 {
504 if (event->button.button != 1)
505 break;
506
507 if (header->in_resize)
508 {
509 if (header->resize_col_width == 0)
510 header->resize_col_width = 1;
511
512 gnc_header_resize_column
513 (header,
514 header->resize_col,
515 header->resize_col_width);
516 header->in_resize = FALSE;
517 header->resize_col = -1;
518 gnc_header_request_redraw (header);
519 }
520 break;
521 }
522
523 case GDK_2BUTTON_PRESS:
524 {
525 gboolean on_line;
526 int ptr_col;
527 int resize_col;
528
529 if (event->button.button != 1)
530 break;
531
532 x = event->button.x;
533 y = event->button.y;
534
535 on_line = pointer_on_resize_line (header, x, y, &ptr_col);
536
537 /* If we're on a resize line and the column to the right is zero
538 width, resize that one. */
539 if (on_line)
540 resize_col = find_resize_col (header, ptr_col);
541 else
542 resize_col = ptr_col;
543
544 if (resize_col > -1)
545 {
546 header->in_resize = FALSE;
547 header->resize_col = -1;
548 gnc_header_auto_resize_column (header, resize_col);
549 }
550 }
551 break;
552
553 default:
554 break;
555 }
556 return FALSE;
557}
558
559
560/* Note that g_value_set_object() refs the object, as does
561 * g_object_get(). But g_object_get() only unrefs once when it disgorges
562 * the object, leaving an unbalanced ref, which leaks. So instead of
563 * using g_value_set_object(), use g_value_take_object() which doesn't
564 * ref the object when used in get_property().
565 */
566static void
567gnc_header_get_property (GObject *object,
568 guint param_id,
569 GValue *value,
570 GParamSpec *pspec)
571{
572 GncHeader *header = GNC_HEADER(object);
573
574 switch (param_id)
575 {
576 case PROP_SHEET:
577 g_value_take_object (value, header->sheet);
578 break;
579 case PROP_CURSOR_NAME:
580 g_value_set_string (value, header->cursor_name);
581 break;
582 default:
583 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
584 break;
585 }
586}
587
588static void
589gnc_header_set_property (GObject *object,
590 guint param_id,
591 const GValue *value,
592 GParamSpec *pspec)
593{
594 GncHeader *header = GNC_HEADER(object);
595 GtkLayout *layout = GTK_LAYOUT(header);
596 gboolean needs_update = FALSE;
597 gchar *old_name;
598
599 switch (param_id)
600 {
601 case PROP_SHEET:
602 header->sheet = GNUCASH_SHEET(g_value_get_object (value));
603 gtk_scrollable_set_hadjustment (GTK_SCROLLABLE(layout), header->sheet->hadj);
604 needs_update = TRUE;
605 break;
606 case PROP_CURSOR_NAME:
607 old_name = header->cursor_name;
608
609 header->cursor_name = g_value_dup_string (value);
610 needs_update = !old_name || !header->cursor_name ||
611 strcmp (old_name, header->cursor_name) != 0;
612 g_free (old_name);
613 break;
614 default:
615 G_OBJECT_WARN_INVALID_PROPERTY_ID(object, param_id, pspec);
616 break;
617 }
618
619 if ((header->sheet != NULL) && needs_update)
620 gnc_header_reconfigure (header);
621}
622
623
624static void
625gnc_header_init (GncHeader *header)
626{
627 header->sheet = NULL;
628 header->cursor_name = NULL;
629 header->in_resize = FALSE;
630 header->resize_col = -1;
631 header->resize_cursor = NULL;
632 header->normal_cursor = NULL;
633 header->height = 20;
634 header->width = 400;
635 header->style = NULL;
636
637 gtk_widget_add_events (GTK_WIDGET(header),
638 (GDK_EXPOSURE_MASK
639 | GDK_BUTTON_PRESS_MASK
640 | GDK_BUTTON_RELEASE_MASK
641 | GDK_POINTER_MOTION_MASK
642 | GDK_POINTER_MOTION_HINT_MASK));
643
644 g_signal_connect (G_OBJECT(header), "configure_event",
645 G_CALLBACK(gnc_header_reconfigure), NULL);
646 gtk_widget_show_all (GTK_WIDGET(header));
647}
648
649
650static void
651gnc_header_class_init (GncHeaderClass *header_class)
652{
653 GObjectClass *object_class = G_OBJECT_CLASS(header_class);
654 GtkWidgetClass *item_class = GTK_WIDGET_CLASS(header_class);
655
656 gtk_widget_class_set_css_name (GTK_WIDGET_CLASS(header_class), "gnc-id-header");
657
658 object_class->finalize = gnc_header_finalize;
659 object_class->get_property = gnc_header_get_property;
660 object_class->set_property = gnc_header_set_property;
661
662 g_object_class_install_property (object_class,
663 PROP_SHEET,
664 g_param_spec_object ("sheet",
665 "Sheet Value",
666 "Sheet Value",
667 GNUCASH_TYPE_SHEET,
668 G_PARAM_READWRITE));
669 g_object_class_install_property (object_class,
670 PROP_CURSOR_NAME,
671 g_param_spec_string ("cursor_name",
672 "Cursor Name",
673 "Cursor Name",
675 G_PARAM_READWRITE));
676
677
678 item_class->unrealize = gnc_header_unrealize;
679 item_class->draw = gnc_header_draw;
680 item_class->event = gnc_header_event;
681}
682
683GtkWidget *
684gnc_header_new (GnucashSheet *sheet)
685{
686 GtkWidget *layout;
687
688 layout = g_object_new (GNC_TYPE_HEADER,
689 "sheet", sheet,
690 "cursor_name", CURSOR_HEADER,
691 NULL);
692
693 sheet->header_item = layout;
694 return layout;
695}
696
gtk helper routines.
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.
BasicCell * gnc_cellblock_get_cell(CellBlock *cellblock, int row, int col)
Retrieve the Cell at the specified coordinates.
Definition cellblock.c:109
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 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.
holds information about each virtual cell.