GnuCash  2.6.99
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
gnc-dense-cal.c
1 /********************************************************************\
2  * gnc-dense-cal.c : a custom densely-dispalyed calendar widget *
3  * Copyright (C) 2002,2006 Joshua Sled <[email protected]> *
4  * *
5  * This program is free software; you can redistribute it and/or *
6  * modify it under the terms of the GNU General Public License as *
7  * published by the Free Software Foundation; either version 2 of *
8  * the License, or (at your option) any later version. *
9  * *
10  * This program is distributed in the hope that it will be useful, *
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13  * GNU General Public License for more details. *
14  * *
15  * You should have received a copy of the GNU General Public License*
16  * along with this program; if not, contact: *
17  * *
18  * Free Software Foundation Voice: +1-617-542-5942 *
19  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20  * Boston, MA 02110-1301, USA [email protected] *
21 \********************************************************************/
22 
23 #include "config.h"
24 
25 #include "gnc-dense-cal.h"
26 #include "gnc-dense-cal-model.h"
27 #include "gnc-engine.h"
28 #include <glib.h>
29 #include <glib/gi18n.h>
30 #include <gtk/gtk.h>
31 #include <math.h>
32 #include <stdlib.h>
33 #include <gnc-gdate-utils.h>
34 
66 static const int DENSE_CAL_DEFAULT_WIDTH = 15;
67 static const int DENSE_CAL_DEFAULT_HEIGHT = 105;
68 static const int MINOR_BORDER_SIZE = 1;
69 static const int COL_BORDER_SIZE = 3;
70 
71 static const gchar* MONTH_THIS_COLOR = "lavender";
72 static const gchar* MONTH_THAT_COLOR = "SlateGray1";
73 
74 static const gchar* MARK_COLOR = "Yellow";
75 
76 #undef G_LOG_DOMAIN
77 #define G_LOG_DOMAIN "gnc.gui.dense-cal"
78 
79 static void gnc_dense_cal_class_init(GncDenseCalClass *klass);
80 static void gnc_dense_cal_init(GncDenseCal *dcal);
81 static void gnc_dense_cal_finalize(GObject *object);
82 static void gnc_dense_cal_dispose(GObject *object);
83 static void gnc_dense_cal_realize(GtkWidget *widget, gpointer user_data);
84 static void gnc_dense_cal_configure(GtkWidget *widget, GdkEventConfigure *event, gpointer user_data);
85 static void gnc_dense_cal_draw_to_buffer(GncDenseCal *dcal);
86 static gboolean gnc_dense_cal_expose(GtkWidget *widget, GdkEventExpose *event, gpointer user_data);
87 
88 static void gdc_reconfig(GncDenseCal *dcal);
89 
90 static void gdc_free_all_mark_data(GncDenseCal *dcal);
91 
92 static void _gdc_compute_min_size(GncDenseCal *dcal,
93  guint *min_width, guint *min_height);
94 static void _gdc_set_cal_min_size_req(GncDenseCal *dcal);
95 static gint gnc_dense_cal_motion_notify(GtkWidget *widget,
96  GdkEventMotion *event);
97 static gint gnc_dense_cal_button_press(GtkWidget *widget,
98  GdkEventButton *evt);
99 
100 static void _gdc_view_option_changed(GtkComboBox *widget, gpointer user_data);
101 
102 static inline int day_width_at(GncDenseCal *dcal, guint xScale);
103 static inline int day_width(GncDenseCal *dcal);
104 static inline int day_height_at(GncDenseCal *dcal, guint yScale);
105 static inline int day_height(GncDenseCal *dcal);
106 static inline int week_width_at(GncDenseCal *dcal, guint xScale);
107 static inline int week_width(GncDenseCal *dcal);
108 static inline int week_height_at(GncDenseCal *dcal, guint yScale);
109 static inline int week_height(GncDenseCal *dcal);
110 static inline int col_width_at(GncDenseCal *dcal, guint xScale);
111 static inline int col_width(GncDenseCal *dcal);
112 
113 static inline int col_height(GncDenseCal *dcal);
114 static inline int num_cols(GncDenseCal *dcal);
115 
116 static void _gnc_dense_cal_set_month(GncDenseCal *dcal, GDateMonth mon, gboolean redraw);
117 static void _gnc_dense_cal_set_year(GncDenseCal *dcal, guint year, gboolean redraw);
118 
119 
124 static inline int num_weeks(GncDenseCal *dcal);
129 static int num_weeks_per_col(GncDenseCal *dcal);
130 
131 /* hotspot calculation */
132 static gint wheres_this(GncDenseCal *dcal, int x, int y);
133 
134 static void recompute_x_y_scales(GncDenseCal *dcal);
135 static void recompute_mark_storage(GncDenseCal *dcal);
136 static void recompute_extents(GncDenseCal *dcal);
137 static void populate_hover_window(GncDenseCal *dcal, gint doc);
138 
139 static void month_coords(GncDenseCal *dcal, int monthOfCal, GList **outList);
140 static void doc_coords(GncDenseCal *dcal, int dayOfCal,
141  int *x1, int *y1, int *x2, int *y2);
142 
143 static void gdc_mark_add(GncDenseCal *dcal, guint tag, gchar *name, gchar *info, guint size, GDate **dateArray);
144 static void gdc_mark_remove(GncDenseCal *dcal, guint mark_to_remove, gboolean redraw);
145 
146 static void gdc_add_tag_markings(GncDenseCal *cal, guint tag);
147 static void gdc_add_markings(GncDenseCal *cal);
148 static void gdc_remove_markings(GncDenseCal *cal);
149 
150 static GObject *parent_class = NULL;
151 
152 #define MONTH_NAME_BUFSIZE 10
153 
154 /* Takes the number of months since January, in the range 0 to
155  * 11. Returns the abbreviated month name according to the current
156  * locale.*/
157 static const gchar*
158 month_name(int mon)
159 {
160  static gchar buf[MONTH_NAME_BUFSIZE];
161  GDate date;
162  gint arbitrary_year = 1977;
163 
164  memset(buf, 0, MONTH_NAME_BUFSIZE);
165  g_date_clear(&date, 1);
166 
167  g_date_set_year(&date, arbitrary_year);
168  g_date_set_day(&date, 1);
169  // g_date API is 1..12 (not 0..11)
170  g_date_set_month(&date, mon + 1);
171  g_date_strftime(buf, MONTH_NAME_BUFSIZE, "%b", &date);
172 
173  return buf;
174 }
175 
176 /* Takes the number of days since Sunday, in the range 0 to 6. Returns
177  * the abbreviated weekday name according to the current locale. */
178 static void
179 day_label(gchar *buf, int buf_len, int dow)
180 {
181  gnc_dow_abbrev(buf, buf_len, dow);
182  /* Use only the first two characters */
183  if (g_utf8_strlen(buf, -1) > 2)
184  {
185  gchar *pointer = g_utf8_offset_to_pointer(buf, 2);
186  *pointer = '\0';
187  }
188 }
189 
190 GType
191 gnc_dense_cal_get_type()
192 {
193  static GType dense_cal_type = 0;
194 
195  if (dense_cal_type == 0)
196  {
197  static const GTypeInfo dense_cal_info =
198  {
199  sizeof (GncDenseCalClass),
200  NULL,
201  NULL,
202  (GClassInitFunc) gnc_dense_cal_class_init,
203  NULL,
204  NULL,
205  sizeof (GncDenseCal),
206  0,
207  (GInstanceInitFunc) gnc_dense_cal_init,
208  NULL
209  };
210 
211  dense_cal_type = g_type_register_static(GTK_TYPE_VBOX,
212  "GncDenseCal",
213  &dense_cal_info, 0);
214  }
215 
216  return dense_cal_type;
217 }
218 
219 static void
220 gnc_dense_cal_class_init(GncDenseCalClass *klass)
221 {
222  GObjectClass *object_class;
223  GtkWidgetClass *widget_class;
224 
225  object_class = G_OBJECT_CLASS (klass);
226  widget_class = GTK_WIDGET_CLASS (klass);
227 
228  parent_class = g_type_class_peek_parent (klass);
229 
230  object_class->finalize = gnc_dense_cal_finalize;
231  object_class->dispose = gnc_dense_cal_dispose;
232 
233  widget_class->motion_notify_event = gnc_dense_cal_motion_notify;
234  widget_class->button_press_event = gnc_dense_cal_button_press;
235 }
236 
237 enum _GdcViewOptsColumns
238 {
239  VIEW_OPTS_COLUMN_LABEL = 0,
240  VIEW_OPTS_COLUMN_NUM_MONTHS
241 };
242 
243 static GtkListStore *_cal_view_options = NULL;
244 static GtkListStore*
245 _gdc_get_view_options(void)
246 {
247  if (_cal_view_options == NULL)
248  {
249  _cal_view_options = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
250  gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("12 months"), 1, 12, -1);
251  gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("6 months"), 1, 6, -1);
252  gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("4 months"), 1, 4, -1);
253  gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("3 months"), 1, 3, -1);
254  gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("2 months"), 1, 2, -1);
255  gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("1 month"), 1, 1, -1);
256  }
257 
258  return _cal_view_options;
259 }
260 
261 static void
262 gnc_dense_cal_init(GncDenseCal *dcal)
263 {
264  gboolean colorAllocSuccess[MAX_COLORS];
265 
266  gtk_widget_push_composite_child();
267 
268  {
269  GtkTreeModel *options;
270  GtkCellRenderer *text_rend;
271 
272  options = GTK_TREE_MODEL(_gdc_get_view_options());
273  dcal->view_options = GTK_COMBO_BOX(gtk_combo_box_new_with_model(options));
274  gtk_combo_box_set_active(GTK_COMBO_BOX(dcal->view_options), 0);
275  text_rend = GTK_CELL_RENDERER(gtk_cell_renderer_text_new());
276  gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dcal->view_options), text_rend, TRUE);
277  gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(dcal->view_options),
278  text_rend, "text", VIEW_OPTS_COLUMN_LABEL);
279  g_signal_connect(G_OBJECT(dcal->view_options), "changed", G_CALLBACK(_gdc_view_option_changed), (gpointer)dcal);
280  }
281 
282  {
283  GtkHBox *hbox = GTK_HBOX(gtk_hbox_new(FALSE, 0));
284  GtkAlignment *label_align;
285  GtkLabel *label;
286  float right_align = 1.0, mid_align = 0.5, fill_x = 0.0, fill_y = 1.0;
287 
288  label = GTK_LABEL(gtk_label_new(_("View:")));
289  label_align = GTK_ALIGNMENT(gtk_alignment_new(right_align, mid_align, fill_x, fill_y));
290  gtk_container_add(GTK_CONTAINER(label_align), GTK_WIDGET(label));
291  gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(label_align), TRUE, TRUE, 0);
292  gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(dcal->view_options), FALSE, FALSE, 0);
293 
294  gtk_box_pack_start(GTK_BOX(dcal), GTK_WIDGET(hbox), FALSE, FALSE, 0);
295  }
296  dcal->cal_drawing_area = GTK_DRAWING_AREA(gtk_drawing_area_new());
297  gtk_widget_add_events(GTK_WIDGET(dcal->cal_drawing_area), (GDK_EXPOSURE_MASK
298  | GDK_BUTTON_PRESS_MASK
299  | GDK_BUTTON_RELEASE_MASK
300  | GDK_POINTER_MOTION_MASK
301  | GDK_POINTER_MOTION_HINT_MASK));
302  gtk_box_pack_start(GTK_BOX(dcal), GTK_WIDGET(dcal->cal_drawing_area), TRUE, TRUE, 0);
303  g_signal_connect(G_OBJECT(dcal->cal_drawing_area), "expose_event", G_CALLBACK(gnc_dense_cal_expose), (gpointer)dcal);
304  g_signal_connect(G_OBJECT(dcal->cal_drawing_area), "realize", G_CALLBACK(gnc_dense_cal_realize), (gpointer)dcal);
305  g_signal_connect(G_OBJECT(dcal->cal_drawing_area), "configure_event", G_CALLBACK(gnc_dense_cal_configure), (gpointer)dcal);
306 
307  dcal->disposed = FALSE;
308  dcal->initialized = FALSE;
309  dcal->markData = NULL;
310  dcal->numMarks = 0;
311  dcal->marks = NULL;
312  dcal->lastMarkTag = 0;
313 
314  dcal->showPopup = FALSE;
315 
316  dcal->transPopup = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
317  {
318  GtkWidget *vbox, *hbox;
319  GtkWidget *l;
320  GtkListStore *tree_data;
321  GtkTreeView *tree_view;
322 
323  vbox = gtk_vbox_new(FALSE, 5);
324  hbox = gtk_hbox_new(FALSE, 5);
325 
326  l = gtk_label_new(_("Date: "));
327  gtk_container_add(GTK_CONTAINER(hbox), l);
328  l = gtk_label_new("YY/MM/DD");
329  g_object_set_data(G_OBJECT(dcal->transPopup), "dateLabel", l);
330  gtk_container_add(GTK_CONTAINER(hbox), l);
331  gtk_container_add(GTK_CONTAINER(vbox), hbox);
332 
333  gtk_container_add(GTK_CONTAINER(vbox), gtk_hseparator_new());
334 
335  tree_data = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
336  tree_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_data)));
337  gtk_tree_view_insert_column_with_attributes(tree_view, -1, _("Name"), gtk_cell_renderer_text_new(), "text", 0, NULL);
338  gtk_tree_view_insert_column_with_attributes(tree_view, -1, _("Frequency"), gtk_cell_renderer_text_new(), "text", 1, NULL);
339  g_object_set_data(G_OBJECT(dcal->transPopup), "model", tree_data);
340  gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(tree_view));
341 
342  gtk_container_add(GTK_CONTAINER(dcal->transPopup), vbox);
343 
344  gtk_window_set_resizable(GTK_WINDOW(dcal->transPopup), FALSE);
345 
346  gtk_widget_realize(GTK_WIDGET(dcal->transPopup));
347  }
348 
349  gdk_color_parse(MONTH_THIS_COLOR, &dcal->weekColors[MONTH_THIS]);
350  gdk_color_parse(MONTH_THAT_COLOR, &dcal->weekColors[MONTH_THAT]);
351 
352  /* success array must be as big as number of colors */
353  g_assert(MAX_COLORS == (sizeof(colorAllocSuccess)/sizeof(gboolean)));
354 
355  if (gdk_colormap_alloc_colors(gdk_colormap_get_system(),
356  dcal->weekColors,
357  MAX_COLORS, TRUE, TRUE,
358  colorAllocSuccess) > 0)
359  {
360  g_error("error allocating colors");
361  }
362 
363  /* Deal with the various label sizes. */
364  {
365  gint i;
366  gint maxWidth, maxHeight;
367  GtkStyle *style;
368  PangoLayout *layout;
369  PangoFontDescription *font_desc;
370  gint font_size;
371  gint font_size_reduction_units = 1;
372 
373  layout = gtk_widget_create_pango_layout(GTK_WIDGET(dcal), NULL);
374 
375  style = gtk_widget_get_style(GTK_WIDGET(dcal));
376 
377  font_desc = pango_font_description_copy(style->font_desc);
378  font_size = pango_font_description_get_size(font_desc);
379  font_size -= font_size_reduction_units * PANGO_SCALE;
380  pango_font_description_set_size(font_desc, font_size);
381  gtk_widget_modify_font(GTK_WIDGET(dcal), font_desc);
382  pango_font_description_free(font_desc);
383 
384  maxWidth = maxHeight = 0;
385  for (i = 0; i < 12; i++)
386  {
387  gint w, h;
388  pango_layout_set_text(layout, month_name(i), -1);
389  pango_layout_get_pixel_size(layout, &w, &h);
390  maxWidth = MAX(maxWidth, w);
391  maxHeight = MAX(maxHeight, h);
392  }
393 
394  // these two were reversed, before...
395  dcal->label_width = maxWidth;
396  dcal->label_height = maxHeight;
397 
398  g_object_unref(layout);
399  }
400 
401  dcal->month = G_DATE_JANUARY;
402  dcal->year = 1970;
403 
404  dcal->numMonths = 12;
405  dcal->monthsPerCol = 3;
406  dcal->leftPadding = 2;
407  dcal->topPadding = 2;
408 
409  {
410  GDate now;
411  g_date_clear (&now, 1);
412  gnc_gdate_set_today (&now);
413  _gnc_dense_cal_set_month(dcal, g_date_get_month(&now), FALSE);
414  _gnc_dense_cal_set_year(dcal, g_date_get_year(&now), FALSE);
415  }
416 
417  recompute_extents(dcal);
418  recompute_mark_storage(dcal);
419 
420  /* Compute initial scaling factors; will be increased when we're
421  * allocated enough space to scale up. */
422  {
423  PangoLayout *layout;
424  int width_88, height_88;
425  int width_XXX, height_XXX;
426 
427  layout = gtk_widget_create_pango_layout(GTK_WIDGET(dcal), NULL);
428 
429  pango_layout_set_text(layout, "88", -1);
430  pango_layout_get_pixel_size(layout, &width_88, &height_88);
431 
432  pango_layout_set_text(layout, "XXX", -1);
433  pango_layout_get_pixel_size(layout, &width_XXX, &height_XXX);
434 
435  dcal->min_x_scale = dcal->x_scale = width_88 + 2;
436  dcal->min_y_scale = dcal->y_scale = MAX(floor((float)width_XXX / 3.), height_88 + 2);
437 
438  dcal->dayLabelHeight = height_88;
439 
440  g_object_unref(layout);
441  }
442 
443  dcal->initialized = TRUE;
444 
445 
446  dcal->week_starts_monday = 0;
447  {
448  gchar **parts;
449  const char *week_start_str;
450 
451  /* Use this renaming macro to avoid extraction of the message
452  string into the gnucash.pot file when calling xgettext. */
453 #define dgettext_noextract dgettext
454  /* Translators: This string must not show up in gnucash.pot as
455  it is looked up in the "gtk20" translation domain
456  instead. */
457  week_start_str = dgettext_noextract("gtk20", "calendar:week_start:0");
458 #undef dgettext_noextract
459 
460  parts = g_strsplit(week_start_str, ":", 3);
461  if (parts[0] != NULL
462  && parts[1] != NULL
463  && parts[2] != NULL)
464  {
465  if (strcmp("1", parts[2]) == 0)
466  dcal->week_starts_monday = 1;
467  }
468  g_strfreev(parts);
469  }
470 
471  gtk_widget_pop_composite_child();
472 
473  gtk_widget_show_all(GTK_WIDGET(dcal));
474 }
475 
476 static void
477 _gdc_set_cal_min_size_req(GncDenseCal *dcal)
478 {
479  guint min_width, min_height;
480 
481  _gdc_compute_min_size(dcal, &min_width, &min_height);
482  gtk_widget_set_size_request(GTK_WIDGET(dcal->cal_drawing_area), min_width, min_height);
483 }
484 
485 GtkWidget*
486 gnc_dense_cal_new(void)
487 {
488  GncDenseCal *dcal;
489  dcal = g_object_new(GNC_TYPE_DENSE_CAL, NULL);
490  return GTK_WIDGET(dcal);
491 }
492 
493 GtkWidget*
494 gnc_dense_cal_new_with_model(GncDenseCalModel *model)
495 {
496  GncDenseCal *cal = GNC_DENSE_CAL(gnc_dense_cal_new());
497  gnc_dense_cal_set_model(cal, model);
498  return GTK_WIDGET(cal);
499 }
500 
501 static void
502 recompute_first_of_month_offset(GncDenseCal *dcal)
503 {
504  GDate *tmpDate;
505 
506  tmpDate = g_date_new_dmy(1, dcal->month, dcal->year);
507  dcal->firstOfMonthOffset = g_date_get_weekday(tmpDate) % 7;
508  g_date_free(tmpDate);
509 }
510 
511 void
512 gnc_dense_cal_set_month(GncDenseCal *dcal, GDateMonth mon)
513 {
514  _gnc_dense_cal_set_month(dcal, mon, TRUE);
515 }
516 
517 static void
518 _gnc_dense_cal_set_month(GncDenseCal *dcal, GDateMonth mon, gboolean redraw)
519 {
520  GTimer *t = g_timer_new();
521  if (dcal->month == mon)
522  return;
523  dcal->month = mon;
524  g_timer_start(t);
525  recompute_first_of_month_offset(dcal);
526  g_debug("recompute_first_of_month_offset: %f", g_timer_elapsed(t, NULL) * 1000.);
527  g_timer_start(t);
528  recompute_extents(dcal);
529  g_debug("recompute_extents: %f", g_timer_elapsed(t, NULL) * 1000.);
530  if (redraw && gtk_widget_get_realized(GTK_WIDGET(dcal)))
531  {
532  g_timer_start(t);
533  recompute_x_y_scales(dcal);
534  g_debug("recompute_x_y_scales: %f", g_timer_elapsed(t, NULL) * 1000.);
535  g_timer_start(t);
536  gnc_dense_cal_draw_to_buffer(dcal);
537  g_debug("draw_to_buffer: %f", g_timer_elapsed(t, NULL) * 1000.);
538  g_timer_start(t);
539  gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
540  g_debug("queue_draw: %f", g_timer_elapsed(t, NULL) * 1000.);
541  }
542  g_timer_stop(t);
543  g_timer_destroy(t);
544 }
545 
546 void
547 gnc_dense_cal_set_year(GncDenseCal *dcal, guint year)
548 {
549  _gnc_dense_cal_set_year(dcal, year, TRUE);
550 }
551 
552 static void
553 _gnc_dense_cal_set_year(GncDenseCal *dcal, guint year, gboolean redraw)
554 {
555  if (dcal->year == year)
556  return;
557  dcal->year = year;
558  recompute_first_of_month_offset(dcal);
559  recompute_extents(dcal);
560  if (redraw && gtk_widget_get_realized(GTK_WIDGET(dcal)))
561  {
562  recompute_x_y_scales(dcal);
563  gnc_dense_cal_draw_to_buffer(dcal);
564  gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
565  }
566 }
567 
568 void
569 gnc_dense_cal_set_num_months(GncDenseCal *dcal, guint num_months)
570 {
571  {
572  GtkListStore *options = _gdc_get_view_options();
573  GtkTreeIter view_opts_iter, iter_closest_to_req;
574  int closest_index_distance = G_MAXINT;
575 
576  // find closest list value to num_months
577  if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(options), &view_opts_iter))
578  {
579  g_critical("no view options?");
580  return;
581  }
582 
583  do
584  {
585  gint months_val, delta_months;
586 
587  gtk_tree_model_get(GTK_TREE_MODEL(options), &view_opts_iter, VIEW_OPTS_COLUMN_NUM_MONTHS, &months_val, -1);
588  delta_months = abs(months_val - (gint)num_months);
589  if (delta_months < closest_index_distance)
590  {
591  iter_closest_to_req = view_opts_iter;
592  closest_index_distance = delta_months;
593  }
594  }
595  while (closest_index_distance != 0
596  && (gtk_tree_model_iter_next(GTK_TREE_MODEL(options), &view_opts_iter)));
597 
598  // set iter on view
599  g_signal_handlers_block_by_func(dcal->view_options, _gdc_view_option_changed, dcal);
600  gtk_combo_box_set_active_iter(GTK_COMBO_BOX(dcal->view_options), &iter_closest_to_req);
601  g_signal_handlers_unblock_by_func(dcal->view_options, _gdc_view_option_changed, dcal);
602  }
603 
604  dcal->numMonths = num_months;
605  recompute_extents(dcal);
606  recompute_mark_storage(dcal);
607  if (gtk_widget_get_realized(GTK_WIDGET(dcal)))
608  {
609  recompute_x_y_scales(dcal);
610  gnc_dense_cal_draw_to_buffer(dcal);
611  gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
612  }
613 }
614 
615 guint
616 gnc_dense_cal_get_num_months(GncDenseCal *dcal)
617 {
618  return dcal->numMonths;
619 }
620 
621 void
622 gnc_dense_cal_set_months_per_col(GncDenseCal *dcal, guint monthsPerCol)
623 {
624  dcal->monthsPerCol = monthsPerCol;
625  recompute_x_y_scales(dcal);
626 }
627 
628 GDateMonth
629 gnc_dense_cal_get_month(GncDenseCal *dcal)
630 {
631  return dcal->month;
632 }
633 
634 GDateYear
635 gnc_dense_cal_get_year(GncDenseCal *dcal)
636 {
637  return dcal->year;
638 }
639 
640 static void
641 gnc_dense_cal_dispose (GObject *object)
642 {
643  GncDenseCal *dcal;
644  g_return_if_fail(object != NULL);
645  g_return_if_fail(GNC_IS_DENSE_CAL(object));
646 
647  dcal = GNC_DENSE_CAL(object);
648 
649  if (dcal->disposed)
650  return;
651  dcal->disposed = TRUE;
652 
653  if (gtk_widget_get_realized(GTK_WIDGET(dcal->transPopup)))
654  {
655  gtk_widget_hide(GTK_WIDGET(dcal->transPopup));
656  gtk_widget_destroy(GTK_WIDGET(dcal->transPopup));
657  dcal->transPopup = NULL;
658  }
659 
660  if (dcal->drawbuf)
661  {
662  g_object_unref(dcal->drawbuf);
663  dcal->drawbuf = NULL;
664  }
665 
666  /* FIXME: we have a bunch of cleanup to do, here. */
667 
668  gdc_free_all_mark_data(dcal);
669 
670  g_object_unref(G_OBJECT(dcal->model));
671 
672  if (G_OBJECT_CLASS (parent_class)->dispose)
673  G_OBJECT_CLASS(parent_class)->dispose(object);
674 }
675 
676 static void
677 gnc_dense_cal_finalize (GObject *object)
678 {
679  g_return_if_fail (object != NULL);
680  g_return_if_fail (GNC_IS_DENSE_CAL (object));
681 
682  if (G_OBJECT_CLASS (parent_class)->finalize)
683  G_OBJECT_CLASS(parent_class)->finalize(object);
684 }
685 
686 static void
687 gnc_dense_cal_configure(GtkWidget *widget,
688  GdkEventConfigure *event,
689  gpointer user_data)
690 {
691  GncDenseCal *dcal = GNC_DENSE_CAL(user_data);
692  recompute_x_y_scales(dcal);
693  gdc_reconfig(dcal);
694  gtk_widget_queue_draw_area(widget,
695  event->x, event->y,
696  event->width, event->height);
697 }
698 
699 static void
700 gnc_dense_cal_realize (GtkWidget *widget, gpointer user_data)
701 {
702  GncDenseCal *dcal;
703 
704  g_return_if_fail(widget != NULL);
705  g_return_if_fail(GNC_IS_DENSE_CAL (user_data));
706  dcal = GNC_DENSE_CAL(user_data);
707 
708  recompute_x_y_scales(dcal);
709  gdc_reconfig(dcal);
710 
711  gtk_style_set_background(widget->style, widget->window, GTK_STATE_ACTIVE);
712 }
713 
714 static void
715 gdc_reconfig(GncDenseCal *dcal)
716 {
717  if (dcal->drawbuf)
718  g_object_unref(dcal->drawbuf);
719  dcal->drawbuf = gdk_pixmap_new(GTK_WIDGET(dcal->cal_drawing_area)->window,
720  GTK_WIDGET(dcal->cal_drawing_area)->allocation.width,
721  GTK_WIDGET(dcal->cal_drawing_area)->allocation.height,
722  -1);
723  gnc_dense_cal_draw_to_buffer(dcal);
724 }
725 
726 static void
727 _gdc_compute_min_size(GncDenseCal *dcal, guint *min_width, guint *min_height)
728 {
729  if (min_width != NULL)
730  {
731  *min_width =
732  (dcal->leftPadding * 2)
733  + (num_cols(dcal) * (col_width_at(dcal, dcal->min_x_scale)
734  + dcal->label_width))
735  + ((num_cols(dcal) - 1) * COL_BORDER_SIZE);
736  }
737 
738  if (min_height != NULL)
739  {
740  *min_height =
741  (dcal->topPadding * 2)
742  + MINOR_BORDER_SIZE
743  + dcal->dayLabelHeight
744  + (num_weeks_per_col(dcal)
745  * week_height_at(dcal, dcal->min_y_scale));
746  }
747 }
748 
749 static void
750 recompute_x_y_scales(GncDenseCal *dcal)
751 {
752  int denom;
753  int width, height;
754 
755  width = DENSE_CAL_DEFAULT_WIDTH;
756  height = DENSE_CAL_DEFAULT_HEIGHT;
757  if (dcal->initialized)
758  {
759  width = GTK_WIDGET(dcal->cal_drawing_area)->allocation.width;
760  height = GTK_WIDGET(dcal->cal_drawing_area)->allocation.height;
761  }
762 
763  /* FIXME: there's something slightly wrong in the x_scale computation that
764  * lets us draw larger than our area. */
765  denom = 7 * num_cols(dcal);
766  g_assert(denom != 0);
767  dcal->x_scale = ((gint)(width
768  - (dcal->leftPadding * 2)
769  - (num_cols(dcal) * ((8 * MINOR_BORDER_SIZE)
770  + dcal->label_width))
771  - ((num_cols(dcal) - 1) * COL_BORDER_SIZE))
772  / denom);
773  dcal->x_scale = MAX(dcal->x_scale, dcal->min_x_scale);
774 
775  denom = num_weeks_per_col(dcal);
776  g_assert(denom != 0);
777  dcal->y_scale = ((gint)(height
778  - (dcal->topPadding * 2)
779  - MINOR_BORDER_SIZE
780  - dcal->dayLabelHeight
781  - (num_weeks_per_col(dcal) - 1
782  * MINOR_BORDER_SIZE))
783  / denom);
784  dcal->y_scale = MAX(dcal->y_scale, dcal->min_y_scale);
785 
786  _gdc_set_cal_min_size_req(dcal);
787 }
788 
789 static void
790 gdc_free_all_mark_data(GncDenseCal *dcal)
791 {
792  int i;
793  GList *l;
794  for (i = 0; i < dcal->numMarks; i++)
795  {
796  /* Each of these just contains an elt of dcal->markData,
797  * which we're about to free, below... */
798  g_list_free(dcal->marks[i]);
799  }
800  g_free(dcal->marks);
801  dcal->marks = NULL;
802  /* Remove the old mark data. */
803  for (l = dcal->markData; l; l = l->next)
804  {
805  g_list_free(((gdc_mark_data*)l->data)->ourMarks);
806  g_free((gdc_mark_data*)l->data);
807  }
808  g_list_free(dcal->markData);
809  dcal->markData = NULL;
810 }
811 
812 static void
813 recompute_mark_storage(GncDenseCal *dcal)
814 {
815  if (dcal->marks == NULL)
816  goto createNew;
817  gdc_free_all_mark_data(dcal);
818 
819 createNew:
820  dcal->numMarks = num_weeks(dcal) * 7;
821  dcal->marks = g_new0(GList*, dcal->numMarks);
822  if (dcal->model)
823  gdc_add_markings(dcal);
824 }
825 
826 static void
827 recompute_extents(GncDenseCal *dcal)
828 {
829  GDate date;
830  gint start_week, end_week;
831 
832  g_date_clear(&date, 1);
833  g_date_set_dmy(&date, 1, dcal->month, dcal->year);
834  start_week = (dcal->week_starts_monday
835  ? g_date_get_monday_week_of_year(&date)
836  : g_date_get_sunday_week_of_year(&date));
837  g_date_add_months(&date, dcal->numMonths);
838  end_week = (dcal->week_starts_monday
839  ? g_date_get_monday_week_of_year(&date)
840  : g_date_get_sunday_week_of_year(&date));
841  if (g_date_get_year(&date) != dcal->year)
842  {
843  end_week += (dcal->week_starts_monday
844  ? g_date_get_monday_weeks_in_year(dcal->year)
845  : g_date_get_sunday_weeks_in_year(dcal->year));
846  }
847  dcal->num_weeks = end_week - start_week + 1;
848 }
849 
850 static void
851 free_rect(gpointer data, gpointer ud)
852 {
853  g_free((GdkRectangle*)data);
854 }
855 
856 static gboolean
857 gnc_dense_cal_expose(GtkWidget *widget,
858  GdkEventExpose *event,
859  gpointer user_data)
860 {
861  GncDenseCal *dcal;
862  GdkGC *gc;
863 
864  g_return_val_if_fail(widget != NULL, FALSE);
865  g_return_val_if_fail(GNC_IS_DENSE_CAL(user_data), FALSE);
866  g_return_val_if_fail(event != NULL, FALSE);
867 
868  if (event->count > 0)
869  return FALSE;
870 
871  dcal = GNC_DENSE_CAL(user_data);
872  gc = widget->style->fg_gc[gtk_widget_get_state(widget)];
873  gdk_draw_drawable(GDK_DRAWABLE(GTK_WIDGET(dcal->cal_drawing_area)->window),
874  gc, GDK_DRAWABLE(dcal->drawbuf),
875  0, 0, 0, 0, -1, -1);
876 
877  return TRUE;
878 }
879 
880 #define LOG_AND_RESET(timer, msg) do { g_debug("%s: %f", msg, g_timer_elapsed(timer, NULL) * 1000.); g_timer_reset(timer); } while (0);
881 
882 static void
883 gnc_dense_cal_draw_to_buffer(GncDenseCal *dcal)
884 {
885  GtkWidget *widget;
886  gint i;
887  int maxWidth;
888  PangoLayout *layout;
889  GTimer *timer;
890 
891  timer = g_timer_new();
892  g_debug("drawing");
893  widget = GTK_WIDGET(dcal);
894 
895  if (!dcal->drawbuf)
896  return;
897 
898  g_timer_start(timer);
899  layout = gtk_widget_create_pango_layout(GTK_WIDGET(dcal), NULL);
900  LOG_AND_RESET(timer, "create_pango_layout");
901 
902  gdk_draw_rectangle(dcal->drawbuf,
903  widget->style->white_gc,
904  TRUE,
905  0, 0,
906  GTK_WIDGET(dcal->cal_drawing_area)->allocation.width,
907  GTK_WIDGET(dcal->cal_drawing_area)->allocation.height);
908 
909  /* Fill in alternating month colors. */
910  {
911  gint i;
912  GdkGC *gc;
913  GdkRectangle *rect;
914  GList *mcList, *mcListIter;
915 
916  gc = gdk_gc_new(GTK_WIDGET(dcal)->window);
917  gdk_gc_copy(gc, widget->style->fg_gc[gtk_widget_get_state(widget)]);
918 
919  /* reset all of the month position offsets. */
920  for (i = 0; i < 12; i++)
921  {
922  dcal->monthPositions[i].x = dcal->monthPositions[i].y = -1;
923  }
924 
925  /* Paint the weeks for the upcoming N months. */
926  for (i = 0; i < dcal->numMonths; i++)
927  {
928  gdk_gc_set_foreground(gc, &dcal->weekColors[ i % 2 ]);
929 
930  mcList = NULL;
931  month_coords(dcal, i, &mcList);
932  dcal->monthPositions[i].x
933  = floor(i / dcal->monthsPerCol)
934  * (col_width(dcal) + COL_BORDER_SIZE);
935  dcal->monthPositions[i].y = ((GdkRectangle*)mcList->next->next->data)->y;
936  for (mcListIter = mcList; mcListIter != NULL; mcListIter = mcListIter->next)
937  {
938  rect = (GdkRectangle*)mcListIter->data;
939  gdk_draw_rectangle(dcal->drawbuf, gc,
940  TRUE, rect->x, rect->y,
941  rect->width, rect->height);
942  }
943  g_list_foreach(mcList, free_rect, NULL);
944  g_list_free(mcList);
945  }
946 
947  g_object_unref(gc);
948  }
949  LOG_AND_RESET(timer, "alternating month colors");
950 
951  /* Hilight the marked days. */
952  {
953  int i;
954  int x1, x2, y1, y2;
955  GdkColor markColor;
956  GdkGCValues current_values;
957 
958  gdk_gc_get_values(widget->style->fg_gc[widget->state], &current_values);
959 
960  gdk_color_parse(MARK_COLOR, &markColor);
961  gdk_colormap_alloc_color(gdk_colormap_get_system(), &markColor, TRUE, TRUE);
962 
963  gdk_gc_set_foreground(widget->style->fg_gc[widget->state], &markColor);
964  for (i = 0; i < dcal->numMarks; i++)
965  {
966  if (dcal->marks[i] != NULL)
967  {
968  int w, h, x_offset, y_offset, circle_delta;
969 
970  doc_coords(dcal, i, &x1, &y1, &x2, &y2);
971  w = x2 - x1;
972  h = y2 - y1;
973 
974  x_offset = x1;
975  y_offset = y1;
976 
977  circle_delta = ABS(w - h) / 2;
978  if (w < h)
979  {
980  y_offset += circle_delta;
981  }
982  else
983  {
984  x_offset += circle_delta;
985  }
986 
987  gdk_draw_arc(dcal->drawbuf,
988  widget->style->fg_gc[widget->state],
989  TRUE,
990  x_offset, y_offset, MIN(w, h), MIN(w, h),
991  0 * 64,
992  360 * 64);
993  }
994  }
995 
996  // reset to the previous foreground color.
997  gdk_gc_set_foreground(widget->style->fg_gc[widget->state], &current_values.foreground);
998  }
999  LOG_AND_RESET(timer, "marked days");
1000 
1001  for (i = 0; i < num_cols(dcal); i++)
1002  {
1003  gint x, y, w, h;
1004  gint j;
1005 
1006  pango_layout_set_text(layout, "S", -1);
1007  pango_layout_get_pixel_size(layout, NULL, &dcal->dayLabelHeight);
1008 
1009  x = dcal->leftPadding
1010  + (i * (col_width(dcal) + COL_BORDER_SIZE))
1011  + dcal->label_width;
1012  y = dcal->topPadding + dcal->dayLabelHeight;
1013  w = col_width(dcal) - COL_BORDER_SIZE - dcal->label_width;
1014  h = col_height(dcal);
1015 
1016  /* draw the outside border [inside the month labels] */
1017  gdk_draw_rectangle(dcal->drawbuf,
1018  widget->style->fg_gc[widget->state],
1019  FALSE, x, y, w, h);
1020  /* draw the week seperations */
1021  for (j = 0; j < num_weeks_per_col(dcal); j++)
1022  {
1023  gint wy = y + (j * week_height(dcal));
1024  gdk_draw_line(dcal->drawbuf,
1025  widget->style->fg_gc[widget->state],
1026  x, wy,
1027  x + w, wy);
1028  }
1029 
1030  /* draw the day seperations */
1031  for (j = 1; j < 7; j++)
1032  {
1033  gint dx = x + (j * day_width(dcal));
1034  gdk_draw_line(dcal->drawbuf,
1035  widget->style->fg_gc[widget->state],
1036  dx, y,
1037  dx, y + col_height(dcal));
1038  }
1039 
1040  /* draw the day labels */
1041  pango_layout_set_text(layout, "88", -1);
1042  pango_layout_get_pixel_size(layout, &maxWidth, NULL);
1043 
1044  if (dcal->x_scale > maxWidth)
1045  {
1046  for (j = 0; j < 7; j++)
1047  {
1048  int day_label_width;
1049  gint label_x_offset, label_y_offset;
1050  gint day_label_str_len = 3;
1051  gchar day_label_str[day_label_str_len+1];
1052 
1053  day_label(day_label_str, day_label_str_len, (j + dcal->week_starts_monday) % 7);
1054  pango_layout_set_text(layout, day_label_str, -1);
1055  pango_layout_get_pixel_size(layout, &day_label_width, NULL);
1056  label_x_offset = x
1057  + (j * day_width(dcal))
1058  + (day_width(dcal) / 2)
1059  - (day_label_width / 2);
1060  label_y_offset = y - dcal->dayLabelHeight;
1061  pango_layout_set_text(layout, day_label_str, -1);
1062  gdk_draw_layout(GDK_DRAWABLE(dcal->drawbuf), widget->style->fg_gc[widget->state],
1063  label_x_offset, label_y_offset,
1064  layout);
1065 
1066  }
1067  }
1068  }
1069  LOG_AND_RESET(timer, "lines and labels");
1070 
1071  /* Month labels. */
1072  {
1073  gint i;
1074  PangoMatrix matrix = PANGO_MATRIX_INIT;
1075 
1076  pango_matrix_rotate(&matrix, 90.);
1077  pango_context_set_matrix(gtk_widget_get_pango_context(GTK_WIDGET(dcal)), &matrix);
1078 
1079  for (i = 0; i < 12; i++)
1080  {
1081  guint idx;
1082 
1083  if (dcal->monthPositions[i].x == -1)
1084  break;
1085  idx = (dcal->month - 1 + i) % 12;
1086  pango_layout_set_text(layout, month_name(idx), -1);
1087  gdk_draw_layout(GDK_DRAWABLE(dcal->drawbuf),
1088  widget->style->fg_gc[widget->state],
1089  dcal->leftPadding + dcal->monthPositions[i].x,
1090  dcal->monthPositions[i].y,
1091  layout);
1092  }
1093 
1094  // reset rotation
1095  pango_matrix_rotate(&matrix, -90.);
1096  pango_context_set_matrix(gtk_widget_get_pango_context(GTK_WIDGET(dcal)), &matrix);
1097  }
1098  LOG_AND_RESET(timer, "month labels");
1099 
1100  /* Try the per-day strings [dates] */
1101  {
1102  GDate d, eoc;
1103  gint doc;
1104  gchar dayNumBuf[3];
1105  gint numW, numH;
1106  gint x1, y1, x2, y2, w, h;
1107 
1108  g_date_set_dmy(&d, 1, dcal->month, dcal->year);
1109  eoc = d;
1110  g_date_add_months(&eoc, dcal->numMonths);
1111  for (doc = 0; g_date_get_julian(&d) < g_date_get_julian(&eoc); g_date_add_days(&d, 1), doc++)
1112  {
1113  doc_coords(dcal, doc, &x1, &y1, &x2, &y2);
1114  memset(dayNumBuf, 0, 3);
1115  snprintf(dayNumBuf, 3, "%d", g_date_get_day(&d));
1116  pango_layout_set_text(layout, dayNumBuf, -1);
1117  pango_layout_get_pixel_size(layout, &numW, &numH);
1118  w = (x2 - x1) + 1;
1119  h = (y2 - y1) + 1;
1120  gdk_draw_layout(GDK_DRAWABLE(dcal->drawbuf),
1121  widget->style->fg_gc[widget->state],
1122  x1 + (w / 2) - (numW / 2),
1123  y1 + (h / 2) - (numH / 2),
1124  layout);
1125  }
1126  }
1127  LOG_AND_RESET(timer, "dates");
1128 
1129  gtk_widget_queue_draw_area(GTK_WIDGET(dcal),
1130  widget->allocation.x,
1131  widget->allocation.y,
1132  widget->allocation.width,
1133  widget->allocation.height);
1134 
1135  LOG_AND_RESET(timer, "queue draw");
1136 
1137  g_object_unref(layout);
1138 
1139  g_timer_destroy(timer);
1140 }
1141 
1142 static void
1143 populate_hover_window(GncDenseCal *dcal, gint doc)
1144 {
1145  GtkWidget *w;
1146  GDate *date;
1147  static const int MAX_STRFTIME_BUF_LEN = 64;
1148  gchar strftimeBuf[MAX_STRFTIME_BUF_LEN];
1149 
1150  if (doc >= 0)
1151  {
1152  GObject *o;
1153  GtkListStore *model;
1154  GList *l;
1155 
1156  w = GTK_WIDGET(g_object_get_data(G_OBJECT(dcal->transPopup), "dateLabel"));
1157  date = g_date_new_dmy(1, dcal->month, dcal->year);
1158  g_date_add_days(date, doc);
1159  /* Note: the ISO date format (%F or equivalently
1160  * %Y-%m-%d) is not a good idea here since many
1161  * locales will want to use a very different date
1162  * format. Please leave the specification of the date
1163  * format up to the locale and use %x here. */
1164  g_date_strftime(strftimeBuf, MAX_STRFTIME_BUF_LEN - 1, "%x", date);
1165  gtk_label_set_text(GTK_LABEL(w), strftimeBuf);
1166 
1167  o = G_OBJECT(dcal->transPopup);
1168  model = GTK_LIST_STORE(g_object_get_data(o, "model"));
1169  gtk_list_store_clear(model);
1170  for (l = dcal->marks[doc]; l; l = l->next)
1171  {
1172  GtkTreeIter iter;
1173  gdc_mark_data *gdcmd;
1174 
1175  gdcmd = (gdc_mark_data*)l->data;
1176  gtk_list_store_insert(model, &iter, INT_MAX);
1177  gtk_list_store_set(model, &iter, 0, (gdcmd->name ? gdcmd->name : _("(unnamed)")), 1, gdcmd->info, -1);
1178  }
1179 
1180  g_date_free(date);
1181  }
1182 }
1183 
1184 static gint
1185 gnc_dense_cal_button_press(GtkWidget *widget,
1186  GdkEventButton *evt)
1187 {
1188  gint doc;
1189  GncDenseCal *dcal = GNC_DENSE_CAL(widget);
1190 
1191  doc = wheres_this(dcal, evt->x, evt->y);
1192  dcal->showPopup = ~(dcal->showPopup);
1193  if (dcal->showPopup && doc >= 0)
1194  {
1195  // Do the move twice in case the WM is ignoring the first one
1196  // because the window hasn't been shown, yet. The WM is free
1197  // to ignore our move and place windows according to it's own
1198  // strategy, but hopefully it'll listen to us. Certainly the
1199  // second move after show_all'ing the window should do the
1200  // trick with a bit of flicker.
1201  gtk_window_move(GTK_WINDOW(dcal->transPopup), evt->x_root + 5, evt->y_root + 5);
1202  populate_hover_window(dcal, doc);
1203  gtk_widget_queue_resize(GTK_WIDGET(dcal->transPopup));
1204  gtk_widget_show_all(GTK_WIDGET(dcal->transPopup));
1205  gtk_window_move(GTK_WINDOW(dcal->transPopup), evt->x_root + 5, evt->y_root + 5);
1206  }
1207  else
1208  {
1209  gtk_widget_hide(GTK_WIDGET(dcal->transPopup));
1210  }
1211  return FALSE;
1212 }
1213 
1214 static gint
1215 gnc_dense_cal_motion_notify(GtkWidget *widget,
1216  GdkEventMotion *event)
1217 {
1218  GncDenseCal *dcal;
1219  gint doc;
1220  int unused;
1221  int x_root_offset, y_root_offset;
1222  GdkModifierType unused2;
1223 
1224  dcal = GNC_DENSE_CAL(widget);
1225  if (!dcal->showPopup)
1226  return FALSE;
1227 
1228  x_root_offset = event->x_root;
1229  y_root_offset = event->y_root;
1230 
1231  /* As per http://www.gtk.org/tutorial/sec-eventhandling.html */
1232  if (event->is_hint)
1233  gdk_window_get_pointer(event->window, &unused, &unused, &unused2);
1234  gdk_window_move(GTK_WIDGET(dcal->transPopup)->window,
1235  x_root_offset + 5, y_root_offset + 5);
1236  doc = wheres_this(dcal, event->x, event->y);
1237  if (doc >= 0)
1238  {
1239  populate_hover_window(dcal, doc);
1240  gtk_widget_queue_resize(GTK_WIDGET(dcal->transPopup));
1241  gtk_widget_show_all(GTK_WIDGET(dcal->transPopup));
1242  }
1243  else
1244  {
1245  gtk_widget_hide(GTK_WIDGET(dcal->transPopup));
1246  }
1247  return TRUE;
1248 }
1249 
1250 
1251 static void
1252 _gdc_view_option_changed(GtkComboBox *widget, gpointer user_data)
1253 {
1254  GtkTreeIter iter;
1255  GtkTreeModel *model;
1256  gint months_val;
1257 
1258  model = GTK_TREE_MODEL(gtk_combo_box_get_model(widget));
1259  if (!gtk_combo_box_get_active_iter(widget, &iter))
1260  return;
1261  gtk_tree_model_get(model, &iter, VIEW_OPTS_COLUMN_NUM_MONTHS, &months_val, -1);
1262  g_debug("changing to %d months", months_val);
1263  gnc_dense_cal_set_num_months(GNC_DENSE_CAL(user_data), months_val);
1264 }
1265 
1266 static inline int
1267 day_width_at(GncDenseCal *dcal, guint xScale)
1268 {
1269  return xScale + MINOR_BORDER_SIZE;
1270 }
1271 
1272 static inline int
1273 day_width(GncDenseCal *dcal)
1274 {
1275  return day_width_at(dcal, dcal->x_scale);
1276 }
1277 
1278 static inline int
1279 day_height_at(GncDenseCal *dcal, guint yScale)
1280 {
1281  return yScale + MINOR_BORDER_SIZE;
1282 }
1283 
1284 static inline int
1285 day_height(GncDenseCal *dcal)
1286 {
1287  return day_height_at(dcal, dcal->y_scale);
1288 }
1289 
1290 static inline int
1291 week_width_at(GncDenseCal *dcal, guint xScale)
1292 {
1293  return day_width_at(dcal, xScale) * 7;
1294 }
1295 
1296 static inline int
1297 week_width(GncDenseCal *dcal)
1298 {
1299  return week_width_at(dcal, dcal->x_scale);
1300 }
1301 
1302 static inline int
1303 week_height_at(GncDenseCal *dcal, guint yScale)
1304 {
1305  return day_height_at(dcal, yScale);
1306 }
1307 
1308 static inline int
1309 week_height(GncDenseCal *dcal)
1310 {
1311  return week_height_at(dcal, dcal->y_scale);
1312 }
1313 
1314 static inline int
1315 col_width_at(GncDenseCal *dcal, guint xScale)
1316 {
1317  return (week_width_at(dcal, xScale)
1318  + dcal->label_width
1319  + COL_BORDER_SIZE);
1320 }
1321 
1322 static inline int
1323 col_width(GncDenseCal *dcal)
1324 {
1325  return col_width_at(dcal, dcal->x_scale);
1326 }
1327 
1328 static inline int
1329 col_height(GncDenseCal *dcal)
1330 {
1331  return week_height(dcal) * num_weeks_per_col(dcal);
1332 }
1333 
1334 static inline int
1335 num_cols(GncDenseCal *dcal)
1336 {
1337  return ceil((float)dcal->numMonths / (float)dcal->monthsPerCol);
1338 }
1339 
1340 static inline int
1341 num_weeks(GncDenseCal *dcal)
1342 {
1343  return dcal->num_weeks;
1344 }
1345 
1346 static
1347 int num_weeks_per_col(GncDenseCal *dcal)
1348 {
1349  int num_weeks_toRet, numCols, i;
1350  GDate *start, *end;
1351  int startWeek, endWeek;
1352 
1353  start = g_date_new();
1354  end = g_date_new();
1355 
1356  num_weeks_toRet = 0;
1357  numCols = num_cols(dcal);
1358 
1359  for (i = 0; i < numCols; i++)
1360  {
1361  g_date_set_dmy(start, 1,
1362  ((dcal->month - 1
1363  + (i * dcal->monthsPerCol)) % 12)
1364  + 1,
1365  dcal->year + floor((dcal->month - 1
1366  + (i * dcal->monthsPerCol))
1367  / 12));
1368  *end = *start;
1369  /* Add the smaller of (the number of months in the
1370  * calendar-display, minus the number of months shown in the
1371  * previous columns) or (the number of months in a column) */
1372  g_date_add_months(end, MIN(dcal->numMonths,
1373  MIN(dcal->monthsPerCol,
1374  dcal->numMonths
1375  - ((i - 1)
1376  * dcal->monthsPerCol))));
1377  g_date_subtract_days(end, 1);
1378  startWeek = (dcal->week_starts_monday
1379  ? g_date_get_monday_week_of_year(start)
1380  : g_date_get_sunday_week_of_year(start));
1381  endWeek = (dcal->week_starts_monday
1382  ? g_date_get_monday_week_of_year(end)
1383  : g_date_get_sunday_week_of_year(end));
1384  if (endWeek < startWeek)
1385  {
1386  endWeek += (dcal->week_starts_monday
1387  ? g_date_get_monday_weeks_in_year(g_date_get_year(start))
1388  : g_date_get_sunday_weeks_in_year(g_date_get_year(start)));
1389  }
1390  num_weeks_toRet = MAX(num_weeks_toRet, (endWeek - startWeek) + 1);
1391  }
1392  g_date_free(start);
1393  g_date_free(end);
1394  return num_weeks_toRet;
1395 }
1396 
1403 static void
1404 month_coords(GncDenseCal *dcal, int monthOfCal, GList **outList)
1405 {
1406  gint weekRow, colNum, previousMonthsInCol, monthOffset;
1407  gint start;
1408  GDate *startD, *endD;
1409  GdkRectangle *rect;
1410  gint startWk, endWk;
1411 
1412  if (monthOfCal > dcal->numMonths)
1413  return;
1414 
1415  colNum = floor(monthOfCal / dcal->monthsPerCol);
1416  monthOffset = colNum * dcal->monthsPerCol;
1417  previousMonthsInCol = MAX(0, (monthOfCal % dcal->monthsPerCol));
1418 
1419  startD = g_date_new();
1420  endD = g_date_new();
1421 
1422  /* Calculate the number of weeks in the column before the month we're
1423  * interested in. */
1424  weekRow = 0;
1425  if (previousMonthsInCol > 0)
1426  {
1427  g_date_set_dmy(startD, 1,
1428  ((dcal->month - 1 + monthOffset) % 12) + 1,
1429  dcal->year + floor((dcal->month - 1 + monthOffset) / 12));
1430  /* get the week of the top of the column */
1431  startWk = (dcal->week_starts_monday
1432  ? g_date_get_monday_week_of_year(startD)
1433  : g_date_get_sunday_week_of_year(startD));
1434  /* get the week of the end of the previous months */
1435  *endD = *startD;
1436  g_date_add_months(endD, previousMonthsInCol);
1437  g_date_subtract_days(endD, 1);
1438  endWk = (dcal->week_starts_monday
1439  ? g_date_get_monday_week_of_year(endD)
1440  : g_date_get_sunday_week_of_year(endD));
1441  if (endWk < startWk)
1442  {
1443  endWk += (dcal->week_starts_monday
1444  ? g_date_get_monday_weeks_in_year(g_date_get_year(startD))
1445  : g_date_get_sunday_weeks_in_year(g_date_get_year(startD)));
1446  }
1447  /* determine how many weeks are before the month we're
1448  * interested in. */
1449  weekRow = endWk - startWk;
1450  if (g_date_get_weekday(endD) == (dcal->week_starts_monday ? G_DATE_SUNDAY : G_DATE_SATURDAY))
1451  {
1452  weekRow++;
1453  }
1454  }
1455 
1456  g_date_set_dmy(startD, 1,
1457  ((dcal->month - 1 + monthOfCal) % 12) + 1,
1458  dcal->year + floor((dcal->month - 1 + monthOfCal) / 12));
1459  *endD = *startD;
1460  g_date_add_months(endD, 1);
1461  g_date_subtract_days(endD, 1);
1462  /* Get the first week. */
1463  {
1464  start = (g_date_get_weekday(startD) - dcal->week_starts_monday) % 7;
1465  rect = g_new0(GdkRectangle, 1);
1466  rect->x = dcal->leftPadding
1467  + MINOR_BORDER_SIZE
1468  + (colNum * (col_width(dcal) + COL_BORDER_SIZE))
1469  + dcal->label_width
1470  + (start * day_width(dcal));
1471  rect->y = dcal->topPadding
1472  + dcal->dayLabelHeight
1473  + MINOR_BORDER_SIZE
1474  + (weekRow * week_height(dcal));
1475  rect->width = (7 - start) * day_width(dcal);
1476  rect->height = week_height(dcal);
1477  *outList = g_list_append(*outList, (gpointer)rect);
1478  rect = NULL;
1479  }
1480 
1481  /* Get the middle weeks. */
1482  {
1483  gint i, weekStart, weekEnd;
1484 
1485  weekStart = (dcal->week_starts_monday
1486  ? g_date_get_monday_week_of_year(startD)
1487  : g_date_get_sunday_week_of_year(startD)) + 1;
1488  weekEnd = (dcal->week_starts_monday
1489  ? g_date_get_monday_week_of_year(endD)
1490  : g_date_get_sunday_week_of_year(endD));
1491  for (i = weekStart; i < weekEnd; i++)
1492  {
1493  rect = g_new0(GdkRectangle, 1);
1494  rect->x = dcal->leftPadding
1495  + MINOR_BORDER_SIZE
1496  + dcal->label_width
1497  + (colNum * (col_width(dcal) + COL_BORDER_SIZE));
1498  rect->y = dcal->topPadding
1499  + dcal->dayLabelHeight
1500  + MINOR_BORDER_SIZE
1501  + ((weekRow + (i - weekStart) + 1) * week_height(dcal));
1502  rect->width = week_width(dcal);
1503  rect->height = week_height(dcal);
1504 
1505  *outList = g_list_append(*outList, (gpointer)rect);
1506  rect = NULL;
1507  }
1508  }
1509 
1510  /* Get the last week. */
1511  {
1512  gint end_week_of_year = g_date_get_sunday_week_of_year(endD);
1513  gint start_week_of_year = g_date_get_sunday_week_of_year(startD);
1514  if (dcal->week_starts_monday == 1)
1515  {
1516  end_week_of_year = g_date_get_monday_week_of_year(endD);
1517  start_week_of_year = g_date_get_monday_week_of_year(startD);
1518  }
1519 
1520  rect = g_new0(GdkRectangle, 1);
1521  rect->x = dcal->leftPadding
1522  + MINOR_BORDER_SIZE
1523  + dcal->label_width
1524  + (colNum * (col_width(dcal) + COL_BORDER_SIZE));
1525  rect->y = dcal->topPadding
1526  + MINOR_BORDER_SIZE
1527  + dcal->dayLabelHeight
1528  + ((weekRow
1529  + (end_week_of_year - start_week_of_year))
1530  * week_height(dcal));
1531  rect->width = (((g_date_get_weekday(endD) - dcal->week_starts_monday) % 7) + 1) * day_width(dcal);
1532  rect->height = week_height(dcal);
1533 
1534  *outList = g_list_append(*outList, (gpointer)rect);
1535  rect = NULL;
1536  }
1537 
1538  g_date_free(startD);
1539  g_date_free(endD);
1540 }
1541 
1542 /* FIXME: make this more like month_coords */
1543 static void
1544 doc_coords(GncDenseCal *dcal, int dayOfCal,
1545  int *x1, int *y1, int *x2, int *y2)
1546 {
1547  GDate d;
1548  gint docMonth;
1549  gint d_week_of_cal, top_of_col_week_of_cal;
1550  gint colNum, dayCol, weekRow;
1551 
1552  /* FIXME: add range checks */
1553  g_date_set_dmy(&d, 1, dcal->month, dcal->year);
1554  g_date_add_days(&d, dayOfCal);
1555  docMonth = g_date_get_month(&d);
1556  if (g_date_get_year(&d) != dcal->year)
1557  {
1558  docMonth += 12;
1559  }
1560  colNum = floor((float)(docMonth - dcal->month) / (float)dcal->monthsPerCol);
1561  dayCol = (g_date_get_weekday(&d) - dcal->week_starts_monday) % 7;
1562  d_week_of_cal = g_date_get_sunday_week_of_year(&d);
1563  if (dcal->week_starts_monday == 1)
1564  {
1565  d_week_of_cal = g_date_get_monday_week_of_year(&d);
1566  }
1567  g_date_set_dmy(&d, 1, dcal->month, dcal->year);
1568  g_date_add_months(&d, (colNum * dcal->monthsPerCol));
1569  top_of_col_week_of_cal = (dcal->week_starts_monday
1570  ? g_date_get_monday_week_of_year(&d)
1571  : g_date_get_sunday_week_of_year(&d));
1572  if (d_week_of_cal < top_of_col_week_of_cal)
1573  {
1574  gint week_offset;
1575  week_offset = g_date_get_sunday_weeks_in_year(dcal->year);
1576  if (dcal->week_starts_monday == 1)
1577  {
1578  week_offset = g_date_get_monday_weeks_in_year(dcal->year);
1579  }
1580  d_week_of_cal += week_offset;
1581  }
1582  weekRow = d_week_of_cal - top_of_col_week_of_cal;
1583 
1584  /* top-left corner */
1585  /* FIXME: this has the math to make the mark-cells come out right,
1586  * which it shouldn't. */
1587  *x1 = dcal->leftPadding
1588  + MINOR_BORDER_SIZE
1589  + dcal->label_width
1590  + (colNum * (col_width(dcal) + COL_BORDER_SIZE))
1591  + (dayCol * day_width(dcal))
1592  + (day_width(dcal) / 4);
1593  *y1 = dcal->topPadding
1594  + MINOR_BORDER_SIZE
1595  + dcal->dayLabelHeight
1596  + (weekRow * week_height(dcal))
1597  + (day_height(dcal) / 4);
1598 
1599  *x2 = *x1 + (day_width(dcal) / 2);
1600  *y2 = *y1 + (day_height(dcal) / 2);
1601 }
1602 
1607 static gint
1608 wheres_this(GncDenseCal *dcal, int x, int y)
1609 {
1610  gint colNum, weekRow, dayCol, dayOfCal;
1611  GDate d, startD;
1612 
1613  x -= dcal->leftPadding;
1614  y -= dcal->topPadding;
1615 
1616  if ((x < 0) || (y < 0))
1617  {
1618  return -1;
1619  }
1620  if ((x >= GTK_WIDGET(dcal)->allocation.width)
1621  || (y >= GTK_WIDGET(dcal)->allocation.height))
1622  {
1623  return -1;
1624  }
1625 
1626  /* "outside of displayed table" check */
1627  if (x >= (num_cols(dcal) * (col_width(dcal) + COL_BORDER_SIZE)))
1628  {
1629  return -1;
1630  }
1631  if (y >= dcal->dayLabelHeight + col_height(dcal))
1632  {
1633  return -1;
1634  }
1635 
1636  /* coords -> year-relative-values */
1637  colNum = floor(x / (col_width(dcal) + COL_BORDER_SIZE));
1638 
1639  x %= (col_width(dcal) + COL_BORDER_SIZE);
1640  x -= dcal->label_width;
1641  if (x < 0)
1642  {
1643  return -1;
1644  }
1645  if (x >= day_width(dcal) * 7)
1646  {
1647  return -1;
1648  }
1649 
1650  y -= dcal->dayLabelHeight;
1651  if (y < 0)
1652  {
1653  return -1;
1654  }
1655 
1656  dayCol = floor((float)x / (float)day_width(dcal));
1657  weekRow = floor((float)y / (float)week_height(dcal));
1658 
1659  g_date_set_dmy(&startD, 1, dcal->month, dcal->year);
1660  d = startD;
1661  g_date_add_months(&d, (colNum * dcal->monthsPerCol));
1662  dayCol -= ((g_date_get_weekday(&d) - dcal->week_starts_monday) % 7);
1663  if (weekRow == 0)
1664  {
1665  if (dayCol < 0)
1666  {
1667  return -1;
1668  }
1669  }
1670  g_date_add_days(&d, dayCol + (weekRow * 7));
1671 
1672  /* Check to make sure we're within the column's displayed range. */
1673  {
1674  GDate ccd;
1675  g_date_set_dmy(&ccd, 1, dcal->month, dcal->year);
1676  g_date_add_months(&ccd, (colNum + 1) * dcal->monthsPerCol);
1677  if (g_date_get_julian(&d) >= g_date_get_julian(&ccd))
1678  {
1679  return -1;
1680  }
1681  }
1682 
1683  dayOfCal = g_date_get_julian(&d) - g_date_get_julian(&startD);
1684 
1685  /* one more check before returning... */
1686  g_date_subtract_months(&d, dcal->numMonths);
1687  if (g_date_get_julian(&d) >= g_date_get_julian(&startD))
1688  {
1689  /* we're past the end of the displayed calendar, thus -1 */
1690  g_debug("%d >= %d", g_date_get_julian(&d), g_date_get_julian(&startD));
1691  return -1;
1692  }
1693 
1694  return dayOfCal;
1695 }
1696 
1697 static gint
1698 gdc_get_doc_offset(GncDenseCal *dcal, GDate *d)
1699 {
1700  gint toRet;
1701  /* soc == start-of-calendar */
1702  GDate soc;
1703 
1704  g_date_clear(&soc, 1);
1705  g_date_set_dmy(&soc, 1, dcal->month, dcal->year);
1706  /* ensure not before calendar start. */
1707  if (g_date_get_julian(d) < g_date_get_julian(&soc))
1708  return -1;
1709  /* do computation here, since we're going to change the
1710  * start-of-calendar date. */
1711  toRet = g_date_get_julian(d) - g_date_get_julian(&soc);
1712  /* ensure not after end of visible calendar. */
1713  g_date_add_months(&soc, dcal->numMonths);
1714  if (g_date_get_julian(d) >= g_date_get_julian(&soc))
1715  return -1;
1716  /* return pre-computed value. */
1717  return toRet;
1718 }
1719 
1720 static void
1721 gdc_add_tag_markings(GncDenseCal *cal, guint tag)
1722 {
1723  gchar *name, *info;
1724  gint num_marks, idx;
1725  GDate **dates;
1726  GDate *calDate;
1727 
1728  // copy the values into the old marking function.
1729  name = gnc_dense_cal_model_get_name(cal->model, tag);
1730  info = gnc_dense_cal_model_get_info(cal->model, tag);
1731  num_marks = gnc_dense_cal_model_get_instance_count(cal->model, tag);
1732 
1733  if (num_marks == 0)
1734  goto cleanup;
1735 
1736  dates = g_new0(GDate*, num_marks);
1737  calDate = g_date_new_dmy(1, cal->month, cal->year);
1738 
1739  for (idx = 0; idx < num_marks; idx++)
1740  {
1741  dates[idx] = g_date_new();
1742  gnc_dense_cal_model_get_instance(cal->model, tag, idx, dates[idx]);
1743 
1744  }
1745  if (g_date_valid(dates[0]))
1746  {
1747  if (g_date_get_julian(dates[0]) < g_date_get_julian(calDate))
1748  {
1749  _gnc_dense_cal_set_month(cal, g_date_get_month(dates[0]), FALSE);
1750  _gnc_dense_cal_set_year(cal, g_date_get_year(dates[0]), FALSE);
1751  }
1752  }
1753  else
1754  {
1755  g_warning("Bad date, skipped.");
1756  }
1757  gdc_mark_add(cal, tag, name, info, num_marks, dates);
1758 
1759  for (idx = 0; idx < num_marks; idx++)
1760  {
1761  g_date_free(dates[idx]);
1762  }
1763  g_free(dates);
1764  g_date_free(calDate);
1765 
1766 cleanup:
1767  g_free(info);
1768 }
1769 
1770 static void
1771 gdc_add_markings(GncDenseCal *cal)
1772 {
1773  GList *tags;
1774  tags = gnc_dense_cal_model_get_contained(cal->model);
1775  for (; tags != NULL; tags = tags->next)
1776  {
1777  guint tag = GPOINTER_TO_UINT(tags->data);
1778  gdc_add_tag_markings(cal, tag);
1779  }
1780  g_list_free(tags);
1781 }
1782 
1783 static void
1784 gdc_remove_markings(GncDenseCal *cal)
1785 {
1786  GList *tags;
1787  tags = gnc_dense_cal_model_get_contained(cal->model);
1788  for (; tags != NULL; tags = tags->next)
1789  {
1790  guint tag = GPOINTER_TO_UINT(tags->data);
1791  gdc_mark_remove(cal, tag, FALSE);
1792  }
1793  g_list_free(tags);
1794 }
1795 
1796 static void
1797 gdc_model_added_cb(GncDenseCalModel *model, guint added_tag, gpointer user_data)
1798 {
1799  GncDenseCal *cal = GNC_DENSE_CAL(user_data);
1800  g_debug("gdc_model_added_cb update\n");
1801  gdc_add_tag_markings(cal, added_tag);
1802 }
1803 
1804 static void
1805 gdc_model_update_cb(GncDenseCalModel *model, guint update_tag, gpointer user_data)
1806 {
1807  GncDenseCal *cal = GNC_DENSE_CAL(user_data);
1808  g_debug("gdc_model_update_cb update for tag [%d]\n", update_tag);
1809  gdc_mark_remove(cal, update_tag, FALSE);
1810  gdc_add_tag_markings(cal, update_tag);
1811 
1812 }
1813 
1814 static void
1815 gdc_model_removing_cb(GncDenseCalModel *model, guint remove_tag, gpointer user_data)
1816 {
1817  GncDenseCal *cal = GNC_DENSE_CAL(user_data);
1818  g_debug("gdc_model_removing_cb update [%d]\n", remove_tag);
1819  gdc_mark_remove(cal, remove_tag, TRUE);
1820 }
1821 
1822 void
1823 gnc_dense_cal_set_model(GncDenseCal *cal, GncDenseCalModel *model)
1824 {
1825  if (cal->model != NULL)
1826  {
1827  gdc_remove_markings(cal);
1828  g_object_unref(G_OBJECT(cal->model));
1829  cal->model = NULL;
1830  }
1831  cal->model = model;
1832  g_object_ref(G_OBJECT(model));
1833  g_signal_connect(G_OBJECT(cal->model), "added", (GCallback)gdc_model_added_cb, cal);
1834  g_signal_connect(G_OBJECT(cal->model), "update", (GCallback)gdc_model_update_cb, cal);
1835  g_signal_connect(G_OBJECT(cal->model), "removing", (GCallback)gdc_model_removing_cb, cal);
1836 
1837  gdc_add_markings(cal);
1838 }
1839 
1843 static void
1844 gdc_mark_add(GncDenseCal *dcal,
1845  guint tag,
1846  gchar *name,
1847  gchar *info,
1848  guint size,
1849  GDate **dateArray)
1850 {
1851  guint i;
1852  gint doc;
1853  gdc_mark_data *newMark;
1854  GDate *d;
1855 
1856  if (size == 0)
1857  {
1858  g_error("0 size not allowed\n");
1859  return;
1860  }
1861 
1862  newMark = g_new0(gdc_mark_data, 1);
1863  newMark->name = NULL;
1864  if (name)
1865  newMark->name = g_strdup(name);
1866  newMark->info = NULL;
1867  if (info)
1868  newMark->info = g_strdup(info);
1869  newMark->tag = tag;
1870  newMark->ourMarks = NULL;
1871  g_debug("saving mark with tag [%d]\n", newMark->tag);
1872 
1873  for (i = 0; i < size; i++)
1874  {
1875  d = dateArray[i];
1876  doc = gdc_get_doc_offset(dcal, d);
1877  if (doc < 0)
1878  continue;
1879  if (doc >= dcal->numMarks)
1880  {
1881  /* It's not going to get any better, so just
1882  * stop processing. */
1883  break;
1884  }
1885  dcal->marks[doc] = g_list_append(dcal->marks[doc], newMark);
1886  newMark->ourMarks = g_list_append(newMark->ourMarks,
1887  GINT_TO_POINTER(doc));
1888  }
1889  dcal->markData = g_list_append(dcal->markData, (gpointer)newMark);
1890  gnc_dense_cal_draw_to_buffer(dcal);
1891  gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
1892 }
1893 
1894 static void
1895 gdc_mark_remove(GncDenseCal *dcal, guint mark_to_remove, gboolean redraw)
1896 {
1897  GList *iter, *calendar_marks;
1898  gint day_of_cal;
1899  gdc_mark_data *mark_data;
1900 
1901  /* Ignore non-realistic marks */
1902  if ((gint)mark_to_remove == -1)
1903  {
1904  g_debug("mark_to_remove = -1");
1905  return;
1906  }
1907 
1908  mark_data = NULL;
1909  for (iter = dcal->markData; iter != NULL; iter = iter->next)
1910  {
1911  mark_data = (gdc_mark_data*)iter->data;
1912  if (mark_data->tag == mark_to_remove)
1913  break;
1914  }
1915  if (iter == NULL)
1916  {
1917  g_message("couldn't find tag [%d]", mark_to_remove);
1918  return;
1919  }
1920  if (mark_data == NULL)
1921  {
1922  g_debug("mark_data == null");
1923  return;
1924  }
1925 
1926  for (calendar_marks = mark_data->ourMarks; calendar_marks != NULL; calendar_marks = calendar_marks->next)
1927  {
1928  day_of_cal = GPOINTER_TO_INT(calendar_marks->data);
1929  dcal->marks[day_of_cal] = g_list_remove(dcal->marks[day_of_cal], mark_data);
1930  }
1931  g_list_free(mark_data->ourMarks);
1932  dcal->markData = g_list_remove(dcal->markData, mark_data);
1933  g_free(mark_data);
1934 
1935  if (redraw)
1936  {
1937  gnc_dense_cal_draw_to_buffer(dcal);
1938  gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
1939  }
1940 }
void gnc_gdate_set_today(GDate *gd)
void gnc_dow_abbrev(gchar *buf, int buf_len, int dow)
GList * markData
All type declarations for the whole Gnucash engine.
GDate helper routines.