GnuCash  5.6-150-g038405b370+
gnc-dense-cal.c
1 /********************************************************************\
2  * gnc-dense-cal.c : a custom densely-dispalyed calendar widget *
3  * Copyright (C) 2002,2006 Joshua Sled <jsled@asynchronous.org> *
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 gnu@gnu.org *
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 "gnc-gtk-utils.h"
29 #include <glib.h>
30 #include <glib/gi18n.h>
31 #include <gtk/gtk.h>
32 #include <math.h>
33 #include <stdlib.h>
34 #include "gnc-date.h"
35 #include "dialog-utils.h"
36 #include <qoflog.h>
37 
38 static const QofLogModule log_module = "gnc.gui.dense-cal";
39 
71 static const int DENSE_CAL_DEFAULT_WIDTH = 15;
72 static const int DENSE_CAL_DEFAULT_HEIGHT = 105;
73 static const int MINOR_BORDER_SIZE = 1;
74 static const int COL_BORDER_SIZE = 3;
75 
76 static void gnc_dense_cal_finalize (GObject *object);
77 static void gnc_dense_cal_dispose (GObject *object);
78 static void gnc_dense_cal_realize (GtkWidget *widget, gpointer user_data);
79 static void gnc_dense_cal_configure (GtkWidget *widget,
80  GdkEventConfigure *event,
81  gpointer user_data);
82 static void gnc_dense_cal_draw_to_buffer (GncDenseCal *dcal);
83 static gboolean gnc_dense_cal_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data);
84 
85 static void gdc_reconfig (GncDenseCal *dcal);
86 
87 static void gdc_free_all_mark_data (GncDenseCal *dcal);
88 
89 static void _gdc_compute_min_size (GncDenseCal *dcal,
90  guint *min_width, guint *min_height);
91 static void _gdc_set_cal_min_size_req (GncDenseCal *dcal);
92 static gint gnc_dense_cal_motion_notify (GtkWidget *widget,
93  GdkEventMotion *event);
94 static gint gnc_dense_cal_button_press (GtkWidget *widget,
95  GdkEventButton *evt);
96 
97 static void _gdc_view_option_changed (GtkComboBox *widget, gpointer user_data);
98 
99 static inline int day_width_at (GncDenseCal *dcal, guint xScale);
100 static inline int day_width (GncDenseCal *dcal);
101 static inline int day_height_at (GncDenseCal *dcal, guint yScale);
102 static inline int day_height (GncDenseCal *dcal);
103 static inline int week_width_at (GncDenseCal *dcal, guint xScale);
104 static inline int week_width (GncDenseCal *dcal);
105 static inline int week_height_at (GncDenseCal *dcal, guint yScale);
106 static inline int week_height (GncDenseCal *dcal);
107 static inline int col_width_at (GncDenseCal *dcal, guint xScale);
108 static inline int col_width (GncDenseCal *dcal);
109 
110 static inline int col_height (GncDenseCal *dcal);
111 static inline int num_cols (GncDenseCal *dcal);
112 
113 static void _gnc_dense_cal_set_month (GncDenseCal *dcal, GDateMonth mon, gboolean redraw);
114 static void _gnc_dense_cal_set_year (GncDenseCal *dcal, guint year, gboolean redraw);
115 
120 static inline int num_weeks (GncDenseCal *dcal);
125 static int num_weeks_per_col (GncDenseCal *dcal);
126 
127 /* hotspot calculation */
128 static gint wheres_this (GncDenseCal *dcal, int x, int y);
129 
130 static void recompute_x_y_scales (GncDenseCal *dcal);
131 static void recompute_mark_storage (GncDenseCal *dcal);
132 static void recompute_extents (GncDenseCal *dcal);
133 static void populate_hover_window (GncDenseCal *dcal);
134 
135 static void month_coords (GncDenseCal *dcal, int monthOfCal, GList **outList);
136 static void doc_coords (GncDenseCal *dcal, int dayOfCal,
137  int *x1, int *y1, int *x2, int *y2);
138 
139 static void gdc_mark_add (GncDenseCal *dcal, guint tag, gchar *name,
140  gchar *info, guint size, GDate **dateArray);
141 static void gdc_mark_remove (GncDenseCal *dcal, guint mark_to_remove, gboolean redraw);
142 
143 static void gdc_add_tag_markings (GncDenseCal *cal, guint tag);
144 static void gdc_add_markings (GncDenseCal *cal);
145 static void gdc_remove_markings (GncDenseCal *cal);
146 
147 typedef struct _gdc_month_coords
148 {
149  gint x, y;
151 
153 {
154  GtkBox widget;
155 
156  GtkComboBox *view_options;
157  GtkDrawingArea *cal_drawing_area;
158 
159  cairo_surface_t *surface;
160 
161  gboolean initialized;
162 
163  gboolean showPopup;
164  GtkWindow *transPopup;
165  gint screen_width;
166  gint screen_height;
167  gint doc;
168 
169  gint min_x_scale;
170  gint min_y_scale;
171 
172  gint x_scale;
173  gint y_scale;
174 
175  gint numMonths;
176  gint monthsPerCol;
177  gint num_weeks; /* computed */
178 
179  GDateMonth month;
180  guint year;
181  gint firstOfMonthOffset;
182 
183  gint leftPadding;
184  gint topPadding;
185 
186  gdc_month_coords monthPositions[12];
187 
188  gint label_height; // dense cal label height
189 
190  guint month_side_bar_width; // month side bar width
191  guint day_top_bar_height; // day top bar height
192  guint bar_label_padding; // padding used in top and side bar
193 
194  GncDenseCalModel *model;
195 
196  guint lastMarkTag;
197 
198  GDateWeekday day_of_week_start;
199 
203  GList *markData;
204  int numMarks;
205  /* array of GList*s of per-cell markings. */
206  GList **marks;
207 
208  int disposed; /* private */
209 };
210 
211 typedef struct _gdc_mark_data
212 {
213  gchar *name;
214  gchar *info;
215  guint tag;
219  GList *ourMarks;
220 } gdc_mark_data;
221 
222 G_DEFINE_TYPE(GncDenseCal, gnc_dense_cal, GTK_TYPE_BOX)
223 
224 #define MONTH_NAME_BUFSIZE 10
225 
226 /* Takes the number of months since January, in the range 0 to
227  * 11. Returns the abbreviated month name according to the current
228  * locale.*/
229 static const gchar*
230 month_name (int mon)
231 {
232  static gchar buf[MONTH_NAME_BUFSIZE];
233  GDate date;
234  gint arbitrary_year = 1977;
235 
236  memset (buf, 0, MONTH_NAME_BUFSIZE);
237  g_date_clear (&date, 1);
238 
239  g_date_set_year (&date, arbitrary_year);
240  g_date_set_day (&date, 1);
241  // g_date API is 1..12 (not 0..11)
242  g_date_set_month (&date, mon + 1);
243  g_date_strftime (buf, MONTH_NAME_BUFSIZE, "%b", &date);
244 
245  return buf;
246 }
247 
248 /* Takes the number of days since Sunday, in the range 0 to 6. Returns
249  * the abbreviated weekday name according to the current locale. */
250 static void
251 day_label (gchar *buf, int buf_len, int dow)
252 {
253  gnc_dow_abbrev (buf, buf_len, dow);
254  /* Use only the first two characters */
255  if (g_utf8_strlen (buf, -1) > 2)
256  {
257  gchar *pointer = g_utf8_offset_to_pointer (buf, 2);
258  *pointer = '\0';
259  }
260 }
261 
262 static void
263 gnc_dense_cal_class_init (GncDenseCalClass *klass)
264 {
265  GObjectClass *object_class = G_OBJECT_CLASS(klass);
266  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
267 
268  gtk_widget_class_set_css_name (GTK_WIDGET_CLASS(klass), "calendar");
269 
270  object_class->finalize = gnc_dense_cal_finalize;
271  object_class->dispose = gnc_dense_cal_dispose;
272 
273  widget_class->motion_notify_event = gnc_dense_cal_motion_notify;
274  widget_class->button_press_event = gnc_dense_cal_button_press;
275 }
276 
277 enum _GdcViewOptsColumns
278 {
279  VIEW_OPTS_COLUMN_LABEL = 0,
280  VIEW_OPTS_COLUMN_NUM_MONTHS,
281  VIEW_OPTS_COLUMN_NUM_MONTHS_PER_COLUMN
282 };
283 
284 static GtkListStore *_cal_view_options = NULL;
285 static GtkListStore*
286 _gdc_get_view_options (void)
287 {
288  if (_cal_view_options == NULL)
289  {
290  _cal_view_options = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
291  gtk_list_store_insert_with_values (_cal_view_options, NULL, G_MAXINT, 0, _("12 months"), 1, 12, 2, 3, -1);
292  gtk_list_store_insert_with_values (_cal_view_options, NULL, G_MAXINT, 0, _("6 months"), 1, 6, 2, 2, -1);
293  gtk_list_store_insert_with_values (_cal_view_options, NULL, G_MAXINT, 0, _("4 months"), 1, 4, 2, 2, -1);
294  gtk_list_store_insert_with_values (_cal_view_options, NULL, G_MAXINT, 0, _("3 months"), 1, 3, 2, 2, -1);
295  gtk_list_store_insert_with_values (_cal_view_options, NULL, G_MAXINT, 0, _("2 months"), 1, 2, 2, 1, -1);
296  gtk_list_store_insert_with_values (_cal_view_options, NULL, G_MAXINT, 0, _("1 month"), 1, 1, 2, 1, -1);
297  }
298 
299  return _cal_view_options;
300 }
301 
302 static void
303 gnc_dense_cal_init (GncDenseCal *dcal)
304 {
305  GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET(dcal));
306 
307  gtk_orientable_set_orientation (GTK_ORIENTABLE(dcal), GTK_ORIENTATION_VERTICAL);
308 
309  // Set the style context for this widget so it can be easily manipulated with css
310  gnc_widget_style_context_add_class (GTK_WIDGET(dcal), "calendar");
311 
312  // Set the name of this widget so it can be easily manipulated with css
313  gtk_widget_set_name (GTK_WIDGET(dcal), "gnc-id-dense-calendar");
314 
315  gtk_style_context_add_class (context, GTK_STYLE_CLASS_CALENDAR);
316  {
317  GtkTreeModel *options = GTK_TREE_MODEL(_gdc_get_view_options());
318  GtkCellRenderer *text_rend = GTK_CELL_RENDERER(gtk_cell_renderer_text_new ());
319 
320  dcal->view_options = GTK_COMBO_BOX(gtk_combo_box_new_with_model (options));
321  gtk_combo_box_set_active (GTK_COMBO_BOX(dcal->view_options), 0);
322  gtk_cell_layout_pack_start (GTK_CELL_LAYOUT(dcal->view_options), text_rend, TRUE);
323  gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT(dcal->view_options),
324  text_rend, "text", VIEW_OPTS_COLUMN_LABEL);
325  g_signal_connect (G_OBJECT(dcal->view_options), "changed",
326  G_CALLBACK(_gdc_view_option_changed), (gpointer)dcal);
327  }
328 
329  {
330  GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
331  GtkWidget *label = gtk_label_new (_("View"));
332 
333  gtk_box_set_homogeneous (GTK_BOX(hbox), FALSE);
334  gtk_widget_set_halign (label, GTK_ALIGN_END);
335  gtk_widget_set_margin_end (label, 5);
336  gtk_box_pack_start (GTK_BOX(hbox), label, TRUE, TRUE, 0);
337  gtk_box_pack_start (GTK_BOX(hbox), GTK_WIDGET(dcal->view_options), FALSE, FALSE, 0);
338 
339  gtk_box_pack_start (GTK_BOX(dcal), GTK_WIDGET(hbox), FALSE, FALSE, 0);
340  }
341  dcal->cal_drawing_area = GTK_DRAWING_AREA(gtk_drawing_area_new ());
342 
343  gtk_widget_add_events (GTK_WIDGET(dcal->cal_drawing_area), (GDK_EXPOSURE_MASK
344  | GDK_BUTTON_PRESS_MASK
345  | GDK_BUTTON_RELEASE_MASK
346  | GDK_POINTER_MOTION_MASK
347  | GDK_POINTER_MOTION_HINT_MASK));
348  gtk_box_pack_start (GTK_BOX(dcal), GTK_WIDGET(dcal->cal_drawing_area), TRUE, TRUE, 0);
349  g_signal_connect (G_OBJECT(dcal->cal_drawing_area), "draw",
350  G_CALLBACK(gnc_dense_cal_draw), (gpointer)dcal);
351  g_signal_connect (G_OBJECT(dcal->cal_drawing_area), "realize",
352  G_CALLBACK(gnc_dense_cal_realize), (gpointer)dcal);
353  g_signal_connect (G_OBJECT(dcal->cal_drawing_area), "configure_event",
354  G_CALLBACK(gnc_dense_cal_configure), (gpointer)dcal);
355 
356  dcal->disposed = FALSE;
357  dcal->initialized = FALSE;
358  dcal->markData = NULL;
359  dcal->numMarks = 0;
360  dcal->marks = NULL;
361  dcal->lastMarkTag = 0;
362 
363  dcal->showPopup = FALSE;
364 
365  dcal->transPopup = GTK_WINDOW(gtk_window_new (GTK_WINDOW_POPUP));
366  {
367  GtkWidget *vbox, *hbox;
368  GtkWidget *l;
369  GtkListStore *tree_data;
370  GtkTreeView *tree_view;
371 
372  vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
373  gtk_box_set_homogeneous (GTK_BOX(vbox), FALSE);
374  hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
375  gtk_box_set_homogeneous (GTK_BOX(hbox), FALSE);
376 
377  gtk_widget_set_name (GTK_WIDGET(dcal->transPopup), "gnc-id-dense-calendar-popup");
378 
379  l = gtk_label_new (_("Date: "));
380  gtk_widget_set_margin_start (l, 5);
381  gtk_container_add (GTK_CONTAINER(hbox), l);
382  l = gtk_label_new ("YY/MM/DD");
383  g_object_set_data (G_OBJECT(dcal->transPopup), "dateLabel", l);
384  gtk_container_add (GTK_CONTAINER(hbox), l);
385  gtk_container_add (GTK_CONTAINER(vbox), hbox);
386 
387  gtk_container_add (GTK_CONTAINER(vbox), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL));
388 
389  tree_data = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
390  tree_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model (GTK_TREE_MODEL(tree_data)));
391  gtk_tree_view_insert_column_with_attributes (tree_view, -1, _("Name"),
392  gtk_cell_renderer_text_new (), "text", 0, NULL);
393  gtk_tree_view_insert_column_with_attributes (tree_view, -1, _("Frequency"),
394  gtk_cell_renderer_text_new (), "text", 1, NULL);
395  gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_view)), GTK_SELECTION_NONE);
396  g_object_set_data (G_OBJECT(dcal->transPopup), "model", tree_data);
397  g_object_unref (tree_data);
398 
399  gtk_container_add (GTK_CONTAINER(vbox), GTK_WIDGET(tree_view));
400  gtk_container_add (GTK_CONTAINER(dcal->transPopup), vbox);
401 
402  gtk_window_set_resizable (GTK_WINDOW(dcal->transPopup), FALSE);
403 
404  gtk_widget_realize (GTK_WIDGET(dcal->transPopup));
405  }
406 
407  dcal->month = G_DATE_JANUARY;
408  dcal->year = 1970;
409 
410  dcal->numMonths = 12;
411  dcal->monthsPerCol = 3;
412  dcal->leftPadding = 4;
413  dcal->topPadding = 4;
414 
415  {
416  GDate now;
417  g_date_clear (&now, 1);
418  gnc_gdate_set_today (&now);
419  _gnc_dense_cal_set_month (dcal, g_date_get_month (&now), FALSE);
420  _gnc_dense_cal_set_year (dcal, g_date_get_year (&now), FALSE);
421  }
422 
423  recompute_extents (dcal);
424  recompute_mark_storage (dcal);
425 
426  /* Compute initial scaling factors; will be increased when we're
427  * allocated enough space to scale up. */
428  {
429  GtkBorder padding;
430  PangoLayout *layout;
431  int width_88, height_88;
432  int width_XXX, height_XXX;
433 
434  layout = gtk_widget_create_pango_layout (GTK_WIDGET(dcal), NULL);
435 
436  pango_layout_set_text (layout, "88", -1);
437  pango_layout_get_pixel_size (layout, &width_88, &height_88);
438 
439  pango_layout_set_text (layout, "XXX", -1);
440  pango_layout_get_pixel_size (layout, &width_XXX, &height_XXX);
441 
442  dcal->min_x_scale = dcal->x_scale = width_88 + 2;
443  dcal->min_y_scale = dcal->y_scale = MAX(floor ((float)width_XXX / 3.), height_88 + 2);
444 
445  gtk_style_context_get_padding (context, GTK_STATE_FLAG_NORMAL, &padding);
446  if ((padding.top + padding.bottom) == 0)
447  dcal->bar_label_padding = 2; // px
448  else
449  dcal->bar_label_padding = (padding.top + padding.bottom) / 2;
450 
451  dcal->month_side_bar_width = height_88 + (dcal->bar_label_padding * 2);
452  dcal->day_top_bar_height = height_88 + (dcal->bar_label_padding * 2);
453 
454  g_object_unref (layout);
455  }
456 
457  dcal->initialized = TRUE;
458 
459  dcal->day_of_week_start = G_DATE_SUNDAY;
460 
461  // Sunday = 1, M = 2, T = 3, W = 4, Th = 5, Fr = 6, Sat = 7
462  gint first_day = gnc_start_of_week ();
463 
464  // Convert to GDateWeekday 1=Mon,2=Tues,3=Wed,4=Thu,5=Fri,6=Sat,7=Sun
465  if (first_day == 1)
466  first_day = G_DATE_SUNDAY;
467  else
468  first_day = first_day - 1;
469 
470  if (first_day > 0 && first_day < 8)
471  dcal->day_of_week_start = first_day;
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 (GtkWindow *parent)
487 {
488  GncDenseCal *dcal = g_object_new (GNC_TYPE_DENSE_CAL, NULL);
489 
490  gtk_window_set_transient_for (GTK_WINDOW(dcal->transPopup),
491  GTK_WINDOW(parent));
492 
493  return GTK_WIDGET(dcal);
494 }
495 
496 GtkWidget*
497 gnc_dense_cal_new_with_model (GtkWindow *parent, GncDenseCalModel *model)
498 {
499  GncDenseCal *cal = GNC_DENSE_CAL(gnc_dense_cal_new (parent));
500  gnc_dense_cal_set_model (cal, model);
501  return GTK_WIDGET(cal);
502 }
503 
504 static void
505 recompute_first_of_month_offset (GncDenseCal *dcal)
506 {
507  GDate *tmpDate;
508 
509  tmpDate = g_date_new_dmy (1, dcal->month, dcal->year);
510  dcal->firstOfMonthOffset = g_date_get_weekday (tmpDate) % 7;
511  g_date_free (tmpDate);
512 }
513 
514 void
515 gnc_dense_cal_set_month (GncDenseCal *dcal, GDateMonth mon)
516 {
517  _gnc_dense_cal_set_month (dcal, mon, TRUE);
518 }
519 
520 static void
521 _gnc_dense_cal_set_month (GncDenseCal *dcal, GDateMonth mon, gboolean redraw)
522 {
523  if (dcal->month == mon)
524  return;
525 
526  dcal->month = mon;
527 
528  recompute_first_of_month_offset (dcal);
529 
530  recompute_extents (dcal);
531 
532  if (redraw && gtk_widget_get_realized (GTK_WIDGET(dcal)))
533  {
534  recompute_x_y_scales (dcal);
535  gnc_dense_cal_draw_to_buffer (dcal);
536  gtk_widget_queue_draw (GTK_WIDGET(dcal->cal_drawing_area));
537  }
538 }
539 
540 void
541 gnc_dense_cal_set_year (GncDenseCal *dcal, guint year)
542 {
543  _gnc_dense_cal_set_year (dcal, year, TRUE);
544 }
545 
546 static void
547 _gnc_dense_cal_set_year (GncDenseCal *dcal, guint year, gboolean redraw)
548 {
549  if (dcal->year == year)
550  return;
551  dcal->year = year;
552  recompute_first_of_month_offset (dcal);
553  recompute_extents (dcal);
554  if (redraw && gtk_widget_get_realized (GTK_WIDGET(dcal)))
555  {
556  recompute_x_y_scales (dcal);
557  gnc_dense_cal_draw_to_buffer (dcal);
558  gtk_widget_queue_draw (GTK_WIDGET(dcal->cal_drawing_area));
559  }
560 }
561 
562 void
563 gnc_dense_cal_set_num_months (GncDenseCal *dcal, guint num_months)
564 {
565  GtkListStore *options = _gdc_get_view_options ();
566  GtkTreeIter view_opts_iter, iter_closest_to_req;
567  int months_per_column = 0;
568  int closest_index_distance = G_MAXINT;
569 
570  // find closest list value to num_months
571  if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL(options), &view_opts_iter))
572  {
573  g_critical ("no view options?");
574  return;
575  }
576 
577  do
578  {
579  gint months_val, delta_months;
580  gtk_tree_model_get (GTK_TREE_MODEL(options), &view_opts_iter,
581  VIEW_OPTS_COLUMN_NUM_MONTHS, &months_val,
582  VIEW_OPTS_COLUMN_NUM_MONTHS_PER_COLUMN, &months_per_column,
583  -1);
584 
585  delta_months = abs (months_val - (int)num_months);
586  if (delta_months < closest_index_distance)
587  {
588  iter_closest_to_req = view_opts_iter;
589  closest_index_distance = delta_months;
590  }
591  }
592  while (closest_index_distance != 0
593  && (gtk_tree_model_iter_next (GTK_TREE_MODEL(options), &view_opts_iter)));
594 
595  // set iter on view
596  g_signal_handlers_block_by_func (dcal->view_options, _gdc_view_option_changed, dcal);
597  gtk_combo_box_set_active_iter (GTK_COMBO_BOX(dcal->view_options), &iter_closest_to_req);
598  g_signal_handlers_unblock_by_func (dcal->view_options, _gdc_view_option_changed, dcal);
599 
600  // set the number of months per column if found in model
601  if (months_per_column != 0)
602  dcal->monthsPerCol = months_per_column;
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->surface)
661  {
662  cairo_surface_destroy (dcal->surface);
663  dcal->surface = 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  G_OBJECT_CLASS(gnc_dense_cal_parent_class)->dispose(object);
673 }
674 
675 static void
676 gnc_dense_cal_finalize (GObject *object)
677 {
678  g_return_if_fail (object != NULL);
679  g_return_if_fail (GNC_IS_DENSE_CAL(object));
680 
681  G_OBJECT_CLASS(gnc_dense_cal_parent_class)->finalize(object);
682 }
683 
684 static void
685 gnc_dense_cal_configure (GtkWidget *widget,
686  GdkEventConfigure *event,
687  gpointer user_data)
688 {
689  GncDenseCal *dcal = GNC_DENSE_CAL(user_data);
690  recompute_x_y_scales (dcal);
691  gdc_reconfig (dcal);
692  gtk_widget_queue_draw_area (widget,
693  event->x, event->y,
694  event->width, event->height);
695 }
696 
697 static void
698 gnc_dense_cal_realize (GtkWidget *widget, gpointer user_data)
699 {
700  GncDenseCal *dcal;
701 
702  g_return_if_fail (widget != NULL);
703  g_return_if_fail (GNC_IS_DENSE_CAL(user_data));
704  dcal = GNC_DENSE_CAL(user_data);
705 
706  recompute_x_y_scales (dcal);
707  gdc_reconfig (dcal);
708 }
709 
710 static void
711 gdc_reconfig (GncDenseCal *dcal)
712 {
713  GtkWidget *widget;
714  GtkAllocation alloc;
715 
716  if (dcal->surface)
717  cairo_surface_destroy (dcal->surface);
718 
719  widget = GTK_WIDGET(dcal->cal_drawing_area);
720  gtk_widget_get_allocation (widget, &alloc);
721  dcal->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
722  alloc.width,
723  alloc.height);
724  gnc_dense_cal_draw_to_buffer (dcal);
725 }
726 
727 static void
728 _gdc_compute_min_size (GncDenseCal *dcal, guint *min_width, guint *min_height)
729 {
730  if (min_width != NULL)
731  {
732  *min_width =
733  (dcal->leftPadding * 2)
734  + (num_cols (dcal) * (col_width_at (dcal, dcal->min_x_scale)
735  + dcal->month_side_bar_width))
736  + ((num_cols (dcal) - 1) * COL_BORDER_SIZE);
737  }
738 
739  if (min_height != NULL)
740  {
741  *min_height =
742  (dcal->topPadding * 2)
743  + MINOR_BORDER_SIZE
744  + dcal->day_top_bar_height
745  + (num_weeks_per_col (dcal)
746  * week_height_at (dcal, dcal->min_y_scale));
747  }
748 }
749 
750 static void
751 recompute_x_y_scales (GncDenseCal *dcal)
752 {
753  int denom;
754  int width, height;
755 
756  width = DENSE_CAL_DEFAULT_WIDTH;
757  height = DENSE_CAL_DEFAULT_HEIGHT;
758  if (dcal->initialized)
759  {
760  GtkAllocation alloc;
761  gtk_widget_get_allocation (GTK_WIDGET(dcal->cal_drawing_area), &alloc);
762  width = alloc.width;
763  height = alloc.height;
764  }
765 
766  /* FIXME: there's something slightly wrong in the x_scale computation that
767  * lets us draw larger than our area. */
768  denom = 7 * num_cols (dcal);
769  g_assert (denom != 0);
770  dcal->x_scale = ((gint)(width
771  - (dcal->leftPadding * 2)
772  - (num_cols (dcal) * ((8 * MINOR_BORDER_SIZE)
773  + dcal->month_side_bar_width))
774  - ((num_cols (dcal) - 1) * COL_BORDER_SIZE))
775  / denom);
776  dcal->x_scale = MAX(dcal->x_scale, dcal->min_x_scale);
777 
778  denom = num_weeks_per_col (dcal);
779  g_assert (denom != 0);
780  dcal->y_scale = ((gint)(height
781  - (dcal->topPadding * 2)
782  - MINOR_BORDER_SIZE
783  - dcal->day_top_bar_height
784  - (num_weeks_per_col (dcal) - 1
785  * MINOR_BORDER_SIZE))
786  / denom);
787  dcal->y_scale = MAX(dcal->y_scale, dcal->min_y_scale);
788 
789  _gdc_set_cal_min_size_req (dcal);
790 }
791 
792 static void
793 gdc_free_all_mark_data (GncDenseCal *dcal)
794 {
795  int i;
796  GList *l;
797  for (i = 0; i < dcal->numMarks; i++)
798  {
799  /* Each of these just contains an elt of dcal->markData,
800  * which we're about to free, below... */
801  g_list_free (dcal->marks[i]);
802  }
803  g_free (dcal->marks);
804  dcal->marks = NULL;
805  /* Remove the old mark data. */
806  for (l = dcal->markData; l; l = l->next)
807  {
808  gdc_mark_data *mark = l->data;
809  g_list_free (mark->ourMarks);
810  g_free (mark->name);
811  g_free (mark->info);
812  g_free (mark);
813  }
814  g_list_free (dcal->markData);
815  dcal->markData = NULL;
816 }
817 
818 static void
819 recompute_mark_storage (GncDenseCal *dcal)
820 {
821  if (dcal->marks == NULL)
822  goto createNew;
823  gdc_free_all_mark_data (dcal);
824 
825 createNew:
826  dcal->numMarks = num_weeks (dcal) * 7;
827  dcal->marks = g_new0 (GList*, dcal->numMarks);
828  if (dcal->model)
829  gdc_add_markings (dcal);
830 }
831 
832 static gint
833 get_week_of_year (GncDenseCal *dcal, GDate *d)
834 {
835  GDateWeekday fwd, lwd;
836  GDateYear year;
837  guint day;
838  GDate first, last;
839  guint ret;
840  gint monday_offset = 1;
841  gint day_offset = 0;
842 
843  g_return_val_if_fail (g_date_valid (d), 0);
844 
845  year = g_date_get_year (d);
846 
847  if (!d->dmy)
848  return 0;
849 
850  g_date_clear (&first, 1);
851  g_date_set_dmy (&first, 1, 1, year);
852 
853  fwd = g_date_get_weekday (&first);
854 
855  day_offset = (fwd + 7 - dcal->day_of_week_start) % 7;
856 
857  if (dcal->day_of_week_start == G_DATE_SUNDAY) //Su,M,T,W,T,F,Sa
858  monday_offset = 1;
859  else if (dcal->day_of_week_start == G_DATE_MONDAY) //M,T,W,T,F,Sa,Su
860  monday_offset = 0;
861  else if (dcal->day_of_week_start == G_DATE_TUESDAY) //T,W,T,F,Sa,Su,M
862  monday_offset = 6;
863  else if (dcal->day_of_week_start == G_DATE_WEDNESDAY) //W,T,F,Sa,Su,M,T
864  monday_offset = 5;
865  else if (dcal->day_of_week_start == G_DATE_THURSDAY) //T,F,Sa,Su,M,T,W
866  monday_offset = 4;
867  else if (dcal->day_of_week_start == G_DATE_FRIDAY) //F,Sa,Su,M,T,W,T
868  monday_offset = 3;
869  else if (dcal->day_of_week_start == G_DATE_SATURDAY) //Sa,Su,M,T,W,T,F,
870  monday_offset = 2;
871  else
872  monday_offset = 1;
873 
874  day = g_date_get_day_of_year (d) - 1;
875 
876  g_date_clear (&last, 1);
877  g_date_set_dmy (&last, 31, 12, year - 1);
878  lwd = g_date_get_weekday (&last);
879  gint lday_offset = 6 - ((lwd + 7 - dcal->day_of_week_start) % 7);
880  gint addone = 1;
881 
882  if (lday_offset)
883  addone = 0;
884 
885  ret = ((day + day_offset)/7U + ((day_offset <= monday_offset) ? addone : 0));
886 
887  return ret;
888 }
889 
890 static gint
891 get_weeks_in_year (GncDenseCal *dcal, GDateYear year)
892 {
893  GDate d;
894 
895  g_return_val_if_fail (g_date_valid_year (year), 0);
896 
897  g_date_clear (&d, 1);
898  g_date_set_dmy (&d, 1, 1, year);
899  if (g_date_get_weekday (&d) == dcal->day_of_week_start) return 53;
900  g_date_set_dmy (&d, 31, 12, year);
901  if (g_date_get_weekday (&d) == dcal->day_of_week_start) return 53;
902  if (g_date_is_leap_year (year))
903  {
904  g_date_set_dmy (&d, 2, 1, year);
905  if (g_date_get_weekday (&d) == dcal->day_of_week_start) return 53;
906  g_date_set_dmy (&d, 30, 12, year);
907  if (g_date_get_weekday (&d) == dcal->day_of_week_start) return 53;
908  }
909  return 52;
910 }
911 
912 static void
913 recompute_extents (GncDenseCal *dcal)
914 {
915  GDate date;
916  gint start_week, end_week;
917 
918  g_date_clear (&date, 1);
919  g_date_set_dmy (&date, 1, dcal->month, dcal->year);
920  start_week = get_week_of_year (dcal, &date);
921  g_date_add_months (&date, dcal->numMonths);
922  end_week = get_week_of_year (dcal, &date);
923 
924  if (g_date_get_year (&date) != dcal->year)
925  end_week += get_weeks_in_year (dcal, dcal->year);
926 
927  dcal->num_weeks = end_week - start_week + 1;
928 }
929 
930 static void
931 free_rect (gpointer data, gpointer user_data)
932 {
933  g_free ((GdkRectangle*)data);
934 }
935 
936 static gboolean
937 gnc_dense_cal_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data)
938 {
939  GncDenseCal *dcal;
940 
941  g_return_val_if_fail (widget != NULL, FALSE);
942  g_return_val_if_fail (GNC_IS_DENSE_CAL(user_data), FALSE);
943 
944  dcal = GNC_DENSE_CAL(user_data);
945 
946  cairo_save (cr);
947  cairo_set_source_surface (cr, dcal->surface, 0, 0);
948  cairo_paint (cr);
949  cairo_restore (cr);
950  return TRUE;
951 }
952 
953 static void
954 gnc_dense_cal_draw_to_buffer (GncDenseCal *dcal)
955 {
956  GtkWidget *widget;
957  GtkStyleContext *stylectxt;
958  GtkStateFlags state_flags;
959  GtkAllocation alloc;
960  gint i;
961  int maxWidth;
962  PangoLayout *layout;
963  cairo_t *cr;
964  gchar *primary_color_class, *secondary_color_class, *marker_color_class;
965 
966  DEBUG("drawing");
967  widget = GTK_WIDGET(dcal);
968 
969  if (!dcal->surface)
970  return;
971 
972  cr = cairo_create (dcal->surface);
973  layout = gtk_widget_create_pango_layout (GTK_WIDGET(dcal), NULL);
974 
975  gtk_widget_get_allocation (GTK_WIDGET(dcal->cal_drawing_area), &alloc);
976  stylectxt = gtk_widget_get_style_context (GTK_WIDGET(dcal->cal_drawing_area));
977  state_flags = gtk_style_context_get_state (stylectxt);
978 
979  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_BACKGROUND);
980  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_CALENDAR);
981 
982  gtk_render_background (stylectxt, cr, 0, 0,
983  cairo_image_surface_get_width (dcal->surface),
984  cairo_image_surface_get_height (dcal->surface));
985 
986  gtk_style_context_remove_class (stylectxt, GTK_STYLE_CLASS_BACKGROUND);
987 
988  /* get the colors */
989  {
990  GdkRGBA color;
991  gchar *class_extension = NULL;
992 
993  gtk_style_context_get_color (stylectxt, GTK_STATE_FLAG_NORMAL, &color);
994 
995  if (gnc_is_dark_theme (&color))
996  class_extension = "-dark";
997 
998  primary_color_class = g_strconcat ("primary", class_extension, NULL);
999  secondary_color_class = g_strconcat ("secondary", class_extension, NULL);
1000  marker_color_class = g_strconcat ("markers", class_extension, NULL);
1001  }
1002 
1003  /* lets confirm text height size */
1004  pango_layout_set_text (layout, "S", -1);
1005  pango_layout_get_pixel_size (layout, NULL, &dcal->label_height);
1006  dcal->month_side_bar_width = dcal->label_height + (dcal->bar_label_padding * 2);
1007  dcal->day_top_bar_height = dcal->label_height + (dcal->bar_label_padding * 2);
1008 
1009  /* Fill in alternating month colors. */
1010  {
1011  gint i;
1012  GdkRectangle *rect;
1013  GList *mcList, *mcListIter;
1014 
1015  /* reset all of the month position offsets. */
1016  for (i = 0; i < 12; i++)
1017  {
1018  dcal->monthPositions[i].x = dcal->monthPositions[i].y = -1;
1019  }
1020 
1021  gtk_style_context_save (stylectxt);
1022 
1023  /* Paint the weeks for the upcoming N months. */
1024  for (i = 0; i < dcal->numMonths; i++)
1025  {
1026  mcList = NULL;
1027  month_coords (dcal, i, &mcList);
1028  dcal->monthPositions[i].x = floor (i / dcal->monthsPerCol)
1029  * (col_width (dcal) + COL_BORDER_SIZE);
1030  dcal->monthPositions[i].y = ((GdkRectangle*)mcList->next->next->next->data)->y;
1031  for (mcListIter = mcList; mcListIter != NULL; mcListIter = mcListIter->next)
1032  {
1033  rect = (GdkRectangle*)mcListIter->data;
1034  gtk_style_context_save (stylectxt);
1035 
1036  if (i % 2 == 0)
1037  gtk_style_context_add_class (stylectxt, primary_color_class);
1038  else
1039  gtk_style_context_add_class (stylectxt, secondary_color_class);
1040 
1041  gtk_render_background (stylectxt, cr, rect->x, rect->y, rect->width, rect->height);
1042  gtk_style_context_restore (stylectxt);
1043  }
1044  g_list_foreach (mcList, free_rect, NULL);
1045  g_list_free (mcList);
1046  }
1047  gtk_style_context_restore (stylectxt);
1048  }
1049 
1050  /* Highlight the marked days. */
1051  {
1052  int i;
1053  int x1, x2, y1, y2;
1054 
1055  gtk_style_context_save (stylectxt);
1056  gtk_style_context_add_class (stylectxt, marker_color_class);
1057  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_VIEW);
1058  gtk_style_context_set_state (stylectxt, GTK_STATE_FLAG_SELECTED);
1059 
1060  for (i = 0; i < dcal->numMarks; i++)
1061  {
1062  if (dcal->marks[i] != NULL)
1063  {
1064  int center_x, center_y, radius;
1065 
1066  doc_coords(dcal, i, &x1, &y1, &x2, &y2);
1067  center_x = (x1 + x2 ) / 2;
1068  center_y = (y1 + y2 ) / 2;
1069  radius = MIN((x2 - x1), (y2 - y1)) * .75;
1070 
1071  // try to compensate for row height/width being odd or even
1072  if (((y2 - y1) % 2) != 0)
1073  center_y = center_y + 1;
1074 
1075  if (((x2 - x1) % 2) != 0)
1076  center_x = center_x + 1;
1077 
1078  gtk_render_background (stylectxt, cr,
1079  center_x - (radius + 2), center_y - radius,
1080  (radius * 2) + 4, radius * 2);
1081  }
1082  }
1083  gtk_style_context_restore (stylectxt);
1084  }
1085 
1086  for (i = 0; i < num_cols (dcal); i++)
1087  {
1088  GdkRGBA color;
1089  gint x, y, w, h;
1090  gint j;
1091 
1092  cairo_save (cr);
1093  gdk_rgba_parse (&color, "black");
1094 
1095  x = dcal->leftPadding
1096  + (i * (col_width (dcal) + COL_BORDER_SIZE))
1097  + dcal->month_side_bar_width + 1;
1098  y = dcal->topPadding + dcal->day_top_bar_height;
1099  w = col_width (dcal) - COL_BORDER_SIZE - dcal->month_side_bar_width;
1100  h = col_height (dcal);
1101 
1102  gtk_style_context_save (stylectxt);
1103 
1104  /* draw the outside border [inside the month labels] */
1105  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_FRAME);
1106 
1107  gtk_render_frame (stylectxt, cr, x, y, w + 1, h + 1);
1108 
1109  gnc_style_context_get_border_color (stylectxt, state_flags, &color);
1110  cairo_set_source_rgb (cr, color.red, color.green, color.blue);
1111  cairo_set_line_width (cr, 1);
1112 
1113  /* draw the week separations */
1114  for (j = 0; j < num_weeks_per_col (dcal); j++)
1115  {
1116  gint wy = y + (j * week_height (dcal));
1117  cairo_move_to (cr, x, wy + 0.5);
1118  cairo_line_to (cr, x + w, wy + 0.5);
1119  cairo_stroke (cr);
1120  }
1121 
1122  /* draw the day separations */
1123  for (j = 1; j < 7; j++)
1124  {
1125  gint dx = x + (j * day_width (dcal));
1126  cairo_move_to (cr, dx + 0.5, y);
1127  cairo_line_to (cr, dx + 0.5, y + col_height (dcal));
1128  cairo_stroke (cr);
1129  }
1130  cairo_restore (cr);
1131  gtk_style_context_restore (stylectxt);
1132 
1133 
1134  /* draw the day of the week labels */
1135  pango_layout_set_text (layout, "88", -1);
1136  pango_layout_get_pixel_size (layout, &maxWidth, NULL);
1137 
1138  if (dcal->x_scale > maxWidth)
1139  {
1140  gtk_style_context_save (stylectxt);
1141  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_HEADER);
1142 
1143  gtk_render_background (stylectxt, cr, x,
1144  y - dcal->day_top_bar_height,
1145  (day_width(dcal) * 7) + 1,
1146  dcal->day_top_bar_height);
1147 
1148  for (j = 0; j < 7; j++)
1149  {
1150  int day_label_width;
1151  gint label_x_offset, label_y_offset;
1152  gint day_label_str_len = 4;
1153  gchar day_label_str[day_label_str_len + 1];
1154  day_label (day_label_str, day_label_str_len, (j + dcal->day_of_week_start) % 7);
1155  pango_layout_set_text (layout, day_label_str, -1);
1156  pango_layout_get_pixel_size (layout, &day_label_width, NULL);
1157  label_x_offset = x
1158  + (j * day_width (dcal))
1159  + (day_width (dcal) / 2)
1160  - (day_label_width / 2);
1161  label_y_offset = y - dcal->day_top_bar_height + dcal->bar_label_padding;
1162  pango_layout_set_text (layout, day_label_str, -1);
1163  gtk_render_layout (stylectxt, cr, label_x_offset, label_y_offset, layout);
1164  }
1165  gtk_style_context_restore (stylectxt);
1166  }
1167  }
1168 
1169  /* Month labels. */
1170  {
1171  gint i;
1172  gint x_offset = dcal->leftPadding;
1173 
1174  gtk_style_context_save (stylectxt);
1175  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_HEADER);
1176 
1177  for (i = 0; i < 12; i++)
1178  {
1179  if (dcal->monthPositions[i].x == -1)
1180  break;
1181 
1182  gtk_render_background (stylectxt, cr, dcal->monthPositions[i].x + x_offset,
1183  dcal->topPadding,
1184  dcal->month_side_bar_width + 1,
1185  col_height(dcal) + dcal->day_top_bar_height + 1);
1186  }
1187 
1188  for (i = 0; i < 12; i++)
1189  {
1190  guint idx;
1191 
1192  if (dcal->monthPositions[i].x == -1)
1193  break;
1194  idx = (dcal->month - 1 + i) % 12;
1195  pango_layout_set_text (layout, month_name (idx), -1);
1196  cairo_save (cr);
1197  cairo_translate (cr, dcal->monthPositions[i].x + x_offset, dcal->monthPositions[i].y);
1198  cairo_rotate (cr, -G_PI / 2.);
1199  gtk_render_layout (stylectxt, cr, 0, dcal->bar_label_padding, layout);
1200  cairo_restore (cr);
1201  }
1202  gtk_style_context_restore (stylectxt);
1203  }
1204 
1205  /* Day number strings [dates] */
1206  {
1207  GDate d, eoc;
1208  gint doc;
1209  gchar dayNumBuf[4];
1210  gint numW, numH;
1211  gint x1, y1, x2, y2, w, h;
1212 
1213  GDate now;
1214  g_date_clear (&now, 1);
1215  gnc_gdate_set_today (&now);
1216  gboolean today_found = FALSE;
1217 
1218  gtk_style_context_save (stylectxt);
1219  gtk_style_context_add_class (stylectxt, "day-number");
1220 
1221  cairo_save (cr);
1222  g_date_set_dmy (&d, 1, dcal->month, dcal->year);
1223  eoc = d;
1224  g_date_add_months (&eoc, dcal->numMonths);
1225  for (doc = 0; g_date_get_julian (&d) < g_date_get_julian (&eoc); g_date_add_days (&d, 1), doc++)
1226  {
1227  doc_coords (dcal, doc, &x1, &y1, &x2, &y2);
1228  memset (dayNumBuf, 0, 4);
1229  snprintf (dayNumBuf, 4, "%d", g_date_get_day(&d));
1230  pango_layout_set_text (layout, dayNumBuf, -1);
1231  pango_layout_get_pixel_size (layout, &numW, &numH);
1232  w = (x2 - x1) + 1;
1233  h = (y2 - y1) + 1;
1234 
1235  if (!today_found && g_date_compare (&d, &now) == 0)
1236  {
1237  GtkBorder border;
1238 
1239  gtk_style_context_save (stylectxt);
1240  gtk_style_context_add_class (stylectxt, marker_color_class);
1241  gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_FRAME);
1242 
1243  gtk_style_context_get_border (stylectxt, GTK_STATE_FLAG_NORMAL, &border);
1244 
1245  today_found = TRUE;
1246 
1247  if (border.left + border.right != 0)
1248  {
1249  GtkCssProvider *provider = gtk_css_provider_new ();
1250  gchar *frame_css = ".marker-border {\n border-color:black;\n}\n";
1251 
1252  gint dayw = day_width (dcal);
1253  gint dayh = day_height (dcal);
1254  gint bw = (border.left + border.right) / 2;
1255 
1256  gtk_css_provider_load_from_data (provider, frame_css, -1, NULL);
1257  gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER(provider),
1258  GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1259  g_object_unref (provider);
1260 
1261  gtk_style_context_add_class (stylectxt, "marker-border");
1262 
1263  gtk_render_frame (stylectxt, cr, x1 - (dayw / 4) + 3,
1264  y1 - (dayh / 4) + 2,
1265  dayw - 4 - bw,
1266  dayh - 4 - bw);
1267 
1268  gtk_style_context_remove_class (stylectxt, "marker-border");
1269  }
1270  gtk_style_context_restore (stylectxt);
1271  }
1272  gtk_render_layout (stylectxt, cr, x1 + (w / 2) - (numW / 2), y1 + (h / 2) - (numH / 2), layout);
1273  }
1274  cairo_restore (cr);
1275  gtk_style_context_restore (stylectxt);
1276  }
1277 
1278  gtk_widget_get_allocation (widget, &alloc);
1279  gtk_widget_queue_draw_area (GTK_WIDGET(dcal),
1280  alloc.x,
1281  alloc.y,
1282  alloc.width,
1283  alloc.height);
1284 
1285  g_free (primary_color_class);
1286  g_free (secondary_color_class);
1287  g_free (marker_color_class);
1288 
1289  g_object_unref (layout);
1290  cairo_destroy (cr);
1291 }
1292 
1293 static void
1294 populate_hover_window (GncDenseCal *dcal)
1295 {
1296  GtkWidget *w;
1297  GDate *date;
1298 
1299  if (dcal->doc >= 0)
1300  {
1301  GObject *o;
1302  GtkListStore *model;
1303  GList *l;
1304 
1305  w = GTK_WIDGET(g_object_get_data (G_OBJECT(dcal->transPopup), "dateLabel"));
1306  date = g_date_new_dmy (1, dcal->month, dcal->year);
1307  g_date_add_days (date, dcal->doc);
1308  /* Note: the ISO date format (%F or equivalently
1309  * %Y-%m-%d) is not a good idea here since many
1310  * locales will want to use a very different date
1311  * format. Please leave the specification of the date
1312  * format up to the preference. */
1313  time64 t64 = gnc_dmy2time64_neutral (g_date_get_day (date),
1314  g_date_get_month (date),
1315  g_date_get_year (date));
1316  gchar date_buff [MAX_DATE_LENGTH + 1];
1317  qof_print_date_buff (date_buff, MAX_DATE_LENGTH, t64);
1318  gtk_label_set_text (GTK_LABEL(w), date_buff);
1319 
1320  o = G_OBJECT(dcal->transPopup);
1321  model = GTK_LIST_STORE(g_object_get_data (o, "model"));
1322  gtk_list_store_clear (model);
1323  for (l = dcal->marks[dcal->doc]; l; l = l->next)
1324  {
1325  GtkTreeIter iter;
1326  gdc_mark_data *gdcmd;
1327 
1328  gdcmd = (gdc_mark_data*)l->data;
1329  gtk_list_store_insert (model, &iter, INT_MAX);
1330  gtk_list_store_set (model, &iter, 0, (gdcmd->name ? gdcmd->name : _("(unnamed)")),
1331  1, gdcmd->info, -1);
1332  }
1333 
1334  // if there are no rows, add one
1335  if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL(model), NULL) == 0)
1336  {
1337  GtkTreeIter iter;
1338  gtk_list_store_insert (model, &iter, -1);
1339  }
1340 
1341  // make sure all pending events are processed
1342  while(gtk_events_pending ())
1343  gtk_main_iteration ();
1344 
1345  g_date_free (date);
1346  }
1347 }
1348 
1349 static const int POPUP_OFFSET = 5; // offset for popup window
1350 
1351 static void
1352 popup_window_move (GncDenseCal *dcal, GdkEvent *event)
1353 {
1354  GtkAllocation alloc;
1355  gdouble x_root, y_root;
1356  gint win_xpos, win_ypos;
1357 
1358  if (event->type == GDK_BUTTON_PRESS)
1359  {
1360  x_root = ((GdkEventButton*)event)->x_root;
1361  y_root = ((GdkEventButton*)event)->y_root;
1362  }
1363  else
1364  {
1365  x_root = ((GdkEventMotion*)event)->x_root;
1366  y_root = ((GdkEventMotion*)event)->y_root;
1367  }
1368  win_xpos = x_root + POPUP_OFFSET;
1369  win_ypos = y_root + POPUP_OFFSET;
1370 
1371  gtk_widget_get_allocation (GTK_WIDGET(dcal->transPopup), &alloc);
1372 
1373  if (x_root + POPUP_OFFSET + alloc.width > dcal->screen_width)
1374  win_xpos = x_root - 2 - alloc.width;
1375 
1376  if (y_root + POPUP_OFFSET + alloc.height > dcal->screen_height)
1377  win_ypos = y_root - 2 - alloc.height;
1378 
1379  gtk_window_move (GTK_WINDOW(dcal->transPopup), win_xpos, win_ypos);
1380 }
1381 
1382 static gint
1383 gnc_dense_cal_button_press (GtkWidget *widget,
1384  GdkEventButton *evt)
1385 {
1386  GdkWindow *win = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
1387  GdkMonitor *mon = gdk_display_get_monitor_at_window (gtk_widget_get_display (widget), win);
1388  GdkRectangle work_area_size;
1389  GncDenseCal *dcal = GNC_DENSE_CAL(widget);
1390 
1391  gdk_monitor_get_workarea (mon, &work_area_size);
1392 
1393  dcal->screen_width = work_area_size.width;
1394  dcal->screen_height = work_area_size.height;
1395 
1396  dcal->doc = wheres_this (dcal, evt->x, evt->y);
1397  dcal->showPopup = ~(dcal->showPopup);
1398  if (dcal->showPopup && dcal->doc >= 0)
1399  {
1400  // Do the move twice in case the WM is ignoring the first one
1401  // because the window hasn't been shown, yet. The WM is free
1402  // to ignore our move and place windows according to it's own
1403  // strategy, but hopefully it'll listen to us. Certainly the
1404  // second move after show_all'ing the window should do the
1405  // trick with a bit of flicker.
1406  gtk_window_move (GTK_WINDOW(dcal->transPopup), evt->x_root + POPUP_OFFSET,
1407  evt->y_root + POPUP_OFFSET);
1408 
1409  populate_hover_window (dcal);
1410  gtk_widget_queue_resize (GTK_WIDGET(dcal->transPopup));
1411  gtk_widget_show_all (GTK_WIDGET(dcal->transPopup));
1412 
1413  popup_window_move (dcal, (GdkEvent*)evt);
1414  }
1415  else
1416  {
1417  dcal->doc = -1;
1418  gtk_widget_hide (GTK_WIDGET(dcal->transPopup));
1419  }
1420  return TRUE;
1421 }
1422 
1423 static gint
1424 gnc_dense_cal_motion_notify (GtkWidget *widget,
1425  GdkEventMotion *event)
1426 {
1427  GncDenseCal *dcal;
1428  gint doc;
1429  int unused;
1430  GdkModifierType unused2;
1431 
1432  dcal = GNC_DENSE_CAL(widget);
1433  if (!dcal->showPopup)
1434  return FALSE;
1435 
1436  /* As per https://www.gtk.org/tutorial/sec-eventhandling.html */
1437  if (event->is_hint)
1438  {
1439  GdkSeat *seat = gdk_display_get_default_seat (gdk_window_get_display (event->window));
1440  GdkDevice *pointer = gdk_seat_get_pointer (seat);
1441 
1442  gdk_window_get_device_position (event->window, pointer, &unused, &unused, &unused2);
1443  }
1444 
1445  doc = wheres_this (dcal, event->x, event->y);
1446  if (doc >= 0)
1447  {
1448  if (dcal->doc != doc) // if we are on the same day, no need to reload
1449  {
1450  dcal->doc = doc;
1451  populate_hover_window (dcal);
1452  gtk_widget_queue_resize (GTK_WIDGET(dcal->transPopup));
1453  gtk_widget_show_all (GTK_WIDGET(dcal->transPopup));
1454  }
1455  popup_window_move (dcal, (GdkEvent*)event);
1456  }
1457  else
1458  {
1459  dcal->doc = -1;
1460  gtk_widget_hide (GTK_WIDGET(dcal->transPopup));
1461  }
1462  return TRUE;
1463 }
1464 
1465 
1466 static void
1467 _gdc_view_option_changed (GtkComboBox *widget, gpointer user_data)
1468 {
1469  GtkTreeIter iter;
1470  GtkTreeModel *model;
1471  gint months_val;
1472 
1473  model = GTK_TREE_MODEL(gtk_combo_box_get_model (widget));
1474  if (!gtk_combo_box_get_active_iter (widget, &iter))
1475  return;
1476  gtk_tree_model_get (model, &iter, VIEW_OPTS_COLUMN_NUM_MONTHS, &months_val, -1);
1477  DEBUG("changing to %d months", months_val);
1478  gnc_dense_cal_set_num_months (GNC_DENSE_CAL(user_data), months_val);
1479 }
1480 
1481 static inline int
1482 day_width_at (GncDenseCal *dcal, guint xScale)
1483 {
1484  return xScale + MINOR_BORDER_SIZE;
1485 }
1486 
1487 static inline int
1488 day_width (GncDenseCal *dcal)
1489 {
1490  return day_width_at (dcal, dcal->x_scale);
1491 }
1492 
1493 static inline int
1494 day_height_at (GncDenseCal *dcal, guint yScale)
1495 {
1496  return yScale + MINOR_BORDER_SIZE;
1497 }
1498 
1499 static inline int
1500 day_height (GncDenseCal *dcal)
1501 {
1502  return day_height_at (dcal, dcal->y_scale);
1503 }
1504 
1505 static inline int
1506 week_width_at (GncDenseCal *dcal, guint xScale)
1507 {
1508  return day_width_at (dcal, xScale) * 7;
1509 }
1510 
1511 static inline int
1512 week_width (GncDenseCal *dcal)
1513 {
1514  return week_width_at (dcal, dcal->x_scale);
1515 }
1516 
1517 static inline int
1518 week_height_at (GncDenseCal *dcal, guint yScale)
1519 {
1520  return day_height_at (dcal, yScale);
1521 }
1522 
1523 static inline int
1524 week_height (GncDenseCal *dcal)
1525 {
1526  return week_height_at (dcal, dcal->y_scale);
1527 }
1528 
1529 static inline int
1530 col_width_at (GncDenseCal *dcal, guint xScale)
1531 {
1532  return (week_width_at (dcal, xScale)
1533  + dcal->month_side_bar_width
1534  + COL_BORDER_SIZE);
1535 }
1536 
1537 static inline int
1538 col_width (GncDenseCal *dcal)
1539 {
1540  return col_width_at (dcal, dcal->x_scale);
1541 }
1542 
1543 static inline int
1544 col_height (GncDenseCal *dcal)
1545 {
1546  return week_height (dcal) * num_weeks_per_col (dcal);
1547 }
1548 
1549 static inline int
1550 num_cols (GncDenseCal *dcal)
1551 {
1552  return ceil ((float)dcal->numMonths / (float)dcal->monthsPerCol);
1553 }
1554 
1555 static inline int
1556 num_weeks (GncDenseCal *dcal)
1557 {
1558  return dcal->num_weeks;
1559 }
1560 
1561 static
1562 int num_weeks_per_col (GncDenseCal *dcal)
1563 {
1564  int num_weeks_toRet, numCols, i;
1565  GDate *start, *end;
1566  int startWeek, endWeek;
1567 
1568  start = g_date_new ();
1569  end = g_date_new ();
1570 
1571  num_weeks_toRet = 0;
1572  numCols = num_cols (dcal);
1573 
1574  for (i = 0; i < numCols; i++)
1575  {
1576  g_date_set_dmy (start, 1,
1577  ((dcal->month - 1
1578  + (i * dcal->monthsPerCol)) % 12)
1579  + 1,
1580  dcal->year + floor ((dcal->month - 1
1581  + (i * dcal->monthsPerCol))
1582  / 12));
1583  *end = *start;
1584  /* Add the smaller of (the number of months in the
1585  * calendar-display, minus the number of months shown in the
1586  * previous columns) or (the number of months in a column) */
1587  g_date_add_months (end, MIN(dcal->numMonths,
1588  MIN(dcal->monthsPerCol,
1589  dcal->numMonths
1590  - ((i - 1)
1591  * dcal->monthsPerCol))));
1592  g_date_subtract_days (end, 1);
1593  startWeek = get_week_of_year (dcal, start);
1594  endWeek = get_week_of_year (dcal, end);
1595 
1596  if (endWeek < startWeek)
1597  endWeek += get_weeks_in_year (dcal, g_date_get_year (start));
1598 
1599  num_weeks_toRet = MAX(num_weeks_toRet, (endWeek - startWeek) + 1);
1600  }
1601  g_date_free (start);
1602  g_date_free (end);
1603  return num_weeks_toRet;
1604 }
1605 
1612 static void
1613 month_coords (GncDenseCal *dcal, int monthOfCal, GList **outList)
1614 {
1615  gint weekRow, colNum, previousMonthsInCol, monthOffset;
1616  gint start;
1617  GDate *startD, *endD;
1618  GdkRectangle *rect;
1619  gint startWk, endWk;
1620 
1621  if (monthOfCal > dcal->numMonths)
1622  return;
1623 
1624  colNum = floor (monthOfCal / dcal->monthsPerCol);
1625  monthOffset = colNum * dcal->monthsPerCol;
1626  previousMonthsInCol = MAX(0, (monthOfCal % dcal->monthsPerCol));
1627 
1628  startD = g_date_new ();
1629  endD = g_date_new ();
1630 
1631  /* Calculate the number of weeks in the column before the month we're
1632  * interested in. */
1633  weekRow = 0;
1634  if (previousMonthsInCol > 0)
1635  {
1636  g_date_set_dmy (startD, 1,
1637  ((dcal->month - 1 + monthOffset) % 12) + 1,
1638  dcal->year + floor ((dcal->month - 1 + monthOffset) / 12));
1639  /* get the week of the top of the column */
1640  startWk = get_week_of_year (dcal, startD);
1641  /* get the week of the end of the previous months */
1642  *endD = *startD;
1643  g_date_add_months (endD, previousMonthsInCol);
1644  g_date_subtract_days (endD, 1);
1645  endWk = get_week_of_year (dcal, endD);
1646 
1647  if (endWk < startWk)
1648  endWk += get_weeks_in_year (dcal, g_date_get_year (startD));
1649 
1650  /* determine how many weeks are before the month we're
1651  * interested in. */
1652  weekRow = endWk - startWk;
1653 
1654  gint end_of_week = dcal->day_of_week_start + 6;
1655  if (end_of_week > 7)
1656  end_of_week = end_of_week - 7;
1657 
1658  if (g_date_get_weekday (endD) == end_of_week)
1659  weekRow++;
1660  }
1661 
1662  g_date_set_dmy (startD, 1,
1663  ((dcal->month - 1 + monthOfCal) % 12) + 1,
1664  dcal->year + floor ((dcal->month - 1 + monthOfCal) / 12));
1665 
1666  *endD = *startD;
1667  g_date_add_months (endD, 1);
1668  g_date_subtract_days (endD, 1);
1669 
1670  /* Get the first week. */
1671  {
1672  start = (g_date_get_weekday (startD) + 7 - dcal->day_of_week_start) % 7;
1673 
1674  rect = g_new0 (GdkRectangle, 1);
1675  rect->x = dcal->leftPadding
1676  + MINOR_BORDER_SIZE
1677  + (colNum * (col_width (dcal) + COL_BORDER_SIZE))
1678  + dcal->month_side_bar_width
1679  + (start * day_width (dcal));
1680  rect->y = dcal->topPadding
1681  + dcal->day_top_bar_height
1682  + MINOR_BORDER_SIZE
1683  + (weekRow * week_height (dcal));
1684  rect->width = (7 - start) * day_width (dcal);
1685  rect->height = week_height (dcal);
1686  *outList = g_list_append (*outList, (gpointer)rect);
1687  rect = NULL;
1688  }
1689 
1690  /* Get the middle weeks. */
1691  {
1692  gint i;
1693  gint weekStart = get_week_of_year (dcal, startD) + 1;
1694  gint weekEnd = get_week_of_year (dcal, endD);
1695 
1696  for (i = weekStart; i < weekEnd; i++)
1697  {
1698  rect = g_new0 (GdkRectangle, 1);
1699  rect->x = dcal->leftPadding
1700  + MINOR_BORDER_SIZE
1701  + dcal->month_side_bar_width
1702  + (colNum * (col_width (dcal) + COL_BORDER_SIZE));
1703  rect->y = dcal->topPadding
1704  + dcal->day_top_bar_height
1705  + MINOR_BORDER_SIZE
1706  + ((weekRow + (i - weekStart) + 1) * week_height (dcal));
1707  rect->width = week_width (dcal);
1708  rect->height = week_height (dcal);
1709 
1710  *outList = g_list_append (*outList, (gpointer)rect);
1711  rect = NULL;
1712  }
1713  }
1714 
1715  /* Get the last week. */
1716  {
1717  gint start_week_of_year = get_week_of_year (dcal, startD);
1718  gint end_week_of_year = get_week_of_year (dcal, endD);
1719 
1720  rect = g_new0 (GdkRectangle, 1);
1721  rect->x = dcal->leftPadding
1722  + MINOR_BORDER_SIZE
1723  + dcal->month_side_bar_width
1724  + (colNum * (col_width (dcal) + COL_BORDER_SIZE));
1725  rect->y = dcal->topPadding
1726  + MINOR_BORDER_SIZE
1727  + dcal->day_top_bar_height
1728  + ((weekRow
1729  + (end_week_of_year - start_week_of_year))
1730  * week_height (dcal));
1731  rect->width = (((g_date_get_weekday (endD) + 7 - dcal->day_of_week_start) % 7) + 1) * day_width (dcal);
1732  rect->height = week_height (dcal);
1733 
1734  *outList = g_list_append (*outList, (gpointer)rect);
1735  rect = NULL;
1736  }
1737 
1738  g_date_free (startD);
1739  g_date_free (endD);
1740 }
1741 
1742 /* FIXME: make this more like month_coords */
1743 static void
1744 doc_coords (GncDenseCal *dcal, int dayOfCal,
1745  int *x1, int *y1, int *x2, int *y2)
1746 {
1747  GDate d;
1748  gint docMonth;
1749  gint d_week_of_cal, top_of_col_week_of_cal;
1750  gint colNum, dayCol, weekRow;
1751 
1752  /* FIXME: add range checks */
1753  g_date_set_dmy (&d, 1, dcal->month, dcal->year);
1754  g_date_add_days (&d, dayOfCal);
1755  docMonth = g_date_get_month (&d);
1756  if (g_date_get_year (&d) != dcal->year)
1757  {
1758  docMonth += 12;
1759  }
1760  colNum = floor ((float)(docMonth - dcal->month) / (float)dcal->monthsPerCol);
1761  dayCol = g_date_get_weekday (&d) - dcal->day_of_week_start;
1762 
1763  if (dayCol < 0)
1764  dayCol = dayCol + 7;
1765 
1766  d_week_of_cal = get_week_of_year (dcal, &d);
1767  g_date_set_dmy (&d, 1, dcal->month, dcal->year);
1768  g_date_add_months (&d, (colNum * dcal->monthsPerCol));
1769  top_of_col_week_of_cal = get_week_of_year (dcal, &d);
1770 
1771  if (d_week_of_cal < top_of_col_week_of_cal)
1772  {
1773  gint week_offset = get_weeks_in_year (dcal, dcal->year);
1774  d_week_of_cal += week_offset;
1775  }
1776  weekRow = d_week_of_cal - top_of_col_week_of_cal;
1777 
1778  /* top-left corner */
1779  /* FIXME: this has the math to make the mark-cells come out right,
1780  * which it shouldn't. */
1781  *x1 = dcal->leftPadding
1782  + MINOR_BORDER_SIZE
1783  + dcal->month_side_bar_width
1784  + (colNum * (col_width (dcal) + COL_BORDER_SIZE))
1785  + (dayCol * day_width (dcal))
1786  + (day_width (dcal) / 4);
1787  *y1 = dcal->topPadding
1788  + MINOR_BORDER_SIZE
1789  + dcal->day_top_bar_height
1790  + (weekRow * week_height (dcal))
1791  + (day_height (dcal) / 4);
1792 
1793  *x2 = *x1 + (day_width (dcal) / 2);
1794  *y2 = *y1 + (day_height (dcal) / 2);
1795 }
1796 
1801 static gint
1802 wheres_this (GncDenseCal *dcal, int x, int y)
1803 {
1804  gint colNum, weekRow, dayCol, dayOfCal;
1805  GDate d, startD;
1806  GtkAllocation alloc;
1807 
1808  x -= dcal->leftPadding;
1809  y -= dcal->topPadding;
1810 
1811  if ((x < 0) || (y < 0))
1812  {
1813  return -1;
1814  }
1815  gtk_widget_get_allocation (GTK_WIDGET(dcal), &alloc);
1816  if ((x >= alloc.width)
1817  || (y >= alloc.height))
1818  {
1819  return -1;
1820  }
1821 
1822  /* "outside of displayed table" check */
1823  if (x >= (num_cols(dcal) * (col_width (dcal) + COL_BORDER_SIZE)))
1824  {
1825  return -1;
1826  }
1827  if (y >= dcal->day_top_bar_height + col_height (dcal))
1828  {
1829  return -1;
1830  }
1831 
1832  /* coords -> year-relative-values */
1833  colNum = floor (x / (col_width (dcal) + COL_BORDER_SIZE));
1834 
1835  x %= (col_width (dcal) + COL_BORDER_SIZE);
1836  x -= dcal->month_side_bar_width;
1837  if (x < 0)
1838  {
1839  return -1;
1840  }
1841  if (x >= day_width (dcal) * 7)
1842  {
1843  return -1;
1844  }
1845 
1846  y -= dcal->day_top_bar_height;
1847  if (y < 0)
1848  {
1849  return -1;
1850  }
1851 
1852  dayCol = floor ((float)x / (float)day_width (dcal));
1853  weekRow = floor ((float)y / (float)week_height (dcal));
1854 
1855  g_date_set_dmy (&startD, 1, dcal->month, dcal->year);
1856  d = startD;
1857  g_date_add_months (&d, (colNum * dcal->monthsPerCol));
1858 
1859  if (dcal->day_of_week_start == G_DATE_SUNDAY)
1860  dayCol -= (g_date_get_weekday (&d) - 0) % 7;
1861  else
1862  dayCol -= (g_date_get_weekday (&d) - 1) % 7;
1863 
1864  if (weekRow == 0)
1865  {
1866  if (dayCol < 0)
1867  {
1868  return -1;
1869  }
1870  }
1871  g_date_add_days (&d, dayCol + (weekRow * 7));
1872 
1873  /* Check to make sure we're within the column's displayed range. */
1874  {
1875  GDate ccd;
1876  g_date_set_dmy (&ccd, 1, dcal->month, dcal->year);
1877  g_date_add_months (&ccd, (colNum + 1) * dcal->monthsPerCol);
1878  if (g_date_get_julian (&d) >= g_date_get_julian (&ccd))
1879  {
1880  return -1;
1881  }
1882  }
1883 
1884  dayOfCal = g_date_get_julian (&d) - g_date_get_julian (&startD);
1885 
1886  /* one more check before returning... */
1887  g_date_subtract_months (&d, dcal->numMonths);
1888  if (g_date_get_julian (&d) >= g_date_get_julian (&startD))
1889  {
1890  /* we're past the end of the displayed calendar, thus -1 */
1891  DEBUG("%d >= %d", g_date_get_julian (&d), g_date_get_julian (&startD));
1892  return -1;
1893  }
1894 
1895  return dayOfCal;
1896 }
1897 
1898 static gint
1899 gdc_get_doc_offset (GncDenseCal *dcal, GDate *d)
1900 {
1901  gint toRet;
1902  /* soc == start-of-calendar */
1903  GDate soc;
1904 
1905  g_date_clear (&soc, 1);
1906  g_date_set_dmy (&soc, 1, dcal->month, dcal->year);
1907  /* ensure not before calendar start. */
1908  if (g_date_get_julian (d) < g_date_get_julian (&soc))
1909  return -1;
1910  /* do computation here, since we're going to change the
1911  * start-of-calendar date. */
1912  toRet = g_date_get_julian (d) - g_date_get_julian (&soc);
1913  /* ensure not after end of visible calendar. */
1914  g_date_add_months (&soc, dcal->numMonths);
1915  if (g_date_get_julian (d) >= g_date_get_julian (&soc))
1916  return -1;
1917  /* return pre-computed value. */
1918  return toRet;
1919 }
1920 
1921 static void
1922 gdc_add_tag_markings (GncDenseCal *cal, guint tag)
1923 {
1924  gchar *name, *info;
1925  gint num_marks, idx;
1926  GDate **dates;
1927  GDate *calDate;
1928 
1929  // copy the values into the old marking function.
1930  name = gnc_dense_cal_model_get_name (cal->model, tag);
1931  info = gnc_dense_cal_model_get_info (cal->model, tag);
1932  num_marks = gnc_dense_cal_model_get_instance_count (cal->model, tag);
1933 
1934  if (num_marks == 0)
1935  goto cleanup;
1936 
1937  dates = g_new0 (GDate*, num_marks);
1938  calDate = g_date_new_dmy (1, cal->month, cal->year);
1939 
1940  for (idx = 0; idx < num_marks; idx++)
1941  {
1942  dates[idx] = g_date_new ();
1943  gnc_dense_cal_model_get_instance (cal->model, tag, idx, dates[idx]);
1944 
1945  }
1946  if (g_date_valid (dates[0]))
1947  {
1948  if (g_date_get_julian (dates[0]) < g_date_get_julian (calDate))
1949  {
1950  /* Oops, first marking is earlier than months displayed.
1951  * Choose new first month and recalculate all markings for all
1952  * tags. Their offsets are all wrong with the newly added month(s).
1953  */
1954  _gnc_dense_cal_set_month (cal, g_date_get_month (dates[0]), FALSE);
1955  _gnc_dense_cal_set_year (cal, g_date_get_year (dates[0]), FALSE);
1956 
1957  gdc_remove_markings (cal);
1958  gdc_add_markings (cal);
1959  }
1960  else
1961  gdc_mark_add (cal, tag, name, info, num_marks, dates);
1962  }
1963  else
1964  {
1965  g_warning ("Bad date, skipped.");
1966  }
1967 
1968  for (idx = 0; idx < num_marks; idx++)
1969  {
1970  g_date_free (dates[idx]);
1971  }
1972  g_free (dates);
1973  g_date_free (calDate);
1974 
1975 cleanup:
1976  g_free (info);
1977 }
1978 
1979 static void
1980 gdc_add_markings (GncDenseCal *cal)
1981 {
1982  GList *tags = gnc_dense_cal_model_get_contained (cal->model);
1983 
1984  for (GList *n = tags; n; n = n->next)
1985  gdc_add_tag_markings (cal, GPOINTER_TO_UINT(n->data));
1986 
1987  g_list_free (tags);
1988 }
1989 
1990 static void
1991 gdc_remove_markings (GncDenseCal *cal)
1992 {
1993  GList *tags = gnc_dense_cal_model_get_contained (cal->model);
1994 
1995  for (GList *n = tags; n; n = n->next)
1996  gdc_mark_remove (cal, GPOINTER_TO_UINT(n->data), FALSE);
1997 
1998  g_list_free (tags);
1999 }
2000 
2001 static void
2002 gdc_model_added_cb (GncDenseCalModel *model, guint added_tag, gpointer user_data)
2003 {
2004  GncDenseCal *cal = GNC_DENSE_CAL(user_data);
2005  DEBUG("gdc_model_added_cb update");
2006  gdc_add_tag_markings (cal, added_tag);
2007 }
2008 
2009 static void
2010 gdc_model_update_cb (GncDenseCalModel *model, guint update_tag, gpointer user_data)
2011 {
2012  GncDenseCal *cal = GNC_DENSE_CAL(user_data);
2013  gint num_marks = 0;
2014  DEBUG("gdc_model_update_cb update for tag [%d]", update_tag);
2015  num_marks = gnc_dense_cal_model_get_instance_count (cal->model, update_tag);
2016  // We need to redraw if there are no mark, to ensure they're all erased.
2017  gdc_mark_remove (cal, update_tag, num_marks==0);
2018  gdc_add_tag_markings (cal, update_tag);
2019 
2020 }
2021 
2022 static void
2023 gdc_model_removing_cb (GncDenseCalModel *model, guint remove_tag, gpointer user_data)
2024 {
2025  GncDenseCal *cal = GNC_DENSE_CAL(user_data);
2026  DEBUG("gdc_model_removing_cb update [%d]", remove_tag);
2027  gdc_mark_remove (cal, remove_tag, TRUE);
2028 }
2029 
2030 void
2031 gnc_dense_cal_set_model (GncDenseCal *cal, GncDenseCalModel *model)
2032 {
2033  if (cal->model != NULL)
2034  {
2035  gdc_remove_markings (cal);
2036  g_object_unref (G_OBJECT(cal->model));
2037  cal->model = NULL;
2038  }
2039  cal->model = model;
2040  g_object_ref (G_OBJECT(model));
2041  g_signal_connect (G_OBJECT(cal->model), "added", (GCallback)gdc_model_added_cb, cal);
2042  g_signal_connect (G_OBJECT(cal->model), "update", (GCallback)gdc_model_update_cb, cal);
2043  g_signal_connect (G_OBJECT(cal->model), "removing", (GCallback)gdc_model_removing_cb, cal);
2044 
2045  gdc_add_markings (cal);
2046 }
2047 
2051 static void
2052 gdc_mark_add (GncDenseCal *dcal,
2053  guint tag,
2054  gchar *name,
2055  gchar *info,
2056  guint size,
2057  GDate **dateArray)
2058 {
2059  guint i;
2060  gint doc;
2061  gdc_mark_data *newMark;
2062  GDate *d;
2063 
2064  if (size == 0)
2065  {
2066  g_error ("0 size not allowed");
2067  return;
2068  }
2069 
2070  newMark = g_new0 (gdc_mark_data, 1);
2071  newMark->name = NULL;
2072  if (name)
2073  newMark->name = g_strdup (name);
2074  newMark->info = NULL;
2075  if (info)
2076  newMark->info = g_strdup (info);
2077  newMark->tag = tag;
2078  newMark->ourMarks = NULL;
2079  DEBUG("saving mark with tag [%d]", newMark->tag);
2080 
2081  for (i = 0; i < size; i++)
2082  {
2083  d = dateArray[i];
2084  doc = gdc_get_doc_offset (dcal, d);
2085  if (doc < 0)
2086  continue;
2087  if (doc >= dcal->numMarks)
2088  {
2089  /* It's not going to get any better, so just
2090  * stop processing. */
2091  break;
2092  }
2093  dcal->marks[doc] = g_list_append (dcal->marks[doc], newMark);
2094  newMark->ourMarks = g_list_append (newMark->ourMarks,
2095  GINT_TO_POINTER(doc));
2096  }
2097  dcal->markData = g_list_append (dcal->markData, (gpointer)newMark);
2098  gnc_dense_cal_draw_to_buffer (dcal);
2099  gtk_widget_queue_draw (GTK_WIDGET(dcal->cal_drawing_area));
2100 }
2101 
2102 static void
2103 gdc_mark_remove (GncDenseCal *dcal, guint mark_to_remove, gboolean redraw)
2104 {
2105  GList *iter, *calendar_marks;
2106  gint day_of_cal;
2107  gdc_mark_data *mark_data;
2108 
2109  /* Ignore non-realistic marks */
2110  if ((gint)mark_to_remove == -1)
2111  {
2112  DEBUG("mark_to_remove = -1");
2113  return;
2114  }
2115 
2116  mark_data = NULL;
2117  for (iter = dcal->markData; iter != NULL; iter = iter->next)
2118  {
2119  mark_data = (gdc_mark_data*)iter->data;
2120  if (mark_data->tag == mark_to_remove)
2121  break;
2122  }
2123  if (iter == NULL)
2124  {
2125  PINFO("couldn't find tag [%d]", mark_to_remove);
2126  return;
2127  }
2128  if (mark_data == NULL)
2129  {
2130  DEBUG("mark_data == null");
2131  return;
2132  }
2133 
2134  for (calendar_marks = mark_data->ourMarks; calendar_marks != NULL; calendar_marks = calendar_marks->next)
2135  {
2136  day_of_cal = GPOINTER_TO_INT(calendar_marks->data);
2137  dcal->marks[day_of_cal] = g_list_remove (dcal->marks[day_of_cal], mark_data);
2138  }
2139  g_list_free (mark_data->ourMarks);
2140  dcal->markData = g_list_remove (dcal->markData, mark_data);
2141  g_free (mark_data->name);
2142  g_free (mark_data->info);
2143  g_free (mark_data);
2144 
2145  if (redraw)
2146  {
2147  gnc_dense_cal_draw_to_buffer (dcal);
2148  gtk_widget_queue_draw (GTK_WIDGET(dcal->cal_drawing_area));
2149  }
2150 }
Date and Time handling routines.
time64 gnc_dmy2time64_neutral(gint day, gint month, gint year)
Converts a day, month, and year to a time64 representing 11:00:00 UTC 11:00:00 UTC falls on the same ...
#define PINFO(format, args...)
Print an informational note.
Definition: qoflog.h:256
gtk helper routines.
#define DEBUG(format, args...)
Print a debugging message.
Definition: qoflog.h:264
void gnc_gdate_set_today(GDate *gd)
Set a GDate to the current day.
Definition: gnc-date.cpp:1236
gboolean gnc_is_dark_theme(GdkRGBA *fg_color)
Return whether the current gtk theme is a dark one.
void gnc_style_context_get_border_color(GtkStyleContext *context, GtkStateFlags state, GdkRGBA *color)
Wrapper to get the border color of a widget for a given state.
void gnc_dow_abbrev(gchar *buf, int buf_len, int dow)
Localized DOW abbreviation.
Definition: gnc-date.cpp:1365
GList * ourMarks
A GList of the dcal->marks indexes containing this mark.
GList * markData
A GList of gdc_mark_data structs, one for each active/valid markTag.
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition: gnc-date.h:108
All type declarations for the whole Gnucash engine.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition: gnc-date.h:87
gint gnc_start_of_week(void)
returns an integer corresponding to locale start of week
Definition: gnc-date.cpp:194
size_t qof_print_date_buff(char *buff, size_t buflen, time64 secs)
Convenience: calls through to qof_print_date_dmy_buff().
Definition: gnc-date.cpp:573