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