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 = G_LOG_DOMAIN;
39
40 /**
41 * Marking ...
42 *
43 * We want a facility to mark multiple days on the calendar. This facility
44 * should be efficient in display. It will take an array+count of GDates on
45 * which to mark the calendar. Dates outside of the visible calendar range
46 * will be ignored.
47 *
48 * Markings will be manipulated in tagged sets for markings related to the
49 * same event; in order to efficiently process these sets [removal,
50 * primarily], we will keep a multiple data structures.
51 *
52 *
53 * We need to be able to perform the following actions:
54 * . Add a new mark-set, returning a calendar-unique tag.
55 * . Remove a mark-set by tag.
56 * . Iterate over all days in the calendar, listing which markings are active
57 * on that day.
58 *
59 * The markings in the calendar will be internally represented as an array of
60 * GLists, with each item in the list pointing to a gdc_mark_data structure.
61 * The gdc_mark_data structures will contain:
62 * . the external/caller marker tag
63 * . the marker indication [color, when supported]
64 * . a GList of all instances of the marker in the visible calendar, by
65 * 'marks' index.
66 *
67 * The list of gdc_mark_data structures itself will be a top-level list in the
68 * GncDenseCal structure.
69 **/
70
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 #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_draw(GtkWidget *widget, cairo_t *cr, 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
120 /**
121 * Returns the total number of weeks to display in the calendar [irrespective
122 * of columns/weeks-per-col].
123 **/
124 static inline int num_weeks(GncDenseCal *dcal);
125 /**
126 * Returns the number of weeks per column. Note that this is the number of
127 * weeks needed to display the longest column.
128 **/
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);
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*
month_name(int mon)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
day_label(gchar * buf,int buf_len,int dow)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
gnc_dense_cal_get_type()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_BOX,
212 "GncDenseCal",
213 &dense_cal_info, 0);
214 }
215
216 return dense_cal_type;
217 }
218
219 static void
gnc_dense_cal_class_init(GncDenseCalClass * klass)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 gtk_widget_class_set_css_name (GTK_WIDGET_CLASS(klass), "calendar");
229
230 parent_class = g_type_class_peek_parent (klass);
231
232 object_class->finalize = gnc_dense_cal_finalize;
233 object_class->dispose = gnc_dense_cal_dispose;
234
235 widget_class->motion_notify_event = gnc_dense_cal_motion_notify;
236 widget_class->button_press_event = gnc_dense_cal_button_press;
237 }
238
239 enum _GdcViewOptsColumns
240 {
241 VIEW_OPTS_COLUMN_LABEL = 0,
242 VIEW_OPTS_COLUMN_NUM_MONTHS
243 };
244
245 static GtkListStore *_cal_view_options = NULL;
246 static GtkListStore*
_gdc_get_view_options(void)247 _gdc_get_view_options(void)
248 {
249 if (_cal_view_options == NULL)
250 {
251 _cal_view_options = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_INT);
252 gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("12 months"), 1, 12, -1);
253 gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("6 months"), 1, 6, -1);
254 gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("4 months"), 1, 4, -1);
255 gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("3 months"), 1, 3, -1);
256 gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("2 months"), 1, 2, -1);
257 gtk_list_store_insert_with_values(_cal_view_options, NULL, G_MAXINT, 0, _("1 month"), 1, 1, -1);
258 }
259
260 return _cal_view_options;
261 }
262
263 static void
gnc_dense_cal_init(GncDenseCal * dcal)264 gnc_dense_cal_init(GncDenseCal *dcal)
265 {
266 GtkStyleContext *context = gtk_widget_get_style_context (GTK_WIDGET(dcal));
267
268 gtk_orientable_set_orientation (GTK_ORIENTABLE(dcal), GTK_ORIENTATION_VERTICAL);
269
270 // Set the style context for this widget so it can be easily manipulated with css
271 gnc_widget_style_context_add_class (GTK_WIDGET(dcal), "calendar");
272
273 // Set the name of this widget so it can be easily manipulated with css
274 gtk_widget_set_name (GTK_WIDGET(dcal), "gnc-id-dense-calendar");
275
276 gtk_style_context_add_class (context, GTK_STYLE_CLASS_CALENDAR);
277 {
278 GtkTreeModel *options;
279 GtkCellRenderer *text_rend;
280
281 options = GTK_TREE_MODEL(_gdc_get_view_options());
282 dcal->view_options = GTK_COMBO_BOX(gtk_combo_box_new_with_model(options));
283 gtk_combo_box_set_active(GTK_COMBO_BOX(dcal->view_options), 0);
284 text_rend = GTK_CELL_RENDERER(gtk_cell_renderer_text_new());
285 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(dcal->view_options), text_rend, TRUE);
286 gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(dcal->view_options),
287 text_rend, "text", VIEW_OPTS_COLUMN_LABEL);
288 g_signal_connect(G_OBJECT(dcal->view_options), "changed", G_CALLBACK(_gdc_view_option_changed), (gpointer)dcal);
289 }
290
291 {
292 GtkWidget *hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0);
293 GtkWidget *label = gtk_label_new (_("View"));
294
295 gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
296 gtk_widget_set_halign (label, GTK_ALIGN_END);
297 gtk_widget_set_margin_end (label, 5);
298 gtk_box_pack_start(GTK_BOX(hbox), label, TRUE, TRUE, 0);
299 gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(dcal->view_options), FALSE, FALSE, 0);
300
301 gtk_box_pack_start(GTK_BOX(dcal), GTK_WIDGET(hbox), FALSE, FALSE, 0);
302 }
303 dcal->cal_drawing_area = GTK_DRAWING_AREA(gtk_drawing_area_new());
304
305 gtk_widget_add_events(GTK_WIDGET(dcal->cal_drawing_area), (GDK_EXPOSURE_MASK
306 | GDK_BUTTON_PRESS_MASK
307 | GDK_BUTTON_RELEASE_MASK
308 | GDK_POINTER_MOTION_MASK
309 | GDK_POINTER_MOTION_HINT_MASK));
310 gtk_box_pack_start(GTK_BOX(dcal), GTK_WIDGET(dcal->cal_drawing_area), TRUE, TRUE, 0);
311 g_signal_connect(G_OBJECT(dcal->cal_drawing_area), "draw", G_CALLBACK(gnc_dense_cal_draw), (gpointer)dcal);
312 g_signal_connect(G_OBJECT(dcal->cal_drawing_area), "realize", G_CALLBACK(gnc_dense_cal_realize), (gpointer)dcal);
313 g_signal_connect(G_OBJECT(dcal->cal_drawing_area), "configure_event", G_CALLBACK(gnc_dense_cal_configure), (gpointer)dcal);
314
315 dcal->disposed = FALSE;
316 dcal->initialized = FALSE;
317 dcal->markData = NULL;
318 dcal->numMarks = 0;
319 dcal->marks = NULL;
320 dcal->lastMarkTag = 0;
321
322 dcal->showPopup = FALSE;
323
324 dcal->transPopup = GTK_WINDOW(gtk_window_new(GTK_WINDOW_POPUP));
325 {
326 GtkWidget *vbox, *hbox;
327 GtkWidget *l;
328 GtkListStore *tree_data;
329 GtkTreeView *tree_view;
330
331 vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 5);
332 gtk_box_set_homogeneous (GTK_BOX (vbox), FALSE);
333 hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5);
334 gtk_box_set_homogeneous (GTK_BOX (hbox), FALSE);
335
336 gtk_widget_set_name (GTK_WIDGET(dcal->transPopup), "gnc-id-dense-calendar-popup");
337
338 l = gtk_label_new(_("Date: "));
339 gtk_widget_set_margin_start (l, 5);
340 gtk_container_add(GTK_CONTAINER(hbox), l);
341 l = gtk_label_new("YY/MM/DD");
342 g_object_set_data(G_OBJECT(dcal->transPopup), "dateLabel", l);
343 gtk_container_add(GTK_CONTAINER(hbox), l);
344 gtk_container_add(GTK_CONTAINER(vbox), hbox);
345
346 gtk_container_add(GTK_CONTAINER(vbox), gtk_separator_new (GTK_ORIENTATION_HORIZONTAL));
347
348 tree_data = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
349 tree_view = GTK_TREE_VIEW(gtk_tree_view_new_with_model(GTK_TREE_MODEL(tree_data)));
350 gtk_tree_view_insert_column_with_attributes(tree_view, -1, _("Name"), gtk_cell_renderer_text_new(), "text", 0, NULL);
351 gtk_tree_view_insert_column_with_attributes(tree_view, -1, _("Frequency"), gtk_cell_renderer_text_new(), "text", 1, NULL);
352 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (GTK_TREE_VIEW(tree_view)), GTK_SELECTION_NONE);
353 g_object_set_data(G_OBJECT(dcal->transPopup), "model", tree_data);
354 g_object_unref (tree_data);
355 gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(tree_view));
356
357 gtk_container_add(GTK_CONTAINER(dcal->transPopup), vbox);
358
359 gtk_window_set_resizable(GTK_WINDOW(dcal->transPopup), FALSE);
360
361 gtk_widget_realize(GTK_WIDGET(dcal->transPopup));
362 }
363
364 /* Deal with the various label sizes. */
365 {
366 PangoLayout *layout = gtk_widget_create_pango_layout(GTK_WIDGET(dcal), NULL);
367 GtkStyleContext *stylectxt = gtk_widget_get_style_context (GTK_WIDGET(dcal));
368 GtkStateFlags state_flags = gtk_style_context_get_state (stylectxt);
369 gint font_size_reduction_units = 1;
370 PangoFontDescription *font_desc;
371 GtkCssProvider *provider;
372 gint font_size, px_size;
373 gint i;
374 gint maxWidth, maxHeight;
375 gchar *px_str, *widget_css;
376 gdouble dpi;
377
378 gtk_style_context_get (stylectxt, state_flags,
379 GTK_STYLE_PROPERTY_FONT, &font_desc, NULL);
380 font_size = pango_font_description_get_size(font_desc);
381
382 provider = gtk_css_provider_new();
383 dpi = gdk_screen_get_resolution (gdk_screen_get_default ());
384 px_size = ((font_size / PANGO_SCALE) - font_size_reduction_units) * (dpi / 72.);
385 px_str = g_strdup_printf("%i", px_size);
386 widget_css = g_strconcat ("*{\n font-size:", px_str, "px;\n}\n", NULL);
387
388 gtk_css_provider_load_from_data (provider, widget_css, -1, NULL);
389 gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER (provider),
390 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
391 g_object_unref (provider);
392 g_free (px_str);
393 g_free (widget_css);
394
395 pango_font_description_free (font_desc);
396
397 maxWidth = maxHeight = 0;
398 for (i = 0; i < 12; i++)
399 {
400 gint w, h;
401 pango_layout_set_text(layout, month_name(i), -1);
402 pango_layout_get_pixel_size(layout, &w, &h);
403 maxWidth = MAX(maxWidth, w);
404 maxHeight = MAX(maxHeight, h);
405 }
406
407 // these two were reversed, before...
408 dcal->label_width = maxWidth;
409 dcal->label_height = maxHeight;
410
411 g_object_unref(layout);
412 }
413
414 dcal->month = G_DATE_JANUARY;
415 dcal->year = 1970;
416
417 dcal->numMonths = 12;
418 dcal->monthsPerCol = 3;
419 dcal->leftPadding = 4;
420 dcal->topPadding = 4;
421
422 {
423 GDate now;
424 g_date_clear (&now, 1);
425 gnc_gdate_set_today (&now);
426 _gnc_dense_cal_set_month(dcal, g_date_get_month(&now), FALSE);
427 _gnc_dense_cal_set_year(dcal, g_date_get_year(&now), FALSE);
428 }
429
430 recompute_extents(dcal);
431 recompute_mark_storage(dcal);
432
433 /* Compute initial scaling factors; will be increased when we're
434 * allocated enough space to scale up. */
435 {
436 PangoLayout *layout;
437 int width_88, height_88;
438 int width_XXX, height_XXX;
439
440 layout = gtk_widget_create_pango_layout(GTK_WIDGET(dcal), NULL);
441
442 pango_layout_set_text(layout, "88", -1);
443 pango_layout_get_pixel_size(layout, &width_88, &height_88);
444
445 pango_layout_set_text(layout, "XXX", -1);
446 pango_layout_get_pixel_size(layout, &width_XXX, &height_XXX);
447
448 dcal->min_x_scale = dcal->x_scale = width_88 + 2;
449 dcal->min_y_scale = dcal->y_scale = MAX(floor((float)width_XXX / 3.), height_88 + 2);
450
451 dcal->dayLabelHeight = height_88;
452
453 g_object_unref(layout);
454 }
455
456 dcal->initialized = TRUE;
457
458
459 dcal->week_starts_monday = 0;
460 {
461 gchar **parts;
462 const char *week_start_str;
463
464 /* Use this renaming macro to avoid extraction of the message
465 string into the gnucash.pot file when calling xgettext. */
466 #define dgettext_noextract dgettext
467 /* Translators: This string must not show up in gnucash.pot as
468 it is looked up in the "gtk20" translation domain
469 instead. */
470 week_start_str = dgettext_noextract("gtk20", "calendar:week_start:0");
471 #undef dgettext_noextract
472
473 parts = g_strsplit(week_start_str, ":", 3);
474 if (parts[0] != NULL
475 && parts[1] != NULL
476 && parts[2] != NULL)
477 {
478 if (strcmp("1", parts[2]) == 0)
479 dcal->week_starts_monday = 1;
480 }
481 g_strfreev(parts);
482 }
483
484 gtk_widget_show_all(GTK_WIDGET(dcal));
485 }
486
487 static void
_gdc_set_cal_min_size_req(GncDenseCal * dcal)488 _gdc_set_cal_min_size_req(GncDenseCal *dcal)
489 {
490 guint min_width, min_height;
491
492 _gdc_compute_min_size(dcal, &min_width, &min_height);
493 gtk_widget_set_size_request(GTK_WIDGET(dcal->cal_drawing_area), min_width, min_height);
494 }
495
496 GtkWidget*
gnc_dense_cal_new(void)497 gnc_dense_cal_new(void)
498 {
499 GncDenseCal *dcal;
500 dcal = g_object_new(GNC_TYPE_DENSE_CAL, NULL);
501 return GTK_WIDGET(dcal);
502 }
503
504 GtkWidget*
gnc_dense_cal_new_with_model(GncDenseCalModel * model)505 gnc_dense_cal_new_with_model(GncDenseCalModel *model)
506 {
507 GncDenseCal *cal = GNC_DENSE_CAL(gnc_dense_cal_new());
508 gnc_dense_cal_set_model(cal, model);
509 return GTK_WIDGET(cal);
510 }
511
512 static void
recompute_first_of_month_offset(GncDenseCal * dcal)513 recompute_first_of_month_offset(GncDenseCal *dcal)
514 {
515 GDate *tmpDate;
516
517 tmpDate = g_date_new_dmy(1, dcal->month, dcal->year);
518 dcal->firstOfMonthOffset = g_date_get_weekday(tmpDate) % 7;
519 g_date_free(tmpDate);
520 }
521
522 void
gnc_dense_cal_set_month(GncDenseCal * dcal,GDateMonth mon)523 gnc_dense_cal_set_month(GncDenseCal *dcal, GDateMonth mon)
524 {
525 _gnc_dense_cal_set_month(dcal, mon, TRUE);
526 }
527
528 static void
_gnc_dense_cal_set_month(GncDenseCal * dcal,GDateMonth mon,gboolean redraw)529 _gnc_dense_cal_set_month(GncDenseCal *dcal, GDateMonth mon, gboolean redraw)
530 {
531 GTimer *t = g_timer_new();
532 if (dcal->month == mon)
533 return;
534 dcal->month = mon;
535 g_timer_start(t);
536 recompute_first_of_month_offset(dcal);
537 DEBUG("recompute_first_of_month_offset: %f", g_timer_elapsed(t, NULL) * 1000.);
538 g_timer_start(t);
539 recompute_extents(dcal);
540 DEBUG("recompute_extents: %f", g_timer_elapsed(t, NULL) * 1000.);
541 if (redraw && gtk_widget_get_realized(GTK_WIDGET(dcal)))
542 {
543 g_timer_start(t);
544 recompute_x_y_scales(dcal);
545 DEBUG("recompute_x_y_scales: %f", g_timer_elapsed(t, NULL) * 1000.);
546 g_timer_start(t);
547 gnc_dense_cal_draw_to_buffer(dcal);
548 DEBUG("draw_to_buffer: %f", g_timer_elapsed(t, NULL) * 1000.);
549 g_timer_start(t);
550 gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
551 DEBUG("queue_draw: %f", g_timer_elapsed(t, NULL) * 1000.);
552 }
553 g_timer_stop(t);
554 g_timer_destroy(t);
555 }
556
557 void
gnc_dense_cal_set_year(GncDenseCal * dcal,guint year)558 gnc_dense_cal_set_year(GncDenseCal *dcal, guint year)
559 {
560 _gnc_dense_cal_set_year(dcal, year, TRUE);
561 }
562
563 static void
_gnc_dense_cal_set_year(GncDenseCal * dcal,guint year,gboolean redraw)564 _gnc_dense_cal_set_year(GncDenseCal *dcal, guint year, gboolean redraw)
565 {
566 if (dcal->year == year)
567 return;
568 dcal->year = year;
569 recompute_first_of_month_offset(dcal);
570 recompute_extents(dcal);
571 if (redraw && gtk_widget_get_realized(GTK_WIDGET(dcal)))
572 {
573 recompute_x_y_scales(dcal);
574 gnc_dense_cal_draw_to_buffer(dcal);
575 gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
576 }
577 }
578
579 void
gnc_dense_cal_set_num_months(GncDenseCal * dcal,guint num_months)580 gnc_dense_cal_set_num_months(GncDenseCal *dcal, guint num_months)
581 {
582 {
583 GtkListStore *options = _gdc_get_view_options();
584 GtkTreeIter view_opts_iter, iter_closest_to_req;
585 int closest_index_distance = G_MAXINT;
586
587 // find closest list value to num_months
588 if (!gtk_tree_model_get_iter_first(GTK_TREE_MODEL(options), &view_opts_iter))
589 {
590 g_critical("no view options?");
591 return;
592 }
593
594 do
595 {
596 gint months_val, delta_months;
597
598 gtk_tree_model_get(GTK_TREE_MODEL(options), &view_opts_iter, VIEW_OPTS_COLUMN_NUM_MONTHS, &months_val, -1);
599 delta_months = abs(months_val - (int)num_months);
600 if (delta_months < closest_index_distance)
601 {
602 iter_closest_to_req = view_opts_iter;
603 closest_index_distance = delta_months;
604 }
605 }
606 while (closest_index_distance != 0
607 && (gtk_tree_model_iter_next(GTK_TREE_MODEL(options), &view_opts_iter)));
608
609 // set iter on view
610 g_signal_handlers_block_by_func(dcal->view_options, _gdc_view_option_changed, dcal);
611 gtk_combo_box_set_active_iter(GTK_COMBO_BOX(dcal->view_options), &iter_closest_to_req);
612 g_signal_handlers_unblock_by_func(dcal->view_options, _gdc_view_option_changed, dcal);
613 }
614
615 dcal->numMonths = num_months;
616 recompute_extents(dcal);
617 recompute_mark_storage(dcal);
618 if (gtk_widget_get_realized(GTK_WIDGET(dcal)))
619 {
620 recompute_x_y_scales(dcal);
621 gnc_dense_cal_draw_to_buffer(dcal);
622 gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
623 }
624 }
625
626 guint
gnc_dense_cal_get_num_months(GncDenseCal * dcal)627 gnc_dense_cal_get_num_months(GncDenseCal *dcal)
628 {
629 return dcal->numMonths;
630 }
631
632 void
gnc_dense_cal_set_months_per_col(GncDenseCal * dcal,guint monthsPerCol)633 gnc_dense_cal_set_months_per_col(GncDenseCal *dcal, guint monthsPerCol)
634 {
635 dcal->monthsPerCol = monthsPerCol;
636 recompute_x_y_scales(dcal);
637 }
638
639 GDateMonth
gnc_dense_cal_get_month(GncDenseCal * dcal)640 gnc_dense_cal_get_month(GncDenseCal *dcal)
641 {
642 return dcal->month;
643 }
644
645 GDateYear
gnc_dense_cal_get_year(GncDenseCal * dcal)646 gnc_dense_cal_get_year(GncDenseCal *dcal)
647 {
648 return dcal->year;
649 }
650
651 static void
gnc_dense_cal_dispose(GObject * object)652 gnc_dense_cal_dispose (GObject *object)
653 {
654 GncDenseCal *dcal;
655 g_return_if_fail(object != NULL);
656 g_return_if_fail(GNC_IS_DENSE_CAL(object));
657
658 dcal = GNC_DENSE_CAL(object);
659
660 if (dcal->disposed)
661 return;
662 dcal->disposed = TRUE;
663
664 if (gtk_widget_get_realized(GTK_WIDGET(dcal->transPopup)))
665 {
666 gtk_widget_hide(GTK_WIDGET(dcal->transPopup));
667 gtk_widget_destroy(GTK_WIDGET(dcal->transPopup));
668 dcal->transPopup = NULL;
669 }
670
671 if (dcal->surface)
672 {
673 cairo_surface_destroy (dcal->surface);
674 dcal->surface = NULL;
675 }
676
677 /* FIXME: we have a bunch of cleanup to do, here. */
678
679 gdc_free_all_mark_data(dcal);
680
681 g_object_unref(G_OBJECT(dcal->model));
682
683 if (G_OBJECT_CLASS (parent_class)->dispose)
684 G_OBJECT_CLASS(parent_class)->dispose(object);
685 }
686
687 static void
gnc_dense_cal_finalize(GObject * object)688 gnc_dense_cal_finalize (GObject *object)
689 {
690 g_return_if_fail (object != NULL);
691 g_return_if_fail (GNC_IS_DENSE_CAL (object));
692
693 if (G_OBJECT_CLASS (parent_class)->finalize)
694 G_OBJECT_CLASS(parent_class)->finalize(object);
695 }
696
697 static void
gnc_dense_cal_configure(GtkWidget * widget,GdkEventConfigure * event,gpointer user_data)698 gnc_dense_cal_configure(GtkWidget *widget,
699 GdkEventConfigure *event,
700 gpointer user_data)
701 {
702 GncDenseCal *dcal = GNC_DENSE_CAL(user_data);
703 recompute_x_y_scales(dcal);
704 gdc_reconfig(dcal);
705 gtk_widget_queue_draw_area(widget,
706 event->x, event->y,
707 event->width, event->height);
708 }
709
710 static void
gnc_dense_cal_realize(GtkWidget * widget,gpointer user_data)711 gnc_dense_cal_realize (GtkWidget *widget, gpointer user_data)
712 {
713 GncDenseCal *dcal;
714
715 g_return_if_fail(widget != NULL);
716 g_return_if_fail(GNC_IS_DENSE_CAL (user_data));
717 dcal = GNC_DENSE_CAL(user_data);
718
719 recompute_x_y_scales(dcal);
720 gdc_reconfig(dcal);
721 }
722
723 static void
gdc_reconfig(GncDenseCal * dcal)724 gdc_reconfig(GncDenseCal *dcal)
725 {
726 GtkWidget *widget;
727 GtkAllocation alloc;
728
729 if (dcal->surface)
730 cairo_surface_destroy (dcal->surface);
731
732 widget = GTK_WIDGET(dcal->cal_drawing_area);
733 gtk_widget_get_allocation (widget, &alloc);
734 dcal->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
735 alloc.width,
736 alloc.height);
737 gnc_dense_cal_draw_to_buffer(dcal);
738 }
739
740 static void
_gdc_compute_min_size(GncDenseCal * dcal,guint * min_width,guint * min_height)741 _gdc_compute_min_size(GncDenseCal *dcal, guint *min_width, guint *min_height)
742 {
743 if (min_width != NULL)
744 {
745 *min_width =
746 (dcal->leftPadding * 2)
747 + (num_cols(dcal) * (col_width_at(dcal, dcal->min_x_scale)
748 + dcal->label_width))
749 + ((num_cols(dcal) - 1) * COL_BORDER_SIZE);
750 }
751
752 if (min_height != NULL)
753 {
754 *min_height =
755 (dcal->topPadding * 2)
756 + MINOR_BORDER_SIZE
757 + dcal->dayLabelHeight
758 + (num_weeks_per_col(dcal)
759 * week_height_at(dcal, dcal->min_y_scale));
760 }
761 }
762
763 static void
recompute_x_y_scales(GncDenseCal * dcal)764 recompute_x_y_scales(GncDenseCal *dcal)
765 {
766 int denom;
767 int width, height;
768
769 width = DENSE_CAL_DEFAULT_WIDTH;
770 height = DENSE_CAL_DEFAULT_HEIGHT;
771 if (dcal->initialized)
772 {
773 GtkAllocation alloc;
774 gtk_widget_get_allocation (GTK_WIDGET(dcal->cal_drawing_area), &alloc);
775 width = alloc.width;
776 height = alloc.height;
777 }
778
779 /* FIXME: there's something slightly wrong in the x_scale computation that
780 * lets us draw larger than our area. */
781 denom = 7 * num_cols(dcal);
782 g_assert(denom != 0);
783 dcal->x_scale = ((gint)(width
784 - (dcal->leftPadding * 2)
785 - (num_cols(dcal) * ((8 * MINOR_BORDER_SIZE)
786 + dcal->label_width))
787 - ((num_cols(dcal) - 1) * COL_BORDER_SIZE))
788 / denom);
789 dcal->x_scale = MAX(dcal->x_scale, dcal->min_x_scale);
790
791 denom = num_weeks_per_col(dcal);
792 g_assert(denom != 0);
793 dcal->y_scale = ((gint)(height
794 - (dcal->topPadding * 2)
795 - MINOR_BORDER_SIZE
796 - dcal->dayLabelHeight
797 - (num_weeks_per_col(dcal) - 1
798 * MINOR_BORDER_SIZE))
799 / denom);
800 dcal->y_scale = MAX(dcal->y_scale, dcal->min_y_scale);
801
802 _gdc_set_cal_min_size_req(dcal);
803 }
804
805 static void
gdc_free_all_mark_data(GncDenseCal * dcal)806 gdc_free_all_mark_data(GncDenseCal *dcal)
807 {
808 int i;
809 GList *l;
810 for (i = 0; i < dcal->numMarks; i++)
811 {
812 /* Each of these just contains an elt of dcal->markData,
813 * which we're about to free, below... */
814 g_list_free(dcal->marks[i]);
815 }
816 g_free(dcal->marks);
817 dcal->marks = NULL;
818 /* Remove the old mark data. */
819 for (l = dcal->markData; l; l = l->next)
820 {
821 g_list_free(((gdc_mark_data*)l->data)->ourMarks);
822 g_free((gdc_mark_data*)l->data);
823 }
824 g_list_free(dcal->markData);
825 dcal->markData = NULL;
826 }
827
828 static void
recompute_mark_storage(GncDenseCal * dcal)829 recompute_mark_storage(GncDenseCal *dcal)
830 {
831 if (dcal->marks == NULL)
832 goto createNew;
833 gdc_free_all_mark_data(dcal);
834
835 createNew:
836 dcal->numMarks = num_weeks(dcal) * 7;
837 dcal->marks = g_new0(GList*, dcal->numMarks);
838 if (dcal->model)
839 gdc_add_markings(dcal);
840 }
841
842 static void
recompute_extents(GncDenseCal * dcal)843 recompute_extents(GncDenseCal *dcal)
844 {
845 GDate date;
846 gint start_week, end_week;
847
848 g_date_clear(&date, 1);
849 g_date_set_dmy(&date, 1, dcal->month, dcal->year);
850 start_week = (dcal->week_starts_monday
851 ? g_date_get_monday_week_of_year(&date)
852 : g_date_get_sunday_week_of_year(&date));
853 g_date_add_months(&date, dcal->numMonths);
854 end_week = (dcal->week_starts_monday
855 ? g_date_get_monday_week_of_year(&date)
856 : g_date_get_sunday_week_of_year(&date));
857 if (g_date_get_year(&date) != dcal->year)
858 {
859 end_week += (dcal->week_starts_monday
860 ? g_date_get_monday_weeks_in_year(dcal->year)
861 : g_date_get_sunday_weeks_in_year(dcal->year));
862 }
863 dcal->num_weeks = end_week - start_week + 1;
864 }
865
866 static void
free_rect(gpointer data,gpointer ud)867 free_rect(gpointer data, gpointer ud)
868 {
869 g_free((GdkRectangle*)data);
870 }
871
872 static gboolean
gnc_dense_cal_draw(GtkWidget * widget,cairo_t * cr,gpointer user_data)873 gnc_dense_cal_draw(GtkWidget *widget, cairo_t *cr, gpointer user_data)
874 {
875 GncDenseCal *dcal;
876
877 g_return_val_if_fail(widget != NULL, FALSE);
878 g_return_val_if_fail(GNC_IS_DENSE_CAL(user_data), FALSE);
879
880 dcal = GNC_DENSE_CAL(user_data);
881
882 cairo_save (cr);
883 cairo_set_source_surface (cr, dcal->surface, 0, 0);
884 cairo_paint (cr);
885 cairo_restore (cr);
886 return TRUE;
887 }
888
889 #define LOG_AND_RESET(timer, msg) do { DEBUG("%s: %f", msg, g_timer_elapsed(timer, NULL) * 1000.); g_timer_reset(timer); } while (0);
890
891 static void
gnc_dense_cal_draw_to_buffer(GncDenseCal * dcal)892 gnc_dense_cal_draw_to_buffer(GncDenseCal *dcal)
893 {
894 GtkWidget *widget;
895 GtkStyleContext *stylectxt;
896 GtkStateFlags state_flags;
897 GtkAllocation alloc;
898 gint i;
899 int maxWidth;
900 PangoLayout *layout;
901 GTimer *timer;
902 cairo_t *cr;
903 gchar *primary_color_class, *secondary_color_class, *marker_color_class;
904
905 timer = g_timer_new();
906 DEBUG("drawing");
907 widget = GTK_WIDGET(dcal);
908
909 if (!dcal->surface)
910 return;
911
912 g_timer_start(timer);
913 cr = cairo_create (dcal->surface);
914 layout = gtk_widget_create_pango_layout(GTK_WIDGET(dcal), NULL);
915 LOG_AND_RESET(timer, "create_pango_layout");
916
917 gtk_widget_get_allocation (GTK_WIDGET(dcal->cal_drawing_area), &alloc);
918 stylectxt = gtk_widget_get_style_context (GTK_WIDGET(dcal->cal_drawing_area));
919 state_flags = gtk_style_context_get_state (stylectxt);
920
921 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_BACKGROUND);
922 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_CALENDAR);
923
924 gtk_render_background (stylectxt, cr, 0, 0,
925 cairo_image_surface_get_width (dcal->surface),
926 cairo_image_surface_get_height (dcal->surface));
927
928 gtk_style_context_remove_class (stylectxt, GTK_STYLE_CLASS_BACKGROUND);
929
930 /* get the colors */
931 {
932 GdkRGBA color;
933 gchar *class_extension = NULL;
934
935 gtk_style_context_get_color (stylectxt, GTK_STATE_FLAG_NORMAL, &color);
936
937 if (gnc_is_dark_theme (&color))
938 class_extension = "-dark";
939
940 primary_color_class = g_strconcat ("primary", class_extension, NULL);
941 secondary_color_class = g_strconcat ("secondary", class_extension, NULL);
942 marker_color_class = g_strconcat ("markers", class_extension, NULL);
943 }
944
945 /* lets confirm text height size */
946 pango_layout_set_text(layout, "S", -1);
947 pango_layout_get_pixel_size(layout, NULL, &dcal->dayLabelHeight);
948
949 /* Fill in alternating month colors. */
950 {
951 gint i;
952 GdkRectangle *rect;
953 GList *mcList, *mcListIter;
954
955 /* reset all of the month position offsets. */
956 for (i = 0; i < 12; i++)
957 {
958 dcal->monthPositions[i].x = dcal->monthPositions[i].y = -1;
959 }
960
961 gtk_style_context_save (stylectxt);
962
963 /* Paint the weeks for the upcoming N months. */
964 for (i = 0; i < dcal->numMonths; i++)
965 {
966 mcList = NULL;
967 month_coords(dcal, i, &mcList);
968 dcal->monthPositions[i].x
969 = floor(i / dcal->monthsPerCol)
970 * (col_width(dcal) + COL_BORDER_SIZE);
971 dcal->monthPositions[i].y = ((GdkRectangle*)mcList->next->next->next->data)->y;
972 for (mcListIter = mcList; mcListIter != NULL; mcListIter = mcListIter->next)
973 {
974 rect = (GdkRectangle*)mcListIter->data;
975 gtk_style_context_save (stylectxt);
976
977 if (i % 2 == 0)
978 gtk_style_context_add_class (stylectxt, primary_color_class);
979 else
980 gtk_style_context_add_class (stylectxt, secondary_color_class);
981
982 gtk_render_background (stylectxt, cr, rect->x, rect->y, rect->width, rect->height);
983 gtk_style_context_restore (stylectxt);
984 }
985 g_list_foreach(mcList, free_rect, NULL);
986 g_list_free(mcList);
987 }
988 gtk_style_context_restore (stylectxt);
989 }
990 LOG_AND_RESET(timer, "alternating month colors");
991
992
993 /* Highlight the marked days. */
994 {
995 int i;
996 int x1, x2, y1, y2;
997
998 gtk_style_context_save (stylectxt);
999 gtk_style_context_add_class (stylectxt, marker_color_class);
1000 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_VIEW);
1001 gtk_style_context_set_state (stylectxt, GTK_STATE_FLAG_SELECTED);
1002
1003 for (i = 0; i < dcal->numMarks; i++)
1004 {
1005 if (dcal->marks[i] != NULL)
1006 {
1007 int center_x, center_y, radius;
1008
1009 doc_coords(dcal, i, &x1, &y1, &x2, &y2);
1010 center_x = (x1 + x2 ) / 2;
1011 center_y = (y1 + y2 ) / 2;
1012 radius = MIN((x2 - x1), (y2 - y1)) * .75;
1013
1014 // try to compensate for row height/width being odd or even
1015 if (((y2 - y1) % 2) != 0)
1016 center_y = center_y + 1;
1017
1018 if (((x2 - x1) % 2) != 0)
1019 center_x = center_x + 1;
1020
1021 gtk_render_background (stylectxt, cr,
1022 center_x - (radius + 2), center_y - radius,
1023 (radius * 2) + 4, radius * 2);
1024 }
1025 }
1026 gtk_style_context_restore (stylectxt);
1027 }
1028 LOG_AND_RESET(timer, "marked days");
1029
1030 for (i = 0; i < num_cols(dcal); i++)
1031 {
1032 GdkRGBA color;
1033 gint x, y, w, h;
1034 gint j;
1035
1036 cairo_save (cr);
1037 gdk_rgba_parse (&color, "black");
1038
1039 x = dcal->leftPadding
1040 + (i * (col_width(dcal) + COL_BORDER_SIZE))
1041 + dcal->label_height + 1;
1042 y = dcal->topPadding + dcal->dayLabelHeight;
1043 w = col_width(dcal) - COL_BORDER_SIZE - dcal->label_width;
1044 h = col_height(dcal);
1045
1046 gtk_style_context_save (stylectxt);
1047
1048 /* draw the outside border [inside the month labels] */
1049 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_FRAME);
1050
1051 gtk_render_frame (stylectxt, cr, x, y, w + 1, h + 1);
1052
1053 gnc_style_context_get_border_color (stylectxt, state_flags, &color);
1054 cairo_set_source_rgb (cr, color.red, color.green, color.blue);
1055 cairo_set_line_width (cr, 1);
1056
1057 /* draw the week separations */
1058 for (j = 0; j < num_weeks_per_col(dcal); j++)
1059 {
1060 gint wy = y + (j * week_height(dcal));
1061 cairo_move_to (cr, x, wy + 0.5);
1062 cairo_line_to (cr, x + w, wy + 0.5);
1063 cairo_stroke (cr);
1064 }
1065
1066 /* draw the day separations */
1067 for (j = 1; j < 7; j++)
1068 {
1069 gint dx = x + (j * day_width(dcal));
1070 cairo_move_to (cr, dx + 0.5, y);
1071 cairo_line_to (cr, dx + 0.5, y + col_height(dcal));
1072 cairo_stroke (cr);
1073 }
1074 cairo_restore (cr);
1075 gtk_style_context_restore (stylectxt);
1076
1077
1078 /* draw the day of the week labels */
1079 pango_layout_set_text(layout, "88", -1);
1080 pango_layout_get_pixel_size(layout, &maxWidth, NULL);
1081
1082 if (dcal->x_scale > maxWidth)
1083 {
1084 gtk_style_context_save (stylectxt);
1085 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_HEADER);
1086
1087 gtk_render_background (stylectxt, cr, x, y - dcal->dayLabelHeight, (day_width(dcal) * 7) + 1, dcal->dayLabelHeight);
1088
1089 for (j = 0; j < 7; j++)
1090 {
1091 int day_label_width;
1092 gint label_x_offset, label_y_offset;
1093 gint day_label_str_len = 4;
1094 gchar day_label_str[day_label_str_len+1];
1095 day_label(day_label_str, day_label_str_len, (j + dcal->week_starts_monday) % 7);
1096 pango_layout_set_text(layout, day_label_str, -1);
1097 pango_layout_get_pixel_size(layout, &day_label_width, NULL);
1098 label_x_offset = x
1099 + (j * day_width(dcal))
1100 + (day_width(dcal) / 2)
1101 - (day_label_width / 2);
1102 label_y_offset = y - dcal->dayLabelHeight;
1103 pango_layout_set_text(layout, day_label_str, -1);
1104 gtk_render_layout (stylectxt, cr, label_x_offset, label_y_offset, layout);
1105 }
1106 gtk_style_context_restore (stylectxt);
1107 }
1108 }
1109 LOG_AND_RESET(timer, "lines and labels");
1110
1111
1112 /* Month labels. */
1113 {
1114 gint i;
1115 gint x_offset = dcal->leftPadding;
1116
1117 gtk_style_context_save (stylectxt);
1118 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_HEADER);
1119
1120 for (i = 0; i < 12; i++)
1121 {
1122 if (dcal->monthPositions[i].x == -1)
1123 break;
1124
1125 gtk_render_background (stylectxt, cr, dcal->monthPositions[i].x + x_offset, dcal->topPadding,
1126 dcal->dayLabelHeight + 1, col_height(dcal) + dcal->dayLabelHeight + 1);
1127 }
1128
1129 for (i = 0; i < 12; i++)
1130 {
1131 guint idx;
1132
1133 if (dcal->monthPositions[i].x == -1)
1134 break;
1135 idx = (dcal->month - 1 + i) % 12;
1136 pango_layout_set_text(layout, month_name(idx), -1);
1137 cairo_save (cr);
1138 cairo_translate (cr, dcal->monthPositions[i].x + x_offset, dcal->monthPositions[i].y);
1139 cairo_rotate (cr, -G_PI / 2.);
1140 gtk_render_layout (stylectxt, cr, 0, 0, layout);
1141 cairo_restore (cr);
1142 }
1143 gtk_style_context_restore (stylectxt);
1144 }
1145 LOG_AND_RESET(timer, "month labels");
1146
1147
1148 /* Day number strings [dates] */
1149 {
1150 GDate d, eoc;
1151 gint doc;
1152 gchar dayNumBuf[4];
1153 gint numW, numH;
1154 gint x1, y1, x2, y2, w, h;
1155
1156 gtk_style_context_save (stylectxt);
1157 gtk_style_context_add_class (stylectxt, "day-number");
1158
1159 cairo_save (cr);
1160 g_date_set_dmy(&d, 1, dcal->month, dcal->year);
1161 eoc = d;
1162 g_date_add_months(&eoc, dcal->numMonths);
1163 for (doc = 0; g_date_get_julian(&d) < g_date_get_julian(&eoc); g_date_add_days(&d, 1), doc++)
1164 {
1165 doc_coords(dcal, doc, &x1, &y1, &x2, &y2);
1166 memset(dayNumBuf, 0, 4);
1167 snprintf(dayNumBuf, 4, "%d", g_date_get_day(&d));
1168 pango_layout_set_text(layout, dayNumBuf, -1);
1169 pango_layout_get_pixel_size(layout, &numW, &numH);
1170 w = (x2 - x1) + 1;
1171 h = (y2 - y1) + 1;
1172 gtk_render_layout (stylectxt, cr, x1 + (w / 2) - (numW / 2), y1 + (h / 2) - (numH / 2), layout);
1173 }
1174 cairo_restore (cr);
1175 gtk_style_context_restore (stylectxt);
1176 }
1177 LOG_AND_RESET(timer, "dates");
1178
1179 gtk_widget_get_allocation (widget, &alloc);
1180 gtk_widget_queue_draw_area(GTK_WIDGET(dcal),
1181 alloc.x,
1182 alloc.y,
1183 alloc.width,
1184 alloc.height);
1185
1186 LOG_AND_RESET(timer, "queue draw");
1187
1188 g_free (primary_color_class);
1189 g_free (secondary_color_class);
1190 g_free (marker_color_class);
1191
1192 g_object_unref(layout);
1193 cairo_destroy (cr);
1194
1195 g_timer_destroy(timer);
1196 }
1197
1198 static void
populate_hover_window(GncDenseCal * dcal)1199 populate_hover_window(GncDenseCal *dcal)
1200 {
1201 GtkWidget *w;
1202 GDate *date;
1203 static const int MAX_STRFTIME_BUF_LEN = 64;
1204 gchar strftimeBuf[MAX_STRFTIME_BUF_LEN];
1205
1206 if (dcal->doc >= 0)
1207 {
1208 GObject *o;
1209 GtkListStore *model;
1210 GList *l;
1211
1212 w = GTK_WIDGET(g_object_get_data(G_OBJECT(dcal->transPopup), "dateLabel"));
1213 date = g_date_new_dmy(1, dcal->month, dcal->year);
1214 g_date_add_days(date, dcal->doc);
1215 /* Note: the ISO date format (%F or equivalently
1216 * %Y-%m-%d) is not a good idea here since many
1217 * locales will want to use a very different date
1218 * format. Please leave the specification of the date
1219 * format up to the locale and use %x here. */
1220 g_date_strftime(strftimeBuf, MAX_STRFTIME_BUF_LEN - 1, "%x", date);
1221 gtk_label_set_text(GTK_LABEL(w), strftimeBuf);
1222
1223 o = G_OBJECT(dcal->transPopup);
1224 model = GTK_LIST_STORE(g_object_get_data(o, "model"));
1225 gtk_list_store_clear(model);
1226 for (l = dcal->marks[dcal->doc]; l; l = l->next)
1227 {
1228 GtkTreeIter iter;
1229 gdc_mark_data *gdcmd;
1230
1231 gdcmd = (gdc_mark_data*)l->data;
1232 gtk_list_store_insert(model, &iter, INT_MAX);
1233 gtk_list_store_set(model, &iter, 0, (gdcmd->name ? gdcmd->name : _("(unnamed)")), 1, gdcmd->info, -1);
1234 }
1235
1236 // if there are no rows, add one
1237 if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL(model), NULL) == 0)
1238 {
1239 GtkTreeIter iter;
1240 gtk_list_store_insert(model, &iter, -1);
1241 }
1242
1243 // make sure all pending events are processed
1244 while(gtk_events_pending())
1245 gtk_main_iteration();
1246
1247 g_date_free(date);
1248 }
1249 }
1250
1251 static gint
gnc_dense_cal_button_press(GtkWidget * widget,GdkEventButton * evt)1252 gnc_dense_cal_button_press(GtkWidget *widget,
1253 GdkEventButton *evt)
1254 {
1255 GdkWindow *win = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
1256 GdkMonitor *mon = gdk_display_get_monitor_at_window (gtk_widget_get_display (widget), win);
1257 GdkRectangle work_area_size;
1258 GtkAllocation alloc;
1259 GncDenseCal *dcal = GNC_DENSE_CAL(widget);
1260 gint win_xpos = evt->x_root + 5;
1261 gint win_ypos = evt->y_root + 5;
1262
1263 gdk_monitor_get_workarea (mon, &work_area_size);
1264
1265 dcal->screen_width = work_area_size.width;
1266 dcal->screen_height = work_area_size.height;
1267
1268 dcal->doc = wheres_this(dcal, evt->x, evt->y);
1269 dcal->showPopup = ~(dcal->showPopup);
1270 if (dcal->showPopup && dcal->doc >= 0)
1271 {
1272 // Do the move twice in case the WM is ignoring the first one
1273 // because the window hasn't been shown, yet. The WM is free
1274 // to ignore our move and place windows according to it's own
1275 // strategy, but hopefully it'll listen to us. Certainly the
1276 // second move after show_all'ing the window should do the
1277 // trick with a bit of flicker.
1278 gtk_window_move(GTK_WINDOW(dcal->transPopup), evt->x_root + 5, evt->y_root + 5);
1279
1280 populate_hover_window(dcal);
1281 gtk_widget_queue_resize(GTK_WIDGET(dcal->transPopup));
1282 gtk_widget_show_all(GTK_WIDGET(dcal->transPopup));
1283
1284 gtk_widget_get_allocation(GTK_WIDGET(dcal->transPopup), &alloc);
1285
1286 if (evt->x_root + 5 + alloc.width > dcal->screen_width)
1287 win_xpos = evt->x_root - 2 - alloc.width;
1288
1289 if (evt->y_root + 5 + alloc.height > dcal->screen_height)
1290 win_ypos = evt->y_root - 2 - alloc.height;
1291
1292 gtk_window_move(GTK_WINDOW(dcal->transPopup), win_xpos, win_ypos);
1293 }
1294 else
1295 {
1296 dcal->doc = -1;
1297 gtk_widget_hide(GTK_WIDGET(dcal->transPopup));
1298 }
1299 return TRUE;
1300 }
1301
1302 static gint
gnc_dense_cal_motion_notify(GtkWidget * widget,GdkEventMotion * event)1303 gnc_dense_cal_motion_notify(GtkWidget *widget,
1304 GdkEventMotion *event)
1305 {
1306 GncDenseCal *dcal;
1307 GtkAllocation alloc;
1308 gint doc;
1309 int unused;
1310 GdkModifierType unused2;
1311 gint win_xpos = event->x_root + 5;
1312 gint win_ypos = event->y_root + 5;
1313
1314 dcal = GNC_DENSE_CAL(widget);
1315 if (!dcal->showPopup)
1316 return FALSE;
1317
1318 /* As per https://www.gtk.org/tutorial/sec-eventhandling.html */
1319 if (event->is_hint)
1320 {
1321 GdkSeat *seat = gdk_display_get_default_seat (gdk_window_get_display (event->window));
1322 GdkDevice *pointer = gdk_seat_get_pointer (seat);
1323
1324 gdk_window_get_device_position (event->window, pointer, &unused, &unused, &unused2);
1325 }
1326
1327 doc = wheres_this(dcal, event->x, event->y);
1328 if (doc >= 0)
1329 {
1330 if (dcal->doc != doc) // if we are on the same day, no need to reload
1331 {
1332 dcal->doc = doc;
1333 populate_hover_window(dcal);
1334 gtk_widget_queue_resize(GTK_WIDGET(dcal->transPopup));
1335 gtk_widget_show_all(GTK_WIDGET(dcal->transPopup));
1336 }
1337 gtk_widget_get_allocation(GTK_WIDGET(dcal->transPopup), &alloc);
1338
1339 if (event->x_root + 5 + alloc.width > dcal->screen_width)
1340 win_xpos = event->x_root - 2 - alloc.width;
1341
1342 if (event->y_root + 5 + alloc.height > dcal->screen_height)
1343 win_ypos = event->y_root - 2 - alloc.height;
1344
1345 gtk_window_move(GTK_WINDOW(dcal->transPopup), win_xpos, win_ypos);
1346 }
1347 else
1348 {
1349 dcal->doc = -1;
1350 gtk_widget_hide(GTK_WIDGET(dcal->transPopup));
1351 }
1352 return TRUE;
1353 }
1354
1355
1356 static void
_gdc_view_option_changed(GtkComboBox * widget,gpointer user_data)1357 _gdc_view_option_changed(GtkComboBox *widget, gpointer user_data)
1358 {
1359 GtkTreeIter iter;
1360 GtkTreeModel *model;
1361 gint months_val;
1362
1363 model = GTK_TREE_MODEL(gtk_combo_box_get_model(widget));
1364 if (!gtk_combo_box_get_active_iter(widget, &iter))
1365 return;
1366 gtk_tree_model_get(model, &iter, VIEW_OPTS_COLUMN_NUM_MONTHS, &months_val, -1);
1367 DEBUG("changing to %d months", months_val);
1368 gnc_dense_cal_set_num_months(GNC_DENSE_CAL(user_data), months_val);
1369 }
1370
1371 static inline int
day_width_at(GncDenseCal * dcal,guint xScale)1372 day_width_at(GncDenseCal *dcal, guint xScale)
1373 {
1374 return xScale + MINOR_BORDER_SIZE;
1375 }
1376
1377 static inline int
day_width(GncDenseCal * dcal)1378 day_width(GncDenseCal *dcal)
1379 {
1380 return day_width_at(dcal, dcal->x_scale);
1381 }
1382
1383 static inline int
day_height_at(GncDenseCal * dcal,guint yScale)1384 day_height_at(GncDenseCal *dcal, guint yScale)
1385 {
1386 return yScale + MINOR_BORDER_SIZE;
1387 }
1388
1389 static inline int
day_height(GncDenseCal * dcal)1390 day_height(GncDenseCal *dcal)
1391 {
1392 return day_height_at(dcal, dcal->y_scale);
1393 }
1394
1395 static inline int
week_width_at(GncDenseCal * dcal,guint xScale)1396 week_width_at(GncDenseCal *dcal, guint xScale)
1397 {
1398 return day_width_at(dcal, xScale) * 7;
1399 }
1400
1401 static inline int
week_width(GncDenseCal * dcal)1402 week_width(GncDenseCal *dcal)
1403 {
1404 return week_width_at(dcal, dcal->x_scale);
1405 }
1406
1407 static inline int
week_height_at(GncDenseCal * dcal,guint yScale)1408 week_height_at(GncDenseCal *dcal, guint yScale)
1409 {
1410 return day_height_at(dcal, yScale);
1411 }
1412
1413 static inline int
week_height(GncDenseCal * dcal)1414 week_height(GncDenseCal *dcal)
1415 {
1416 return week_height_at(dcal, dcal->y_scale);
1417 }
1418
1419 static inline int
col_width_at(GncDenseCal * dcal,guint xScale)1420 col_width_at(GncDenseCal *dcal, guint xScale)
1421 {
1422 return (week_width_at(dcal, xScale)
1423 + dcal->label_width
1424 + COL_BORDER_SIZE);
1425 }
1426
1427 static inline int
col_width(GncDenseCal * dcal)1428 col_width(GncDenseCal *dcal)
1429 {
1430 return col_width_at(dcal, dcal->x_scale);
1431 }
1432
1433 static inline int
col_height(GncDenseCal * dcal)1434 col_height(GncDenseCal *dcal)
1435 {
1436 return week_height(dcal) * num_weeks_per_col(dcal);
1437 }
1438
1439 static inline int
num_cols(GncDenseCal * dcal)1440 num_cols(GncDenseCal *dcal)
1441 {
1442 return ceil((float)dcal->numMonths / (float)dcal->monthsPerCol);
1443 }
1444
1445 static inline int
num_weeks(GncDenseCal * dcal)1446 num_weeks(GncDenseCal *dcal)
1447 {
1448 return dcal->num_weeks;
1449 }
1450
1451 static
num_weeks_per_col(GncDenseCal * dcal)1452 int num_weeks_per_col(GncDenseCal *dcal)
1453 {
1454 int num_weeks_toRet, numCols, i;
1455 GDate *start, *end;
1456 int startWeek, endWeek;
1457
1458 start = g_date_new();
1459 end = g_date_new();
1460
1461 num_weeks_toRet = 0;
1462 numCols = num_cols(dcal);
1463
1464 for (i = 0; i < numCols; i++)
1465 {
1466 g_date_set_dmy(start, 1,
1467 ((dcal->month - 1
1468 + (i * dcal->monthsPerCol)) % 12)
1469 + 1,
1470 dcal->year + floor((dcal->month - 1
1471 + (i * dcal->monthsPerCol))
1472 / 12));
1473 *end = *start;
1474 /* Add the smaller of (the number of months in the
1475 * calendar-display, minus the number of months shown in the
1476 * previous columns) or (the number of months in a column) */
1477 g_date_add_months(end, MIN(dcal->numMonths,
1478 MIN(dcal->monthsPerCol,
1479 dcal->numMonths
1480 - ((i - 1)
1481 * dcal->monthsPerCol))));
1482 g_date_subtract_days(end, 1);
1483 startWeek = (dcal->week_starts_monday
1484 ? g_date_get_monday_week_of_year(start)
1485 : g_date_get_sunday_week_of_year(start));
1486 endWeek = (dcal->week_starts_monday
1487 ? g_date_get_monday_week_of_year(end)
1488 : g_date_get_sunday_week_of_year(end));
1489 if (endWeek < startWeek)
1490 {
1491 endWeek += (dcal->week_starts_monday
1492 ? g_date_get_monday_weeks_in_year(g_date_get_year(start))
1493 : g_date_get_sunday_weeks_in_year(g_date_get_year(start)));
1494 }
1495 num_weeks_toRet = MAX(num_weeks_toRet, (endWeek - startWeek) + 1);
1496 }
1497 g_date_free(start);
1498 g_date_free(end);
1499 return num_weeks_toRet;
1500 }
1501
1502 /**
1503 * @param monthOfCal 0-based; offset of calendar's first month.
1504 * @param outList A GList in which to place GdkRectangle's of the extents of
1505 * each week. 4 or 5 GdkRectangle*s will be added to the list, as per the
1506 * size of the month.
1507 **/
1508 static void
month_coords(GncDenseCal * dcal,int monthOfCal,GList ** outList)1509 month_coords(GncDenseCal *dcal, int monthOfCal, GList **outList)
1510 {
1511 gint weekRow, colNum, previousMonthsInCol, monthOffset;
1512 gint start;
1513 GDate *startD, *endD;
1514 GdkRectangle *rect;
1515 gint startWk, endWk;
1516
1517 if (monthOfCal > dcal->numMonths)
1518 return;
1519
1520 colNum = floor(monthOfCal / dcal->monthsPerCol);
1521 monthOffset = colNum * dcal->monthsPerCol;
1522 previousMonthsInCol = MAX(0, (monthOfCal % dcal->monthsPerCol));
1523
1524 startD = g_date_new();
1525 endD = g_date_new();
1526
1527 /* Calculate the number of weeks in the column before the month we're
1528 * interested in. */
1529 weekRow = 0;
1530 if (previousMonthsInCol > 0)
1531 {
1532 g_date_set_dmy(startD, 1,
1533 ((dcal->month - 1 + monthOffset) % 12) + 1,
1534 dcal->year + floor((dcal->month - 1 + monthOffset) / 12));
1535 /* get the week of the top of the column */
1536 startWk = (dcal->week_starts_monday
1537 ? g_date_get_monday_week_of_year(startD)
1538 : g_date_get_sunday_week_of_year(startD));
1539 /* get the week of the end of the previous months */
1540 *endD = *startD;
1541 g_date_add_months(endD, previousMonthsInCol);
1542 g_date_subtract_days(endD, 1);
1543 endWk = (dcal->week_starts_monday
1544 ? g_date_get_monday_week_of_year(endD)
1545 : g_date_get_sunday_week_of_year(endD));
1546 if (endWk < startWk)
1547 {
1548 endWk += (dcal->week_starts_monday
1549 ? g_date_get_monday_weeks_in_year(g_date_get_year(startD))
1550 : g_date_get_sunday_weeks_in_year(g_date_get_year(startD)));
1551 }
1552 /* determine how many weeks are before the month we're
1553 * interested in. */
1554 weekRow = endWk - startWk;
1555 if (g_date_get_weekday(endD) == (dcal->week_starts_monday ? G_DATE_SUNDAY : G_DATE_SATURDAY))
1556 {
1557 weekRow++;
1558 }
1559 }
1560
1561 g_date_set_dmy(startD, 1,
1562 ((dcal->month - 1 + monthOfCal) % 12) + 1,
1563 dcal->year + floor((dcal->month - 1 + monthOfCal) / 12));
1564 *endD = *startD;
1565 g_date_add_months(endD, 1);
1566 g_date_subtract_days(endD, 1);
1567 /* Get the first week. */
1568 {
1569 start = (g_date_get_weekday(startD) - dcal->week_starts_monday) % 7;
1570 rect = g_new0(GdkRectangle, 1);
1571 rect->x = dcal->leftPadding
1572 + MINOR_BORDER_SIZE
1573 + (colNum * (col_width(dcal) + COL_BORDER_SIZE))
1574 + dcal->label_height
1575 + (start * day_width(dcal));
1576 rect->y = dcal->topPadding
1577 + dcal->dayLabelHeight
1578 + MINOR_BORDER_SIZE
1579 + (weekRow * week_height(dcal));
1580 rect->width = (7 - start) * day_width(dcal);
1581 rect->height = week_height(dcal);
1582 *outList = g_list_append(*outList, (gpointer)rect);
1583 rect = NULL;
1584 }
1585
1586 /* Get the middle weeks. */
1587 {
1588 gint i, weekStart, weekEnd;
1589
1590 weekStart = (dcal->week_starts_monday
1591 ? g_date_get_monday_week_of_year(startD)
1592 : g_date_get_sunday_week_of_year(startD)) + 1;
1593 weekEnd = (dcal->week_starts_monday
1594 ? g_date_get_monday_week_of_year(endD)
1595 : g_date_get_sunday_week_of_year(endD));
1596 for (i = weekStart; i < weekEnd; i++)
1597 {
1598 rect = g_new0(GdkRectangle, 1);
1599 rect->x = dcal->leftPadding
1600 + MINOR_BORDER_SIZE
1601 + dcal->label_height
1602 + (colNum * (col_width(dcal) + COL_BORDER_SIZE));
1603 rect->y = dcal->topPadding
1604 + dcal->dayLabelHeight
1605 + MINOR_BORDER_SIZE
1606 + ((weekRow + (i - weekStart) + 1) * week_height(dcal));
1607 rect->width = week_width(dcal);
1608 rect->height = week_height(dcal);
1609
1610 *outList = g_list_append(*outList, (gpointer)rect);
1611 rect = NULL;
1612 }
1613 }
1614
1615 /* Get the last week. */
1616 {
1617 gint end_week_of_year = g_date_get_sunday_week_of_year(endD);
1618 gint start_week_of_year = g_date_get_sunday_week_of_year(startD);
1619 if (dcal->week_starts_monday == 1)
1620 {
1621 end_week_of_year = g_date_get_monday_week_of_year(endD);
1622 start_week_of_year = g_date_get_monday_week_of_year(startD);
1623 }
1624
1625 rect = g_new0(GdkRectangle, 1);
1626 rect->x = dcal->leftPadding
1627 + MINOR_BORDER_SIZE
1628 + dcal->label_height
1629 + (colNum * (col_width(dcal) + COL_BORDER_SIZE));
1630 rect->y = dcal->topPadding
1631 + MINOR_BORDER_SIZE
1632 + dcal->dayLabelHeight
1633 + ((weekRow
1634 + (end_week_of_year - start_week_of_year))
1635 * week_height(dcal));
1636 rect->width = (((g_date_get_weekday(endD) - dcal->week_starts_monday) % 7) + 1) * day_width(dcal);
1637 rect->height = week_height(dcal);
1638
1639 *outList = g_list_append(*outList, (gpointer)rect);
1640 rect = NULL;
1641 }
1642
1643 g_date_free(startD);
1644 g_date_free(endD);
1645 }
1646
1647 /* FIXME: make this more like month_coords */
1648 static void
doc_coords(GncDenseCal * dcal,int dayOfCal,int * x1,int * y1,int * x2,int * y2)1649 doc_coords(GncDenseCal *dcal, int dayOfCal,
1650 int *x1, int *y1, int *x2, int *y2)
1651 {
1652 GDate d;
1653 gint docMonth;
1654 gint d_week_of_cal, top_of_col_week_of_cal;
1655 gint colNum, dayCol, weekRow;
1656
1657 /* FIXME: add range checks */
1658 g_date_set_dmy(&d, 1, dcal->month, dcal->year);
1659 g_date_add_days(&d, dayOfCal);
1660 docMonth = g_date_get_month(&d);
1661 if (g_date_get_year(&d) != dcal->year)
1662 {
1663 docMonth += 12;
1664 }
1665 colNum = floor((float)(docMonth - dcal->month) / (float)dcal->monthsPerCol);
1666 dayCol = (g_date_get_weekday(&d) - dcal->week_starts_monday) % 7;
1667 d_week_of_cal = g_date_get_sunday_week_of_year(&d);
1668 if (dcal->week_starts_monday == 1)
1669 {
1670 d_week_of_cal = g_date_get_monday_week_of_year(&d);
1671 }
1672 g_date_set_dmy(&d, 1, dcal->month, dcal->year);
1673 g_date_add_months(&d, (colNum * dcal->monthsPerCol));
1674 top_of_col_week_of_cal = (dcal->week_starts_monday
1675 ? g_date_get_monday_week_of_year(&d)
1676 : g_date_get_sunday_week_of_year(&d));
1677 if (d_week_of_cal < top_of_col_week_of_cal)
1678 {
1679 gint week_offset;
1680 week_offset = g_date_get_sunday_weeks_in_year(dcal->year);
1681 if (dcal->week_starts_monday == 1)
1682 {
1683 week_offset = g_date_get_monday_weeks_in_year(dcal->year);
1684 }
1685 d_week_of_cal += week_offset;
1686 }
1687 weekRow = d_week_of_cal - top_of_col_week_of_cal;
1688
1689 /* top-left corner */
1690 /* FIXME: this has the math to make the mark-cells come out right,
1691 * which it shouldn't. */
1692 *x1 = dcal->leftPadding
1693 + MINOR_BORDER_SIZE
1694 + dcal->label_height
1695 + (colNum * (col_width(dcal) + COL_BORDER_SIZE))
1696 + (dayCol * day_width(dcal))
1697 + (day_width(dcal) / 4);
1698 *y1 = dcal->topPadding
1699 + MINOR_BORDER_SIZE
1700 + dcal->dayLabelHeight
1701 + (weekRow * week_height(dcal))
1702 + (day_height(dcal) / 4);
1703
1704 *x2 = *x1 + (day_width(dcal) / 2);
1705 *y2 = *y1 + (day_height(dcal) / 2);
1706 }
1707
1708 /**
1709 * Given x,y coordinates, returns the day-of-cal under the mouse; will return
1710 * '-1' if invalid.
1711 **/
1712 static gint
wheres_this(GncDenseCal * dcal,int x,int y)1713 wheres_this(GncDenseCal *dcal, int x, int y)
1714 {
1715 gint colNum, weekRow, dayCol, dayOfCal;
1716 GDate d, startD;
1717 GtkAllocation alloc;
1718
1719 x -= dcal->leftPadding;
1720 y -= dcal->topPadding;
1721
1722 if ((x < 0) || (y < 0))
1723 {
1724 return -1;
1725 }
1726 gtk_widget_get_allocation (GTK_WIDGET(dcal), &alloc);
1727 if ((x >= alloc.width)
1728 || (y >= alloc.height))
1729 {
1730 return -1;
1731 }
1732
1733 /* "outside of displayed table" check */
1734 if (x >= (num_cols(dcal) * (col_width(dcal) + COL_BORDER_SIZE)))
1735 {
1736 return -1;
1737 }
1738 if (y >= dcal->dayLabelHeight + col_height(dcal))
1739 {
1740 return -1;
1741 }
1742
1743 /* coords -> year-relative-values */
1744 colNum = floor(x / (col_width(dcal) + COL_BORDER_SIZE));
1745
1746 x %= (col_width(dcal) + COL_BORDER_SIZE);
1747 x -= dcal->label_width;
1748 if (x < 0)
1749 {
1750 return -1;
1751 }
1752 if (x >= day_width(dcal) * 7)
1753 {
1754 return -1;
1755 }
1756
1757 y -= dcal->dayLabelHeight;
1758 if (y < 0)
1759 {
1760 return -1;
1761 }
1762
1763 dayCol = floor((float)x / (float)day_width(dcal));
1764 weekRow = floor((float)y / (float)week_height(dcal));
1765
1766 g_date_set_dmy(&startD, 1, dcal->month, dcal->year);
1767 d = startD;
1768 g_date_add_months(&d, (colNum * dcal->monthsPerCol));
1769 dayCol -= ((g_date_get_weekday(&d) - dcal->week_starts_monday) % 7);
1770 if (weekRow == 0)
1771 {
1772 if (dayCol < 0)
1773 {
1774 return -1;
1775 }
1776 }
1777 g_date_add_days(&d, dayCol + (weekRow * 7));
1778
1779 /* Check to make sure we're within the column's displayed range. */
1780 {
1781 GDate ccd;
1782 g_date_set_dmy(&ccd, 1, dcal->month, dcal->year);
1783 g_date_add_months(&ccd, (colNum + 1) * dcal->monthsPerCol);
1784 if (g_date_get_julian(&d) >= g_date_get_julian(&ccd))
1785 {
1786 return -1;
1787 }
1788 }
1789
1790 dayOfCal = g_date_get_julian(&d) - g_date_get_julian(&startD);
1791
1792 /* one more check before returning... */
1793 g_date_subtract_months(&d, dcal->numMonths);
1794 if (g_date_get_julian(&d) >= g_date_get_julian(&startD))
1795 {
1796 /* we're past the end of the displayed calendar, thus -1 */
1797 DEBUG("%d >= %d", g_date_get_julian(&d), g_date_get_julian(&startD));
1798 return -1;
1799 }
1800
1801 return dayOfCal;
1802 }
1803
1804 static gint
gdc_get_doc_offset(GncDenseCal * dcal,GDate * d)1805 gdc_get_doc_offset(GncDenseCal *dcal, GDate *d)
1806 {
1807 gint toRet;
1808 /* soc == start-of-calendar */
1809 GDate soc;
1810
1811 g_date_clear(&soc, 1);
1812 g_date_set_dmy(&soc, 1, dcal->month, dcal->year);
1813 /* ensure not before calendar start. */
1814 if (g_date_get_julian(d) < g_date_get_julian(&soc))
1815 return -1;
1816 /* do computation here, since we're going to change the
1817 * start-of-calendar date. */
1818 toRet = g_date_get_julian(d) - g_date_get_julian(&soc);
1819 /* ensure not after end of visible calendar. */
1820 g_date_add_months(&soc, dcal->numMonths);
1821 if (g_date_get_julian(d) >= g_date_get_julian(&soc))
1822 return -1;
1823 /* return pre-computed value. */
1824 return toRet;
1825 }
1826
1827 static void
gdc_add_tag_markings(GncDenseCal * cal,guint tag)1828 gdc_add_tag_markings(GncDenseCal *cal, guint tag)
1829 {
1830 gchar *name, *info;
1831 gint num_marks, idx;
1832 GDate **dates;
1833 GDate *calDate;
1834
1835 // copy the values into the old marking function.
1836 name = gnc_dense_cal_model_get_name(cal->model, tag);
1837 info = gnc_dense_cal_model_get_info(cal->model, tag);
1838 num_marks = gnc_dense_cal_model_get_instance_count(cal->model, tag);
1839
1840 if (num_marks == 0)
1841 goto cleanup;
1842
1843 dates = g_new0(GDate*, num_marks);
1844 calDate = g_date_new_dmy(1, cal->month, cal->year);
1845
1846 for (idx = 0; idx < num_marks; idx++)
1847 {
1848 dates[idx] = g_date_new();
1849 gnc_dense_cal_model_get_instance(cal->model, tag, idx, dates[idx]);
1850
1851 }
1852 if (g_date_valid(dates[0]))
1853 {
1854 if (g_date_get_julian(dates[0]) < g_date_get_julian(calDate))
1855 {
1856 /* Oops, first marking is earlier than months displayed.
1857 * Choose new first month and recalculate all markings for all
1858 * tags. Their offsets are all wrong with the newly added month(s).
1859 */
1860 _gnc_dense_cal_set_month(cal, g_date_get_month(dates[0]), FALSE);
1861 _gnc_dense_cal_set_year(cal, g_date_get_year(dates[0]), FALSE);
1862
1863 gdc_remove_markings (cal);
1864 gdc_add_markings (cal);
1865 }
1866 else
1867 gdc_mark_add(cal, tag, name, info, num_marks, dates);
1868 }
1869 else
1870 {
1871 g_warning("Bad date, skipped.");
1872 }
1873
1874 for (idx = 0; idx < num_marks; idx++)
1875 {
1876 g_date_free(dates[idx]);
1877 }
1878 g_free(dates);
1879 g_date_free(calDate);
1880
1881 cleanup:
1882 g_free(info);
1883 }
1884
1885 static void
gdc_add_markings(GncDenseCal * cal)1886 gdc_add_markings(GncDenseCal *cal)
1887 {
1888 GList *tags;
1889 tags = gnc_dense_cal_model_get_contained(cal->model);
1890 for (; tags != NULL; tags = tags->next)
1891 {
1892 guint tag = GPOINTER_TO_UINT(tags->data);
1893 gdc_add_tag_markings(cal, tag);
1894 }
1895 g_list_free(tags);
1896 }
1897
1898 static void
gdc_remove_markings(GncDenseCal * cal)1899 gdc_remove_markings(GncDenseCal *cal)
1900 {
1901 GList *tags;
1902 tags = gnc_dense_cal_model_get_contained(cal->model);
1903 for (; tags != NULL; tags = tags->next)
1904 {
1905 guint tag = GPOINTER_TO_UINT(tags->data);
1906 gdc_mark_remove(cal, tag, FALSE);
1907 }
1908 g_list_free(tags);
1909 }
1910
1911 static void
gdc_model_added_cb(GncDenseCalModel * model,guint added_tag,gpointer user_data)1912 gdc_model_added_cb(GncDenseCalModel *model, guint added_tag, gpointer user_data)
1913 {
1914 GncDenseCal *cal = GNC_DENSE_CAL(user_data);
1915 DEBUG("gdc_model_added_cb update\n");
1916 gdc_add_tag_markings(cal, added_tag);
1917 }
1918
1919 static void
gdc_model_update_cb(GncDenseCalModel * model,guint update_tag,gpointer user_data)1920 gdc_model_update_cb(GncDenseCalModel *model, guint update_tag, gpointer user_data)
1921 {
1922 GncDenseCal *cal = GNC_DENSE_CAL (user_data);
1923 gint num_marks = 0;
1924 DEBUG ("gdc_model_update_cb update for tag [%d]\n", update_tag);
1925 num_marks = gnc_dense_cal_model_get_instance_count (cal->model, update_tag);
1926 // We need to redraw if there are no mark, to ensure they're all erased.
1927 gdc_mark_remove (cal, update_tag, num_marks==0);
1928 gdc_add_tag_markings (cal, update_tag);
1929
1930 }
1931
1932 static void
gdc_model_removing_cb(GncDenseCalModel * model,guint remove_tag,gpointer user_data)1933 gdc_model_removing_cb(GncDenseCalModel *model, guint remove_tag, gpointer user_data)
1934 {
1935 GncDenseCal *cal = GNC_DENSE_CAL(user_data);
1936 DEBUG("gdc_model_removing_cb update [%d]\n", remove_tag);
1937 gdc_mark_remove(cal, remove_tag, TRUE);
1938 }
1939
1940 void
gnc_dense_cal_set_model(GncDenseCal * cal,GncDenseCalModel * model)1941 gnc_dense_cal_set_model(GncDenseCal *cal, GncDenseCalModel *model)
1942 {
1943 if (cal->model != NULL)
1944 {
1945 gdc_remove_markings(cal);
1946 g_object_unref(G_OBJECT(cal->model));
1947 cal->model = NULL;
1948 }
1949 cal->model = model;
1950 g_object_ref(G_OBJECT(model));
1951 g_signal_connect(G_OBJECT(cal->model), "added", (GCallback)gdc_model_added_cb, cal);
1952 g_signal_connect(G_OBJECT(cal->model), "update", (GCallback)gdc_model_update_cb, cal);
1953 g_signal_connect(G_OBJECT(cal->model), "removing", (GCallback)gdc_model_removing_cb, cal);
1954
1955 gdc_add_markings(cal);
1956 }
1957
1958 /**
1959 * Marks the given array of GDate*s on the calendar with the given name.
1960 **/
1961 static void
gdc_mark_add(GncDenseCal * dcal,guint tag,gchar * name,gchar * info,guint size,GDate ** dateArray)1962 gdc_mark_add(GncDenseCal *dcal,
1963 guint tag,
1964 gchar *name,
1965 gchar *info,
1966 guint size,
1967 GDate **dateArray)
1968 {
1969 guint i;
1970 gint doc;
1971 gdc_mark_data *newMark;
1972 GDate *d;
1973
1974 if (size == 0)
1975 {
1976 g_error("0 size not allowed\n");
1977 return;
1978 }
1979
1980 newMark = g_new0(gdc_mark_data, 1);
1981 newMark->name = NULL;
1982 if (name)
1983 newMark->name = g_strdup(name);
1984 newMark->info = NULL;
1985 if (info)
1986 newMark->info = g_strdup(info);
1987 newMark->tag = tag;
1988 newMark->ourMarks = NULL;
1989 DEBUG("saving mark with tag [%d]\n", newMark->tag);
1990
1991 for (i = 0; i < size; i++)
1992 {
1993 d = dateArray[i];
1994 doc = gdc_get_doc_offset(dcal, d);
1995 if (doc < 0)
1996 continue;
1997 if (doc >= dcal->numMarks)
1998 {
1999 /* It's not going to get any better, so just
2000 * stop processing. */
2001 break;
2002 }
2003 dcal->marks[doc] = g_list_append(dcal->marks[doc], newMark);
2004 newMark->ourMarks = g_list_append(newMark->ourMarks,
2005 GINT_TO_POINTER(doc));
2006 }
2007 dcal->markData = g_list_append(dcal->markData, (gpointer)newMark);
2008 gnc_dense_cal_draw_to_buffer(dcal);
2009 gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
2010 }
2011
2012 static void
gdc_mark_remove(GncDenseCal * dcal,guint mark_to_remove,gboolean redraw)2013 gdc_mark_remove(GncDenseCal *dcal, guint mark_to_remove, gboolean redraw)
2014 {
2015 GList *iter, *calendar_marks;
2016 gint day_of_cal;
2017 gdc_mark_data *mark_data;
2018
2019 /* Ignore non-realistic marks */
2020 if ((gint)mark_to_remove == -1)
2021 {
2022 DEBUG("mark_to_remove = -1");
2023 return;
2024 }
2025
2026 mark_data = NULL;
2027 for (iter = dcal->markData; iter != NULL; iter = iter->next)
2028 {
2029 mark_data = (gdc_mark_data*)iter->data;
2030 if (mark_data->tag == mark_to_remove)
2031 break;
2032 }
2033 if (iter == NULL)
2034 {
2035 PINFO("couldn't find tag [%d]", mark_to_remove);
2036 return;
2037 }
2038 if (mark_data == NULL)
2039 {
2040 DEBUG("mark_data == null");
2041 return;
2042 }
2043
2044 for (calendar_marks = mark_data->ourMarks; calendar_marks != NULL; calendar_marks = calendar_marks->next)
2045 {
2046 day_of_cal = GPOINTER_TO_INT(calendar_marks->data);
2047 dcal->marks[day_of_cal] = g_list_remove(dcal->marks[day_of_cal], mark_data);
2048 }
2049 g_list_free(mark_data->ourMarks);
2050 dcal->markData = g_list_remove(dcal->markData, mark_data);
2051 g_free(mark_data);
2052
2053 if (redraw)
2054 {
2055 gnc_dense_cal_draw_to_buffer(dcal);
2056 gtk_widget_queue_draw(GTK_WIDGET(dcal->cal_drawing_area));
2057 }
2058 }
2059