GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
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 [email protected] *
18  * *
19 \********************************************************************/
20 
21 /*
22  * The Gnucash Header Canvas
23  *
24  * Authors:
25  * Heath Martin <[email protected]>
26  * Dave Peticolas <[email protected]>
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-grid.h"
38 #include "gnucash-cursor.h"
39 #include "gnucash-item-edit.h"
40 
41 #include "gnucash-header.h"
42 
43 static GnomeCanvasItem *parent_class;
44 
45 enum
46 {
47  PROP_0,
48  PROP_SHEET, /* the sheet this header is associated with */
49  PROP_CURSOR_NAME, /* the name of the current cursor */
50 };
51 
52 
53 static void
54 gnc_header_update (GnomeCanvasItem *item, double *affine,
55  ArtSVP *clip_path, int flags)
56 {
57  if (GNOME_CANVAS_ITEM_CLASS (parent_class)->update)
58  (*GNOME_CANVAS_ITEM_CLASS (parent_class)->update)
59  (item, affine, clip_path, flags);
60 
61  item->x1 = 0;
62  item->y1 = 0;
63  item->x2 = (INT_MAX / 2) - 1;
64  item->y2 = (INT_MAX / 2) - 1;
65 }
66 
67 
68 static void
69 gnc_header_draw (GnomeCanvasItem *item, GdkDrawable *drawable,
70  int x, int y, int width, int height)
71 {
72  GncHeader *header = GNC_HEADER(item);
73  SheetBlockStyle *style = header->style;
74  Table *table = header->sheet->table;
75  VirtualLocation virt_loc;
76  VirtualCell *vcell;
77  CellDimensions *cd;
78  GdkColor *bg_color, *fg_color;
79  int xpaint, ypaint;
80  const char *text;
81  CellBlock *cb;
82  guint32 argb, color_type;
83  int i, j;
84  int w, h;
85  PangoLayout *layout;
86 
87  virt_loc.vcell_loc.virt_row = 0;
88  virt_loc.vcell_loc.virt_col = 0;
89  virt_loc.phys_row_offset = 0;
90  virt_loc.phys_col_offset = 0;
91 
92  if (header->sheet->use_theme_colors)
93  {
94  color_type = gnc_table_get_gtkrc_bg_color (table, virt_loc,
95  NULL);
96  bg_color = get_gtkrc_color(header->sheet, color_type);
97  color_type = gnc_table_get_gtkrc_fg_color (table, virt_loc);
98  fg_color = get_gtkrc_color(header->sheet, color_type);
99  }
100  else
101  {
102  argb = gnc_table_get_bg_color (table, virt_loc, NULL);
103  bg_color = gnucash_color_argb_to_gdk (argb);
104  argb = gnc_table_get_fg_color (table, virt_loc);
105  fg_color = gnucash_color_argb_to_gdk (argb);
106  }
107 
108  h = style->dimensions->height;
109  h *= header->num_phys_rows;
110  h /= header->style->nrows;
111 
112  gdk_gc_set_foreground (header->gc, bg_color);
113 
114  gdk_draw_rectangle (drawable, header->gc, TRUE, 0, 0,
115  style->dimensions->width, h);
116 
117  gdk_gc_set_line_attributes (header->gc, 1, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER);
118  gdk_gc_set_foreground (header->gc, fg_color);
119 
120  gdk_draw_rectangle (drawable, header->gc, FALSE, -x, -y,
121  style->dimensions->width, h);
122 
123  gdk_draw_line (drawable, header->gc, 0, h + 1,
124  style->dimensions->width, h + 1);
125 
126  gdk_gc_set_line_attributes (header->gc, 1, GDK_LINE_SOLID, GDK_CAP_NOT_LAST, GDK_JOIN_MITER);
127  gdk_gc_set_background (header->gc, &gn_white);
128  gdk_gc_set_foreground (header->gc, fg_color);
129  /*font = gnucash_register_font;*/
130 
131  vcell = gnc_table_get_virtual_cell
132  (table, table->current_cursor_loc.vcell_loc);
133  cb = vcell ? vcell->cellblock : NULL;
134 
135  ypaint = -y;
136  h = 0;
137 
138  for (i = 0; i < style->nrows; i++)
139  {
140  xpaint = -x;
141  virt_loc.phys_row_offset = i;
142 
143  /* TODO: This routine is duplicated in several places.
144  Can we abstract at least the cell drawing routine?
145  That way we'll be sure everything is drawn
146  consistently, and cut down on maintenance issues. */
147 
148  for (j = 0; j < style->ncols; j++)
149  {
150  /* gint x_offset, y_offset;*/
151  GdkRectangle rect;
152  BasicCell *cell;
153 
154  virt_loc.phys_col_offset = j;
155 
156  cd = gnucash_style_get_cell_dimensions (style, i, j);
157 
158  if (header->in_resize && (j == header->resize_col))
159  w = header->resize_col_width;
160  else
161  w = cd->pixel_width;
162 
163  cell = gnc_cellblock_get_cell (cb, i, j);
164  if (!cell || !cell->cell_name)
165  {
166  xpaint += w;
167  continue;
168  }
169 
170  h = cd->pixel_height;
171 
172  gdk_draw_rectangle (drawable, header->gc, FALSE,
173  xpaint, ypaint, w, h);
174 
175  virt_loc.vcell_loc =
176  table->current_cursor_loc.vcell_loc;
177  text = gnc_table_get_label (table, virt_loc);
178  if (!text)
179  text = "";
180 
181  layout = gtk_widget_create_pango_layout (GTK_WIDGET (header->sheet), text);
182 
183  /*y_offset = ((h / 2) +
184  (((font->ascent + font->descent) / 2) -
185  font->descent));
186  y_offset++;*/
187 
188  switch (gnc_table_get_align (table, virt_loc))
189  {
190  default:
191  case CELL_ALIGN_LEFT:
192  pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
193  break;
194 
195  case CELL_ALIGN_RIGHT:
196  pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
197  break;
198 
199  case CELL_ALIGN_CENTER:
200  pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
201  break;
202  }
203 
204  rect.x = xpaint + CELL_HPADDING;
205  rect.y = ypaint + 1;
206  rect.width = MAX (0, w - (2 * CELL_HPADDING));
207  rect.height = h - 2;
208 
209  gdk_gc_set_clip_rectangle (header->gc, &rect);
210 
211  gdk_draw_layout (drawable,
212  header->gc,
213  xpaint + CELL_HPADDING,
214  ypaint + 1,
215  layout);
216 
217  g_object_unref (layout);
218 
219  gdk_gc_set_clip_rectangle (header->gc, NULL);
220 
221  xpaint += w;
222  }
223 
224  ypaint += h;
225  }
226 }
227 
228 
229 void
230 gnc_header_request_redraw (GncHeader *header)
231 {
232  GnomeCanvas *canvas = GNOME_CANVAS_ITEM(header)->canvas;
233 
234  if (header->style == NULL)
235  return;
236 
237  gnome_canvas_request_redraw (canvas, 0, 0,
238  header->width + 1,
239  header->height + 1);
240 }
241 
242 
243 static void
244 gnc_header_realize (GnomeCanvasItem *item)
245 {
246  GncHeader *header = GNC_HEADER (item);
247  GdkWindow *window;
248 
249  if (GNOME_CANVAS_ITEM_CLASS (parent_class)->realize)
250  GNOME_CANVAS_ITEM_CLASS (parent_class)->realize (item);
251 
252  window = GTK_WIDGET (item->canvas)->window;
253 
254  header->gc = gdk_gc_new (window);
255 }
256 
257 
258 static void
259 gnc_header_unrealize (GnomeCanvasItem *item)
260 {
261  GncHeader *header = GNC_HEADER (item);
262 
263  if (header->gc != NULL)
264  {
265  g_object_unref (header->gc);
266  header->gc = NULL;
267  }
268 
269  if (header->resize_cursor != NULL)
270  gdk_cursor_unref (header->resize_cursor);
271  header->resize_cursor = NULL;
272 
273  if (header->normal_cursor != NULL)
274  gdk_cursor_unref (header->normal_cursor);
275  header->normal_cursor = NULL;
276 
277  if (GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize)
278  GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize (item);
279 }
280 
281 
282 static void
283 gnc_header_finalize (GObject *object)
284 {
285  GncHeader *header;
286 
287  header = GNC_HEADER (object);
288 
289  g_free (header->cursor_name);
290  header->cursor_name = NULL;
291 
292  G_OBJECT_CLASS (parent_class)->finalize (object);
293 }
294 
295 
296 void
297 gnc_header_reconfigure (GncHeader *header)
298 {
299  GnomeCanvas *canvas;
300  GnucashSheet *sheet;
301  SheetBlockStyle *old_style;
302  int w, h;
303 
304  g_return_if_fail (header != NULL);
305  g_return_if_fail (GNC_IS_HEADER (header));
306 
307  canvas = GNOME_CANVAS_ITEM(header)->canvas;
308  sheet = GNUCASH_SHEET(header->sheet);
309  old_style = header->style;
310 
311  header->style = gnucash_sheet_get_style_from_cursor
312  (sheet, header->cursor_name);
313 
314  if (header->style == NULL)
315  return;
316 
317  sheet->width = header->style->dimensions->width;
318 
319  w = header->style->dimensions->width;
320  h = header->style->dimensions->height;
321  h *= header->num_phys_rows;
322  h /= header->style->nrows;
323  h += 2;
324 
325  if (header->height != h ||
326  header->width != w ||
327  header->style != old_style)
328  {
329  header->height = h;
330  header->width = w;
331 
332  gnome_canvas_set_scroll_region (GNOME_CANVAS(canvas),
333  0, 0, w, h);
334 
335  gtk_widget_set_size_request (GTK_WIDGET(canvas), -1, h);
336 
337  gnc_header_request_redraw (header);
338  }
339 }
340 
341 void
342 gnc_header_set_header_rows (GncHeader *header,
343  int num_phys_rows)
344 {
345  g_return_if_fail (header != NULL);
346  g_return_if_fail (GNC_IS_HEADER (header));
347 
348  header->num_phys_rows = num_phys_rows;
349 }
350 
351 static double
352 gnc_header_point (GnomeCanvasItem *item,
353  double x, double y, int cx, int cy,
354  GnomeCanvasItem **actual_item)
355 {
356  *actual_item = item;
357  return 0.0;
358 }
359 
360 /*
361  * Returns FALSE if pointer not on a resize line, else returns
362  * TRUE. Returns the index of the column to the left in the col
363  * argument.
364  */
365 static gboolean
366 pointer_on_resize_line (GncHeader *header, int x, int y, int *col)
367 {
368  SheetBlockStyle *style = header->style;
369  gboolean on_the_line = FALSE;
370  CellDimensions *cd;
371  int pixels = 0;
372  int j;
373 
374  for (j = 0; j < style->ncols; j++)
375  {
376  cd = gnucash_style_get_cell_dimensions (style, 0, j);
377  pixels += cd->pixel_width;
378  if (x >= pixels - 1 && x <= pixels + 1)
379  on_the_line = TRUE;
380  if (x <= pixels + 1)
381  break;
382  }
383 
384  if (col != NULL)
385  *col = j;
386 
387  return on_the_line;
388 }
389 
390 static int
391 find_resize_col (GncHeader *header, int col)
392 {
393  SheetBlockStyle *style = header->style;
394  CellDimensions *cd;
395  int start = col;
396 
397  if (col < 0 || col >= style->ncols)
398  return -1;
399 
400  /* skip to the right over zero-width columns */
401  while ((col + 1 < style->ncols) &&
402  (cd = gnucash_style_get_cell_dimensions (style, 0, col + 1)) &&
403  (cd->pixel_width == 0))
404  col++;
405 
406  /* now go back left till we have a resizable column */
407  while (col >= start)
408  {
409  if (gnucash_style_col_is_resizable (style, col))
410  return col;
411  else
412  col--;
413  }
414 
415  /* didn't find a resizable column to the right of col */
416  return -1;
417 }
418 
419 static void
420 gnc_header_resize_column (GncHeader *header, gint col, gint width)
421 {
422  GnucashSheet *sheet = header->sheet;
423 
424  gnucash_sheet_set_col_width (sheet, col, width);
425 
426  gnucash_cursor_configure (GNUCASH_CURSOR(sheet->cursor));
427  gnc_item_edit_configure (gnucash_sheet_get_item_edit (sheet));
428 
429  gnc_header_reconfigure (header);
430  gnucash_sheet_set_scroll_region (sheet);
431  gnucash_sheet_update_adjustments (sheet);
432 
433  gnc_header_request_redraw (header);
434  gnucash_sheet_redraw_all (sheet);
435 }
436 
437 static void
438 gnc_header_auto_resize_column (GncHeader *header, gint col)
439 {
440  int width;
441 
442  width = gnucash_sheet_col_max_width (header->sheet, 0, col);
443 
444  gnc_header_resize_column (header, col, width);
445 }
446 
447 static gint
448 gnc_header_event (GnomeCanvasItem *item, GdkEvent *event)
449 {
450  GncHeader *header = GNC_HEADER(item);
451  GnomeCanvas *canvas = item->canvas;
452  int x, y;
453  int col;
454 
455  switch (event->type)
456  {
457  case GDK_MOTION_NOTIFY:
458 
459  gnome_canvas_w2c (canvas, event->motion.x, event->motion.y,
460  &x, &y);
461 
462  if (header->in_resize)
463  {
464  int change = x - header->resize_x;
465  int new_width;
466 
467  if (!header->needs_ungrab)
468  {
469  gnome_canvas_item_grab (item,
470  GDK_POINTER_MOTION_MASK |
471  GDK_BUTTON_RELEASE_MASK,
472  header->resize_cursor,
473  event->button.time);
474  header->needs_ungrab = TRUE;
475  }
476 
477  new_width = header->resize_col_width + change;
478 
479  if (new_width >= 0)
480  {
481  header->resize_x = x;
482  header->resize_col_width = new_width;
483  gnc_header_request_redraw (header);
484  }
485 
486  break;
487  }
488 
489  if (pointer_on_resize_line(header, x, y, &col) &&
490  gnucash_style_col_is_resizable (header->style, col))
491  gdk_window_set_cursor (GTK_WIDGET(canvas)->window,
492  header->resize_cursor);
493  else
494  gdk_window_set_cursor (GTK_WIDGET(canvas)->window,
495  header->normal_cursor);
496  break;
497 
498  case GDK_BUTTON_PRESS:
499  {
500  int col;
501 
502  if (event->button.button != 1)
503  break;
504 
505  gnome_canvas_w2c (canvas, event->button.x, event->button.y,
506  &x, &y);
507 
508  if (pointer_on_resize_line (header, x, y, &col))
509  col = find_resize_col (header, col);
510  else
511  col = -1;
512 
513  if (col > -1)
514  {
515  CellDimensions *cd;
516 
517  cd = gnucash_style_get_cell_dimensions
518  (header->style, 0, col);
519 
520  header->in_resize = TRUE;
521  header->resize_col = col;
522  header->resize_col_width = cd->pixel_width;
523  header->resize_x = x;
524  }
525 
526  break;
527  }
528  case GDK_BUTTON_RELEASE:
529  {
530  if (event->button.button != 1)
531  break;
532 
533  gnome_canvas_w2c (canvas, event->button.x, event->button.y,
534  &x, &y);
535 
536  if (header->in_resize)
537  {
538  if (header->needs_ungrab)
539  {
540  gnome_canvas_item_ungrab (item,
541  event->button.time);
542  header->needs_ungrab = FALSE;
543 
544  gnc_header_resize_column
545  (header,
546  header->resize_col,
547  header->resize_col_width);
548  }
549  header->in_resize = FALSE;
550  header->resize_col = -1;
551  }
552 
553  break;
554  }
555 
556  case GDK_2BUTTON_PRESS:
557  {
558  gboolean on_line;
559  int ptr_col;
560  int resize_col;
561 
562  if (event->button.button != 1)
563  break;
564 
565  gnome_canvas_w2c (canvas, event->button.x, event->button.y,
566  &x, &y);
567 
568  on_line = pointer_on_resize_line (header, x, y, &ptr_col);
569 
570  /* If we're on a resize line and the column to the right is zero
571  width, resize that one. */
572  if (on_line)
573  resize_col = find_resize_col (header, ptr_col);
574  else
575  resize_col = ptr_col;
576 
577  if (resize_col > -1)
578  {
579  header->in_resize = FALSE;
580  header->resize_col = -1;
581  if (header->needs_ungrab)
582  {
583  gnome_canvas_item_ungrab (item,
584  event->button.time);
585  header->needs_ungrab = FALSE;
586  }
587 
588  gnc_header_auto_resize_column (header, resize_col);
589  }
590 
591  }
592  break;
593 
594  default:
595  break;
596  }
597 
598  return TRUE;
599 }
600 
601 
602 /* Note that g_value_set_object() refs the object, as does
603  * g_object_get(). But g_object_get() only unrefs once when it disgorges
604  * the object, leaving an unbalanced ref, which leaks. So instead of
605  * using g_value_set_object(), use g_value_take_object() which doesn't
606  * ref the object when used in get_property().
607  */
608 static void
609 gnc_header_get_property (GObject *object,
610  guint param_id,
611  GValue *value,
612  GParamSpec *pspec)
613 {
614  GncHeader *header = GNC_HEADER (object);
615 
616  switch (param_id)
617  {
618  case PROP_SHEET:
619  g_value_take_object (value, header->sheet);
620  break;
621  case PROP_CURSOR_NAME:
622  g_value_set_string (value, header->cursor_name);
623  break;
624  default:
625  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
626  break;
627  }
628 }
629 
630 static void
631 gnc_header_set_property (GObject *object,
632  guint param_id,
633  const GValue *value,
634  GParamSpec *pspec)
635 {
636  GncHeader *header = GNC_HEADER (object);
637  GtkLayout *layout = GTK_LAYOUT (GNOME_CANVAS_ITEM (header)->canvas);
638  gboolean needs_update = FALSE;
639  gchar *old_name;
640 
641  switch (param_id)
642  {
643  case PROP_SHEET:
644  header->sheet = GNUCASH_SHEET (g_value_get_object (value));
645  gtk_layout_set_hadjustment (layout, header->sheet->hadj);
646  needs_update = TRUE;
647  break;
648  case PROP_CURSOR_NAME:
649  old_name = header->cursor_name;
650 
651  header->cursor_name = g_value_dup_string (value);
652  needs_update = !old_name || !header->cursor_name ||
653  strcmp (old_name, header->cursor_name) != 0;
654  g_free (old_name);
655  break;
656  default:
657  G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
658  break;
659  }
660 
661  if ((header->sheet != NULL) && needs_update)
662  gnc_header_reconfigure (header);
663 }
664 
665 
666 static void
667 gnc_header_init (GncHeader *header)
668 {
669  header->sheet = NULL;
670  header->cursor_name = NULL;
671  header->in_resize = FALSE;
672  header->resize_col = -1;
673  header->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW);
674  header->normal_cursor = NULL;
675  header->height = 20;
676  header->width = 400;
677  header->style = NULL;
678 }
679 
680 
681 static void
682 gnc_header_class_init (GncHeaderClass *header_class)
683 {
684  GObjectClass *object_class = G_OBJECT_CLASS (header_class);
685  GnomeCanvasItemClass *item_class = GNOME_CANVAS_ITEM_CLASS (header_class);
686 
687  parent_class = g_type_class_peek_parent (header_class);
688 
689  object_class->finalize = gnc_header_finalize;
690  object_class->get_property = gnc_header_get_property;
691  object_class->set_property = gnc_header_set_property;
692 
693  g_object_class_install_property (object_class,
694  PROP_SHEET,
695  g_param_spec_object ("sheet",
696  "Sheet Value",
697  "Sheet Value",
698  GNUCASH_TYPE_SHEET,
699  G_PARAM_READWRITE));
700  g_object_class_install_property (object_class,
701  PROP_CURSOR_NAME,
702  g_param_spec_string ("cursor_name",
703  "Cursor Name",
704  "Cursor Name",
705  CURSOR_HEADER,
706  G_PARAM_READWRITE));
707 
708 
709  item_class->realize = gnc_header_realize;
710  item_class->unrealize = gnc_header_unrealize;
711  item_class->update = gnc_header_update;
712  item_class->draw = gnc_header_draw;
713  item_class->event = gnc_header_event;
714  item_class->point = gnc_header_point;
715 }
716 
717 
718 GType
719 gnc_header_get_type (void)
720 {
721  static GType gnc_header_type = 0;
722 
723  if (!gnc_header_type)
724  {
725  static const GTypeInfo gnc_header_info =
726  {
727  sizeof (GncHeaderClass),
728  NULL,
729  NULL,
730  (GClassInitFunc) gnc_header_class_init,
731  NULL,
732  NULL,
733  sizeof (GncHeader),
734  0,
735  (GInstanceInitFunc) gnc_header_init
736  };
737 
738  gnc_header_type = g_type_register_static (gnome_canvas_item_get_type (),
739  "GncHeader",
740  &gnc_header_info, 0);
741  }
742 
743  return gnc_header_type;
744 }
745 
746 
747 static void
748 gnc_header_realized (GtkWidget *widget, gpointer data)
749 {
750  gdk_window_set_back_pixmap (GTK_LAYOUT (widget)->bin_window,
751  NULL, FALSE);
752 }
753 
754 
755 GtkWidget *
756 gnc_header_new (GnucashSheet *sheet)
757 {
758  GnomeCanvasGroup *group;
759  GnomeCanvasItem *item;
760  GtkWidget *canvas;
761 
762  canvas = gnome_canvas_new ();
763 
764  g_signal_connect (G_OBJECT (canvas), "realize",
765  G_CALLBACK (gnc_header_realized),
766  NULL);
767 
768  group = GNOME_CANVAS_GROUP (GNOME_CANVAS (canvas)->root);
769 
770  item = gnome_canvas_item_new (group,
771  gnc_header_get_type (),
772  "sheet", sheet,
773  "cursor_name", CURSOR_HEADER,
774  NULL);
775 
776  sheet->header_item = item;
777 
778  gtk_widget_show (canvas);
779 
780  return canvas;
781 }
782 
783