GnuCash c935c2f+
Loading...
Searching...
No Matches
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
38static const QofLogModule log_module = "gnc.gui.dense-cal";
39
71static const int DENSE_CAL_DEFAULT_WIDTH = 15;
72static const int DENSE_CAL_DEFAULT_HEIGHT = 105;
73static const int MINOR_BORDER_SIZE = 1;
74static const int COL_BORDER_SIZE = 3;
75
76static void gnc_dense_cal_finalize (GObject *object);
77static void gnc_dense_cal_dispose (GObject *object);
78static void gnc_dense_cal_realize (GtkWidget *widget, gpointer user_data);
79static void gnc_dense_cal_configure (GtkWidget *widget,
80 GdkEventConfigure *event,
81 gpointer user_data);
82static void gnc_dense_cal_draw_to_buffer (GncDenseCal *dcal);
83static gboolean gnc_dense_cal_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data);
84
85static void gdc_reconfig (GncDenseCal *dcal);
86
87static void gdc_free_all_mark_data (GncDenseCal *dcal);
88
89static void _gdc_compute_min_size (GncDenseCal *dcal,
90 guint *min_width, guint *min_height);
91static void _gdc_set_cal_min_size_req (GncDenseCal *dcal);
92static gint gnc_dense_cal_motion_notify (GtkWidget *widget,
93 GdkEventMotion *event);
94static gint gnc_dense_cal_button_press (GtkWidget *widget,
95 GdkEventButton *evt);
96
97static void _gdc_view_option_changed (GtkComboBox *widget, gpointer user_data);
98
99static inline int day_width_at (GncDenseCal *dcal, guint xScale);
100static inline int day_width (GncDenseCal *dcal);
101static inline int day_height_at (GncDenseCal *dcal, guint yScale);
102static inline int day_height (GncDenseCal *dcal);
103static inline int week_width_at (GncDenseCal *dcal, guint xScale);
104static inline int week_width (GncDenseCal *dcal);
105static inline int week_height_at (GncDenseCal *dcal, guint yScale);
106static inline int week_height (GncDenseCal *dcal);
107static inline int col_width_at (GncDenseCal *dcal, guint xScale);
108static inline int col_width (GncDenseCal *dcal);
109
110static inline int col_height (GncDenseCal *dcal);
111static inline int num_cols (GncDenseCal *dcal);
112
113static void _gnc_dense_cal_set_month (GncDenseCal *dcal, GDateMonth mon, gboolean redraw);
114static void _gnc_dense_cal_set_year (GncDenseCal *dcal, guint year, gboolean redraw);
115
120static inline int num_weeks (GncDenseCal *dcal);
125static int num_weeks_per_col (GncDenseCal *dcal);
126
127/* hotspot calculation */
128static gint wheres_this (GncDenseCal *dcal, int x, int y);
129
130static void recompute_x_y_scales (GncDenseCal *dcal);
131static void recompute_mark_storage (GncDenseCal *dcal);
132static void recompute_extents (GncDenseCal *dcal);
133static void populate_hover_window (GncDenseCal *dcal);
134
135static void month_coords (GncDenseCal *dcal, int monthOfCal, GList **outList);
136static void doc_coords (GncDenseCal *dcal, int dayOfCal,
137 int *x1, int *y1, int *x2, int *y2);
138
139static void gdc_mark_add (GncDenseCal *dcal, guint tag, gchar *name,
140 gchar *info, guint size, GDate **dateArray);
141static void gdc_mark_remove (GncDenseCal *dcal, guint mark_to_remove, gboolean redraw);
142
143static void gdc_add_tag_markings (GncDenseCal *cal, guint tag);
144static void gdc_add_markings (GncDenseCal *cal);
145static void gdc_remove_markings (GncDenseCal *cal);
146
147typedef 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
211typedef struct _gdc_mark_data
212{
213 gchar *name;
214 gchar *info;
215 guint tag;
219 GList *ourMarks;
221
222G_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.*/
229static const gchar*
230month_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. */
250static void
251day_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
262static void
263gnc_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
277enum _GdcViewOptsColumns
278{
279 VIEW_OPTS_COLUMN_LABEL = 0,
280 VIEW_OPTS_COLUMN_NUM_MONTHS,
281 VIEW_OPTS_COLUMN_NUM_MONTHS_PER_COLUMN
282};
283
284static GtkListStore *_cal_view_options = NULL;
285static 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
302static void
303gnc_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
476static 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
485GtkWidget*
486gnc_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
496GtkWidget*
497gnc_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
504static void
505recompute_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
514void
515gnc_dense_cal_set_month (GncDenseCal *dcal, GDateMonth mon)
516{
517 _gnc_dense_cal_set_month (dcal, mon, TRUE);
518}
519
520static 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
540void
541gnc_dense_cal_set_year (GncDenseCal *dcal, guint year)
542{
543 _gnc_dense_cal_set_year (dcal, year, TRUE);
544}
545
546static 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
562void
563gnc_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
615guint
616gnc_dense_cal_get_num_months (GncDenseCal *dcal)
617{
618 return dcal->numMonths;
619}
620
621void
622gnc_dense_cal_set_months_per_col (GncDenseCal *dcal, guint monthsPerCol)
623{
624 dcal->monthsPerCol = monthsPerCol;
625 recompute_x_y_scales (dcal);
626}
627
628GDateMonth
629gnc_dense_cal_get_month (GncDenseCal *dcal)
630{
631 return dcal->month;
632}
633
634GDateYear
635gnc_dense_cal_get_year (GncDenseCal *dcal)
636{
637 return dcal->year;
638}
639
640static void
641gnc_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
675static void
676gnc_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
684static void
685gnc_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
697static void
698gnc_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
710static void
711gdc_reconfig (GncDenseCal *dcal)
712{
713 GtkWidget *widget;
714 GtkAllocation alloc;
715 int scale;
716
717 if (dcal->surface)
718 cairo_surface_destroy (dcal->surface);
719
720 widget = GTK_WIDGET(dcal->cal_drawing_area);
721 gtk_widget_get_allocation (widget, &alloc);
722 scale = gtk_widget_get_scale_factor (widget);
723 if (scale < 1)
724 scale = 1;
725 dcal->surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
726 alloc.width * scale,
727 alloc.height * scale);
728 cairo_surface_set_device_scale (dcal->surface, scale, scale);
729 gnc_dense_cal_draw_to_buffer (dcal);
730}
731
732static void
733_gdc_compute_min_size (GncDenseCal *dcal, guint *min_width, guint *min_height)
734{
735 if (min_width != NULL)
736 {
737 *min_width =
738 (dcal->leftPadding * 2)
739 + (num_cols (dcal) * (col_width_at (dcal, dcal->min_x_scale)
740 + dcal->month_side_bar_width))
741 + ((num_cols (dcal) - 1) * COL_BORDER_SIZE);
742 }
743
744 if (min_height != NULL)
745 {
746 *min_height =
747 (dcal->topPadding * 2)
748 + MINOR_BORDER_SIZE
749 + dcal->day_top_bar_height
750 + (num_weeks_per_col (dcal)
751 * week_height_at (dcal, dcal->min_y_scale));
752 }
753}
754
755static void
756recompute_x_y_scales (GncDenseCal *dcal)
757{
758 int denom;
759 int width, height;
760
761 width = DENSE_CAL_DEFAULT_WIDTH;
762 height = DENSE_CAL_DEFAULT_HEIGHT;
763 if (dcal->initialized)
764 {
765 GtkAllocation alloc;
766 gtk_widget_get_allocation (GTK_WIDGET(dcal->cal_drawing_area), &alloc);
767 width = alloc.width;
768 height = alloc.height;
769 }
770
771 /* FIXME: there's something slightly wrong in the x_scale computation that
772 * lets us draw larger than our area. */
773 denom = 7 * num_cols (dcal);
774 g_assert (denom != 0);
775 dcal->x_scale = ((gint)(width
776 - (dcal->leftPadding * 2)
777 - (num_cols (dcal) * ((8 * MINOR_BORDER_SIZE)
778 + dcal->month_side_bar_width))
779 - ((num_cols (dcal) - 1) * COL_BORDER_SIZE))
780 / denom);
781 dcal->x_scale = MAX(dcal->x_scale, dcal->min_x_scale);
782
783 denom = num_weeks_per_col (dcal);
784 g_assert (denom != 0);
785 dcal->y_scale = ((gint)(height
786 - (dcal->topPadding * 2)
787 - MINOR_BORDER_SIZE
788 - dcal->day_top_bar_height
789 - (num_weeks_per_col (dcal) - 1
790 * MINOR_BORDER_SIZE))
791 / denom);
792 dcal->y_scale = MAX(dcal->y_scale, dcal->min_y_scale);
793
794 _gdc_set_cal_min_size_req (dcal);
795}
796
797static void
798gdc_free_all_mark_data (GncDenseCal *dcal)
799{
800 int i;
801 GList *l;
802 for (i = 0; i < dcal->numMarks; i++)
803 {
804 /* Each of these just contains an elt of dcal->markData,
805 * which we're about to free, below... */
806 g_list_free (dcal->marks[i]);
807 }
808 g_free (dcal->marks);
809 dcal->marks = NULL;
810 /* Remove the old mark data. */
811 for (l = dcal->markData; l; l = l->next)
812 {
813 gdc_mark_data *mark = l->data;
814 g_list_free (mark->ourMarks);
815 g_free (mark->name);
816 g_free (mark->info);
817 g_free (mark);
818 }
819 g_list_free (dcal->markData);
820 dcal->markData = NULL;
821}
822
823static void
824recompute_mark_storage (GncDenseCal *dcal)
825{
826 if (dcal->marks == NULL)
827 goto createNew;
828 gdc_free_all_mark_data (dcal);
829
830createNew:
831 dcal->numMarks = num_weeks (dcal) * 7;
832 dcal->marks = g_new0 (GList*, dcal->numMarks);
833 if (dcal->model)
834 gdc_add_markings (dcal);
835}
836
837static gint
838get_week_of_year (GncDenseCal *dcal, GDate *d)
839{
840 GDateWeekday fwd, lwd;
841 GDateYear year;
842 guint day;
843 GDate first, last;
844 guint ret;
845 gint monday_offset = 1;
846 gint day_offset = 0;
847
848 g_return_val_if_fail (g_date_valid (d), 0);
849
850 year = g_date_get_year (d);
851
852 if (!d->dmy)
853 return 0;
854
855 g_date_clear (&first, 1);
856 g_date_set_dmy (&first, 1, 1, year);
857
858 fwd = g_date_get_weekday (&first);
859
860 day_offset = (fwd + 7 - dcal->day_of_week_start) % 7;
861
862 if (dcal->day_of_week_start == G_DATE_SUNDAY) //Su,M,T,W,T,F,Sa
863 monday_offset = 1;
864 else if (dcal->day_of_week_start == G_DATE_MONDAY) //M,T,W,T,F,Sa,Su
865 monday_offset = 0;
866 else if (dcal->day_of_week_start == G_DATE_TUESDAY) //T,W,T,F,Sa,Su,M
867 monday_offset = 6;
868 else if (dcal->day_of_week_start == G_DATE_WEDNESDAY) //W,T,F,Sa,Su,M,T
869 monday_offset = 5;
870 else if (dcal->day_of_week_start == G_DATE_THURSDAY) //T,F,Sa,Su,M,T,W
871 monday_offset = 4;
872 else if (dcal->day_of_week_start == G_DATE_FRIDAY) //F,Sa,Su,M,T,W,T
873 monday_offset = 3;
874 else if (dcal->day_of_week_start == G_DATE_SATURDAY) //Sa,Su,M,T,W,T,F,
875 monday_offset = 2;
876 else
877 monday_offset = 1;
878
879 day = g_date_get_day_of_year (d) - 1;
880
881 g_date_clear (&last, 1);
882 g_date_set_dmy (&last, 31, 12, year - 1);
883 lwd = g_date_get_weekday (&last);
884 gint lday_offset = 6 - ((lwd + 7 - dcal->day_of_week_start) % 7);
885 gint addone = 1;
886
887 if (lday_offset)
888 addone = 0;
889
890 ret = ((day + day_offset)/7U + ((day_offset <= monday_offset) ? addone : 0));
891
892 return ret;
893}
894
895static gint
896get_weeks_in_year (GncDenseCal *dcal, GDateYear year)
897{
898 GDate d;
899
900 g_return_val_if_fail (g_date_valid_year (year), 0);
901
902 g_date_clear (&d, 1);
903 g_date_set_dmy (&d, 1, 1, year);
904 if (g_date_get_weekday (&d) == dcal->day_of_week_start) return 53;
905 g_date_set_dmy (&d, 31, 12, year);
906 if (g_date_get_weekday (&d) == dcal->day_of_week_start) return 53;
907 if (g_date_is_leap_year (year))
908 {
909 g_date_set_dmy (&d, 2, 1, year);
910 if (g_date_get_weekday (&d) == dcal->day_of_week_start) return 53;
911 g_date_set_dmy (&d, 30, 12, year);
912 if (g_date_get_weekday (&d) == dcal->day_of_week_start) return 53;
913 }
914 return 52;
915}
916
917static void
918recompute_extents (GncDenseCal *dcal)
919{
920 GDate date;
921 gint start_week, end_week;
922
923 g_date_clear (&date, 1);
924 g_date_set_dmy (&date, 1, dcal->month, dcal->year);
925 start_week = get_week_of_year (dcal, &date);
926 g_date_add_months (&date, dcal->numMonths);
927 end_week = get_week_of_year (dcal, &date);
928
929 if (g_date_get_year (&date) != dcal->year)
930 end_week += get_weeks_in_year (dcal, dcal->year);
931
932 dcal->num_weeks = end_week - start_week + 1;
933}
934
935static void
936free_rect (gpointer data, gpointer user_data)
937{
938 g_free ((GdkRectangle*)data);
939}
940
941static gboolean
942gnc_dense_cal_draw (GtkWidget *widget, cairo_t *cr, gpointer user_data)
943{
944 GncDenseCal *dcal;
945
946 g_return_val_if_fail (widget != NULL, FALSE);
947 g_return_val_if_fail (GNC_IS_DENSE_CAL(user_data), FALSE);
948
949 dcal = GNC_DENSE_CAL(user_data);
950
951 cairo_save (cr);
952 cairo_set_source_surface (cr, dcal->surface, 0, 0);
953 cairo_paint (cr);
954 cairo_restore (cr);
955 return TRUE;
956}
957
958static void
959gnc_dense_cal_draw_to_buffer (GncDenseCal *dcal)
960{
961 GtkWidget *widget;
962 GtkStyleContext *stylectxt;
963 GtkStateFlags state_flags;
964 GtkAllocation alloc;
965 gint i;
966 int maxWidth;
967 PangoLayout *layout;
968 cairo_t *cr;
969 gchar *primary_color_class, *secondary_color_class, *marker_color_class;
970
971 DEBUG("drawing");
972 widget = GTK_WIDGET(dcal);
973
974 if (!dcal->surface)
975 return;
976
977 cr = cairo_create (dcal->surface);
978 layout = gtk_widget_create_pango_layout (GTK_WIDGET(dcal), NULL);
979
980 gtk_widget_get_allocation (GTK_WIDGET(dcal->cal_drawing_area), &alloc);
981 stylectxt = gtk_widget_get_style_context (GTK_WIDGET(dcal->cal_drawing_area));
982 state_flags = gtk_style_context_get_state (stylectxt);
983
984 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_BACKGROUND);
985 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_CALENDAR);
986
987 gtk_render_background (stylectxt, cr, 0, 0,
988 alloc.width,
989 alloc.height);
990
991 gtk_style_context_remove_class (stylectxt, GTK_STYLE_CLASS_BACKGROUND);
992
993 /* get the colors */
994 {
995 GdkRGBA color;
996 gchar *class_extension = NULL;
997
998 gtk_style_context_get_color (stylectxt, GTK_STATE_FLAG_NORMAL, &color);
999
1000 if (gnc_is_dark_theme (&color))
1001 class_extension = "-dark";
1002
1003 primary_color_class = g_strconcat ("primary", class_extension, NULL);
1004 secondary_color_class = g_strconcat ("secondary", class_extension, NULL);
1005 marker_color_class = g_strconcat ("markers", class_extension, NULL);
1006 }
1007
1008 /* lets confirm text height size */
1009 pango_layout_set_text (layout, "S", -1);
1010 pango_layout_get_pixel_size (layout, NULL, &dcal->label_height);
1011 dcal->month_side_bar_width = dcal->label_height + (dcal->bar_label_padding * 2);
1012 dcal->day_top_bar_height = dcal->label_height + (dcal->bar_label_padding * 2);
1013
1014 /* Fill in alternating month colors. */
1015 {
1016 gint i;
1017 GdkRectangle *rect;
1018 GList *mcList, *mcListIter;
1019
1020 /* reset all of the month position offsets. */
1021 for (i = 0; i < 12; i++)
1022 {
1023 dcal->monthPositions[i].x = dcal->monthPositions[i].y = -1;
1024 }
1025
1026 gtk_style_context_save (stylectxt);
1027
1028 /* Paint the weeks for the upcoming N months. */
1029 for (i = 0; i < dcal->numMonths; i++)
1030 {
1031 mcList = NULL;
1032 month_coords (dcal, i, &mcList);
1033 dcal->monthPositions[i].x = floor (i / dcal->monthsPerCol)
1034 * (col_width (dcal) + COL_BORDER_SIZE);
1035 dcal->monthPositions[i].y = ((GdkRectangle*)mcList->next->next->next->data)->y;
1036 for (mcListIter = mcList; mcListIter != NULL; mcListIter = mcListIter->next)
1037 {
1038 rect = (GdkRectangle*)mcListIter->data;
1039 gtk_style_context_save (stylectxt);
1040
1041 if (i % 2 == 0)
1042 gtk_style_context_add_class (stylectxt, primary_color_class);
1043 else
1044 gtk_style_context_add_class (stylectxt, secondary_color_class);
1045
1046 gtk_render_background (stylectxt, cr, rect->x, rect->y, rect->width, rect->height);
1047 gtk_style_context_restore (stylectxt);
1048 }
1049 g_list_foreach (mcList, free_rect, NULL);
1050 g_list_free (mcList);
1051 }
1052 gtk_style_context_restore (stylectxt);
1053 }
1054
1055 /* Highlight the marked days. */
1056 {
1057 int i;
1058 int x1, x2, y1, y2;
1059
1060 gtk_style_context_save (stylectxt);
1061 gtk_style_context_add_class (stylectxt, marker_color_class);
1062 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_VIEW);
1063 gtk_style_context_set_state (stylectxt, GTK_STATE_FLAG_SELECTED);
1064
1065 for (i = 0; i < dcal->numMarks; i++)
1066 {
1067 if (dcal->marks[i] != NULL)
1068 {
1069 int center_x, center_y, radius;
1070
1071 doc_coords(dcal, i, &x1, &y1, &x2, &y2);
1072 center_x = (x1 + x2 ) / 2;
1073 center_y = (y1 + y2 ) / 2;
1074 radius = MIN((x2 - x1), (y2 - y1)) * .75;
1075
1076 // try to compensate for row height/width being odd or even
1077 if (((y2 - y1) % 2) != 0)
1078 center_y = center_y + 1;
1079
1080 if (((x2 - x1) % 2) != 0)
1081 center_x = center_x + 1;
1082
1083 gtk_render_background (stylectxt, cr,
1084 center_x - (radius + 2), center_y - radius,
1085 (radius * 2) + 4, radius * 2);
1086 }
1087 }
1088 gtk_style_context_restore (stylectxt);
1089 }
1090
1091 for (i = 0; i < num_cols (dcal); i++)
1092 {
1093 GdkRGBA color;
1094 gint x, y, w, h;
1095 gint j;
1096
1097 cairo_save (cr);
1098 gdk_rgba_parse (&color, "black");
1099
1100 x = dcal->leftPadding
1101 + (i * (col_width (dcal) + COL_BORDER_SIZE))
1102 + dcal->month_side_bar_width + 1;
1103 y = dcal->topPadding + dcal->day_top_bar_height;
1104 w = col_width (dcal) - COL_BORDER_SIZE - dcal->month_side_bar_width;
1105 h = col_height (dcal);
1106
1107 gtk_style_context_save (stylectxt);
1108
1109 /* draw the outside border [inside the month labels] */
1110 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_FRAME);
1111
1112 gtk_render_frame (stylectxt, cr, x, y, w + 1, h + 1);
1113
1114 gnc_style_context_get_border_color (stylectxt, state_flags, &color);
1115 cairo_set_source_rgb (cr, color.red, color.green, color.blue);
1116 cairo_set_line_width (cr, 1);
1117
1118 /* draw the week separations */
1119 for (j = 0; j < num_weeks_per_col (dcal); j++)
1120 {
1121 gint wy = y + (j * week_height (dcal));
1122 cairo_move_to (cr, x, wy + 0.5);
1123 cairo_line_to (cr, x + w, wy + 0.5);
1124 cairo_stroke (cr);
1125 }
1126
1127 /* draw the day separations */
1128 for (j = 1; j < 7; j++)
1129 {
1130 gint dx = x + (j * day_width (dcal));
1131 cairo_move_to (cr, dx + 0.5, y);
1132 cairo_line_to (cr, dx + 0.5, y + col_height (dcal));
1133 cairo_stroke (cr);
1134 }
1135 cairo_restore (cr);
1136 gtk_style_context_restore (stylectxt);
1137
1138
1139 /* draw the day of the week labels */
1140 pango_layout_set_text (layout, "88", -1);
1141 pango_layout_get_pixel_size (layout, &maxWidth, NULL);
1142
1143 if (dcal->x_scale > maxWidth)
1144 {
1145 gtk_style_context_save (stylectxt);
1146 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_HEADER);
1147
1148 gtk_render_background (stylectxt, cr, x,
1149 y - dcal->day_top_bar_height,
1150 (day_width(dcal) * 7) + 1,
1151 dcal->day_top_bar_height);
1152
1153 for (j = 0; j < 7; j++)
1154 {
1155 int day_label_width;
1156 gint label_x_offset, label_y_offset;
1157 gint day_label_str_len = 4;
1158 gchar day_label_str[day_label_str_len + 1];
1159 day_label (day_label_str, day_label_str_len, (j + dcal->day_of_week_start) % 7);
1160 pango_layout_set_text (layout, day_label_str, -1);
1161 pango_layout_get_pixel_size (layout, &day_label_width, NULL);
1162 label_x_offset = x
1163 + (j * day_width (dcal))
1164 + (day_width (dcal) / 2)
1165 - (day_label_width / 2);
1166 label_y_offset = y - dcal->day_top_bar_height + dcal->bar_label_padding;
1167 pango_layout_set_text (layout, day_label_str, -1);
1168 gtk_render_layout (stylectxt, cr, label_x_offset, label_y_offset, layout);
1169 }
1170 gtk_style_context_restore (stylectxt);
1171 }
1172 }
1173
1174 /* Month labels. */
1175 {
1176 gint i;
1177 gint x_offset = dcal->leftPadding;
1178
1179 gtk_style_context_save (stylectxt);
1180 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_HEADER);
1181
1182 for (i = 0; i < 12; i++)
1183 {
1184 if (dcal->monthPositions[i].x == -1)
1185 break;
1186
1187 gtk_render_background (stylectxt, cr, dcal->monthPositions[i].x + x_offset,
1188 dcal->topPadding,
1189 dcal->month_side_bar_width + 1,
1190 col_height(dcal) + dcal->day_top_bar_height + 1);
1191 }
1192
1193 for (i = 0; i < 12; i++)
1194 {
1195 guint idx;
1196
1197 if (dcal->monthPositions[i].x == -1)
1198 break;
1199 idx = (dcal->month - 1 + i) % 12;
1200 pango_layout_set_text (layout, month_name (idx), -1);
1201 cairo_save (cr);
1202 cairo_translate (cr, dcal->monthPositions[i].x + x_offset, dcal->monthPositions[i].y);
1203 cairo_rotate (cr, -G_PI / 2.);
1204 gtk_render_layout (stylectxt, cr, 0, dcal->bar_label_padding, layout);
1205 cairo_restore (cr);
1206 }
1207 gtk_style_context_restore (stylectxt);
1208 }
1209
1210 /* Day number strings [dates] */
1211 {
1212 GDate d, eoc;
1213 gint doc;
1214 gchar dayNumBuf[4];
1215 gint numW, numH;
1216 gint x1, y1, x2, y2, w, h;
1217
1218 GDate now;
1219 g_date_clear (&now, 1);
1220 gnc_gdate_set_today (&now);
1221 gboolean today_found = FALSE;
1222
1223 gtk_style_context_save (stylectxt);
1224 gtk_style_context_add_class (stylectxt, "day-number");
1225
1226 cairo_save (cr);
1227 g_date_set_dmy (&d, 1, dcal->month, dcal->year);
1228 eoc = d;
1229 g_date_add_months (&eoc, dcal->numMonths);
1230 for (doc = 0; g_date_get_julian (&d) < g_date_get_julian (&eoc); g_date_add_days (&d, 1), doc++)
1231 {
1232 doc_coords (dcal, doc, &x1, &y1, &x2, &y2);
1233 memset (dayNumBuf, 0, 4);
1234 snprintf (dayNumBuf, 4, "%d", g_date_get_day(&d));
1235 pango_layout_set_text (layout, dayNumBuf, -1);
1236 pango_layout_get_pixel_size (layout, &numW, &numH);
1237 w = (x2 - x1) + 1;
1238 h = (y2 - y1) + 1;
1239
1240 if (!today_found && g_date_compare (&d, &now) == 0)
1241 {
1242 GtkBorder border;
1243
1244 gtk_style_context_save (stylectxt);
1245 gtk_style_context_add_class (stylectxt, marker_color_class);
1246 gtk_style_context_add_class (stylectxt, GTK_STYLE_CLASS_FRAME);
1247
1248 gtk_style_context_get_border (stylectxt, GTK_STATE_FLAG_NORMAL, &border);
1249
1250 today_found = TRUE;
1251
1252 if (border.left + border.right != 0)
1253 {
1254 GtkCssProvider *provider = gtk_css_provider_new ();
1255 gchar *frame_css = ".marker-border {\n border-color:black;\n}\n";
1256
1257 gint dayw = day_width (dcal);
1258 gint dayh = day_height (dcal);
1259 gint bw = (border.left + border.right) / 2;
1260
1261 gtk_css_provider_load_from_data (provider, frame_css, -1, NULL);
1262 gtk_style_context_add_provider (stylectxt, GTK_STYLE_PROVIDER(provider),
1263 GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
1264 g_object_unref (provider);
1265
1266 gtk_style_context_add_class (stylectxt, "marker-border");
1267
1268 gtk_render_frame (stylectxt, cr, x1 - (dayw / 4) + 3,
1269 y1 - (dayh / 4) + 2,
1270 dayw - 4 - bw,
1271 dayh - 4 - bw);
1272
1273 gtk_style_context_remove_class (stylectxt, "marker-border");
1274 }
1275 gtk_style_context_restore (stylectxt);
1276 }
1277 gtk_render_layout (stylectxt, cr, x1 + (w / 2) - (numW / 2), y1 + (h / 2) - (numH / 2), layout);
1278 }
1279 cairo_restore (cr);
1280 gtk_style_context_restore (stylectxt);
1281 }
1282
1283 gtk_widget_get_allocation (widget, &alloc);
1284 gtk_widget_queue_draw_area (GTK_WIDGET(dcal),
1285 alloc.x,
1286 alloc.y,
1287 alloc.width,
1288 alloc.height);
1289
1290 g_free (primary_color_class);
1291 g_free (secondary_color_class);
1292 g_free (marker_color_class);
1293
1294 g_object_unref (layout);
1295 cairo_destroy (cr);
1296}
1297
1298static void
1299populate_hover_window (GncDenseCal *dcal)
1300{
1301 GtkWidget *w;
1302 GDate *date;
1303
1304 if (dcal->doc >= 0)
1305 {
1306 GObject *o;
1307 GtkListStore *model;
1308 GList *l;
1309
1310 w = GTK_WIDGET(g_object_get_data (G_OBJECT(dcal->transPopup), "dateLabel"));
1311 date = g_date_new_dmy (1, dcal->month, dcal->year);
1312 g_date_add_days (date, dcal->doc);
1313 /* Note: the ISO date format (%F or equivalently
1314 * %Y-%m-%d) is not a good idea here since many
1315 * locales will want to use a very different date
1316 * format. Please leave the specification of the date
1317 * format up to the preference. */
1318 time64 t64 = gnc_dmy2time64_neutral (g_date_get_day (date),
1319 g_date_get_month (date),
1320 g_date_get_year (date));
1321 gchar date_buff [MAX_DATE_LENGTH + 1];
1322 qof_print_date_buff (date_buff, MAX_DATE_LENGTH, t64);
1323 gtk_label_set_text (GTK_LABEL(w), date_buff);
1324
1325 o = G_OBJECT(dcal->transPopup);
1326 model = GTK_LIST_STORE(g_object_get_data (o, "model"));
1327 gtk_list_store_clear (model);
1328 for (l = dcal->marks[dcal->doc]; l; l = l->next)
1329 {
1330 GtkTreeIter iter;
1331 gdc_mark_data *gdcmd;
1332
1333 gdcmd = (gdc_mark_data*)l->data;
1334 gtk_list_store_insert (model, &iter, INT_MAX);
1335 gtk_list_store_set (model, &iter, 0, (gdcmd->name ? gdcmd->name : _("(unnamed)")),
1336 1, gdcmd->info, -1);
1337 }
1338
1339 // if there are no rows, add one
1340 if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL(model), NULL) == 0)
1341 {
1342 GtkTreeIter iter;
1343 gtk_list_store_insert (model, &iter, -1);
1344 }
1345
1346 // make sure all pending events are processed
1347 while(gtk_events_pending ())
1348 gtk_main_iteration ();
1349
1350 g_date_free (date);
1351 }
1352}
1353
1354static const int POPUP_OFFSET = 5; // offset for popup window
1355
1356static void
1357popup_window_move (GncDenseCal *dcal, GdkEvent *event)
1358{
1359 GtkAllocation alloc;
1360 gdouble x_root, y_root;
1361 gint win_xpos, win_ypos;
1362
1363 if (event->type == GDK_BUTTON_PRESS)
1364 {
1365 x_root = ((GdkEventButton*)event)->x_root;
1366 y_root = ((GdkEventButton*)event)->y_root;
1367 }
1368 else
1369 {
1370 x_root = ((GdkEventMotion*)event)->x_root;
1371 y_root = ((GdkEventMotion*)event)->y_root;
1372 }
1373 win_xpos = x_root + POPUP_OFFSET;
1374 win_ypos = y_root + POPUP_OFFSET;
1375
1376 gtk_widget_get_allocation (GTK_WIDGET(dcal->transPopup), &alloc);
1377
1378 if (x_root + POPUP_OFFSET + alloc.width > dcal->screen_width)
1379 win_xpos = x_root - 2 - alloc.width;
1380
1381 if (y_root + POPUP_OFFSET + alloc.height > dcal->screen_height)
1382 win_ypos = y_root - 2 - alloc.height;
1383
1384 gtk_window_move (GTK_WINDOW(dcal->transPopup), win_xpos, win_ypos);
1385}
1386
1387static gint
1388gnc_dense_cal_button_press (GtkWidget *widget,
1389 GdkEventButton *evt)
1390{
1391 GdkWindow *win = gdk_screen_get_root_window (gtk_widget_get_screen (widget));
1392 GdkMonitor *mon = gdk_display_get_monitor_at_window (gtk_widget_get_display (widget), win);
1393 GdkRectangle work_area_size;
1394 GncDenseCal *dcal = GNC_DENSE_CAL(widget);
1395
1396 gdk_monitor_get_workarea (mon, &work_area_size);
1397
1398 dcal->screen_width = work_area_size.width;
1399 dcal->screen_height = work_area_size.height;
1400
1401 dcal->doc = wheres_this (dcal, evt->x, evt->y);
1402 dcal->showPopup = ~(dcal->showPopup);
1403 if (dcal->showPopup && dcal->doc >= 0)
1404 {
1405 // Do the move twice in case the WM is ignoring the first one
1406 // because the window hasn't been shown, yet. The WM is free
1407 // to ignore our move and place windows according to it's own
1408 // strategy, but hopefully it'll listen to us. Certainly the
1409 // second move after show_all'ing the window should do the
1410 // trick with a bit of flicker.
1411 gtk_window_move (GTK_WINDOW(dcal->transPopup), evt->x_root + POPUP_OFFSET,
1412 evt->y_root + POPUP_OFFSET);
1413
1414 populate_hover_window (dcal);
1415 gtk_widget_queue_resize (GTK_WIDGET(dcal->transPopup));
1416 gtk_widget_show_all (GTK_WIDGET(dcal->transPopup));
1417
1418 popup_window_move (dcal, (GdkEvent*)evt);
1419 }
1420 else
1421 {
1422 dcal->doc = -1;
1423 gtk_widget_hide (GTK_WIDGET(dcal->transPopup));
1424 }
1425 return TRUE;
1426}
1427
1428static gint
1429gnc_dense_cal_motion_notify (GtkWidget *widget,
1430 GdkEventMotion *event)
1431{
1432 GncDenseCal *dcal;
1433 gint doc;
1434 int unused;
1435 GdkModifierType unused2;
1436
1437 dcal = GNC_DENSE_CAL(widget);
1438 if (!dcal->showPopup)
1439 return FALSE;
1440
1441 /* As per https://www.gtk.org/tutorial/sec-eventhandling.html */
1442 if (event->is_hint)
1443 {
1444 GdkSeat *seat = gdk_display_get_default_seat (gdk_window_get_display (event->window));
1445 GdkDevice *pointer = gdk_seat_get_pointer (seat);
1446
1447 gdk_window_get_device_position (event->window, pointer, &unused, &unused, &unused2);
1448 }
1449
1450 doc = wheres_this (dcal, event->x, event->y);
1451 if (doc >= 0)
1452 {
1453 if (dcal->doc != doc) // if we are on the same day, no need to reload
1454 {
1455 dcal->doc = doc;
1456 populate_hover_window (dcal);
1457 gtk_widget_queue_resize (GTK_WIDGET(dcal->transPopup));
1458 gtk_widget_show_all (GTK_WIDGET(dcal->transPopup));
1459 }
1460 popup_window_move (dcal, (GdkEvent*)event);
1461 }
1462 else
1463 {
1464 dcal->doc = -1;
1465 gtk_widget_hide (GTK_WIDGET(dcal->transPopup));
1466 }
1467 return TRUE;
1468}
1469
1470
1471static void
1472_gdc_view_option_changed (GtkComboBox *widget, gpointer user_data)
1473{
1474 GtkTreeIter iter;
1475 GtkTreeModel *model;
1476 gint months_val;
1477
1478 model = GTK_TREE_MODEL(gtk_combo_box_get_model (widget));
1479 if (!gtk_combo_box_get_active_iter (widget, &iter))
1480 return;
1481 gtk_tree_model_get (model, &iter, VIEW_OPTS_COLUMN_NUM_MONTHS, &months_val, -1);
1482 DEBUG("changing to %d months", months_val);
1483 gnc_dense_cal_set_num_months (GNC_DENSE_CAL(user_data), months_val);
1484}
1485
1486static inline int
1487day_width_at (GncDenseCal *dcal, guint xScale)
1488{
1489 return xScale + MINOR_BORDER_SIZE;
1490}
1491
1492static inline int
1493day_width (GncDenseCal *dcal)
1494{
1495 return day_width_at (dcal, dcal->x_scale);
1496}
1497
1498static inline int
1499day_height_at (GncDenseCal *dcal, guint yScale)
1500{
1501 return yScale + MINOR_BORDER_SIZE;
1502}
1503
1504static inline int
1505day_height (GncDenseCal *dcal)
1506{
1507 return day_height_at (dcal, dcal->y_scale);
1508}
1509
1510static inline int
1511week_width_at (GncDenseCal *dcal, guint xScale)
1512{
1513 return day_width_at (dcal, xScale) * 7;
1514}
1515
1516static inline int
1517week_width (GncDenseCal *dcal)
1518{
1519 return week_width_at (dcal, dcal->x_scale);
1520}
1521
1522static inline int
1523week_height_at (GncDenseCal *dcal, guint yScale)
1524{
1525 return day_height_at (dcal, yScale);
1526}
1527
1528static inline int
1529week_height (GncDenseCal *dcal)
1530{
1531 return week_height_at (dcal, dcal->y_scale);
1532}
1533
1534static inline int
1535col_width_at (GncDenseCal *dcal, guint xScale)
1536{
1537 return (week_width_at (dcal, xScale)
1538 + dcal->month_side_bar_width
1539 + COL_BORDER_SIZE);
1540}
1541
1542static inline int
1543col_width (GncDenseCal *dcal)
1544{
1545 return col_width_at (dcal, dcal->x_scale);
1546}
1547
1548static inline int
1549col_height (GncDenseCal *dcal)
1550{
1551 return week_height (dcal) * num_weeks_per_col (dcal);
1552}
1553
1554static inline int
1555num_cols (GncDenseCal *dcal)
1556{
1557 return ceil ((float)dcal->numMonths / (float)dcal->monthsPerCol);
1558}
1559
1560static inline int
1561num_weeks (GncDenseCal *dcal)
1562{
1563 return dcal->num_weeks;
1564}
1565
1566static
1567int num_weeks_per_col (GncDenseCal *dcal)
1568{
1569 int num_weeks_toRet, numCols, i;
1570 GDate *start, *end;
1571 int startWeek, endWeek;
1572
1573 start = g_date_new ();
1574 end = g_date_new ();
1575
1576 num_weeks_toRet = 0;
1577 numCols = num_cols (dcal);
1578
1579 for (i = 0; i < numCols; i++)
1580 {
1581 g_date_set_dmy (start, 1,
1582 ((dcal->month - 1
1583 + (i * dcal->monthsPerCol)) % 12)
1584 + 1,
1585 dcal->year + floor ((dcal->month - 1
1586 + (i * dcal->monthsPerCol))
1587 / 12));
1588 *end = *start;
1589 /* Add the smaller of (the number of months in the
1590 * calendar-display, minus the number of months shown in the
1591 * previous columns) or (the number of months in a column) */
1592 g_date_add_months (end, MIN(dcal->numMonths,
1593 MIN(dcal->monthsPerCol,
1594 dcal->numMonths
1595 - ((i - 1)
1596 * dcal->monthsPerCol))));
1597 g_date_subtract_days (end, 1);
1598 startWeek = get_week_of_year (dcal, start);
1599 endWeek = get_week_of_year (dcal, end);
1600
1601 if (endWeek < startWeek)
1602 endWeek += get_weeks_in_year (dcal, g_date_get_year (start));
1603
1604 num_weeks_toRet = MAX(num_weeks_toRet, (endWeek - startWeek) + 1);
1605 }
1606 g_date_free (start);
1607 g_date_free (end);
1608 return num_weeks_toRet;
1609}
1610
1617static void
1618month_coords (GncDenseCal *dcal, int monthOfCal, GList **outList)
1619{
1620 gint weekRow, colNum, previousMonthsInCol, monthOffset;
1621 gint start;
1622 GDate *startD, *endD;
1623 GdkRectangle *rect;
1624 gint startWk, endWk;
1625
1626 if (monthOfCal > dcal->numMonths)
1627 return;
1628
1629 colNum = floor (monthOfCal / dcal->monthsPerCol);
1630 monthOffset = colNum * dcal->monthsPerCol;
1631 previousMonthsInCol = MAX(0, (monthOfCal % dcal->monthsPerCol));
1632
1633 startD = g_date_new ();
1634 endD = g_date_new ();
1635
1636 /* Calculate the number of weeks in the column before the month we're
1637 * interested in. */
1638 weekRow = 0;
1639 if (previousMonthsInCol > 0)
1640 {
1641 g_date_set_dmy (startD, 1,
1642 ((dcal->month - 1 + monthOffset) % 12) + 1,
1643 dcal->year + floor ((dcal->month - 1 + monthOffset) / 12));
1644 /* get the week of the top of the column */
1645 startWk = get_week_of_year (dcal, startD);
1646 /* get the week of the end of the previous months */
1647 *endD = *startD;
1648 g_date_add_months (endD, previousMonthsInCol);
1649 g_date_subtract_days (endD, 1);
1650 endWk = get_week_of_year (dcal, endD);
1651
1652 if (endWk < startWk)
1653 endWk += get_weeks_in_year (dcal, g_date_get_year (startD));
1654
1655 /* determine how many weeks are before the month we're
1656 * interested in. */
1657 weekRow = endWk - startWk;
1658
1659 gint end_of_week = dcal->day_of_week_start + 6;
1660 if (end_of_week > 7)
1661 end_of_week = end_of_week - 7;
1662
1663 if (g_date_get_weekday (endD) == end_of_week)
1664 weekRow++;
1665 }
1666
1667 g_date_set_dmy (startD, 1,
1668 ((dcal->month - 1 + monthOfCal) % 12) + 1,
1669 dcal->year + floor ((dcal->month - 1 + monthOfCal) / 12));
1670
1671 *endD = *startD;
1672 g_date_add_months (endD, 1);
1673 g_date_subtract_days (endD, 1);
1674
1675 /* Get the first week. */
1676 {
1677 start = (g_date_get_weekday (startD) + 7 - dcal->day_of_week_start) % 7;
1678
1679 rect = g_new0 (GdkRectangle, 1);
1680 rect->x = dcal->leftPadding
1681 + MINOR_BORDER_SIZE
1682 + (colNum * (col_width (dcal) + COL_BORDER_SIZE))
1683 + dcal->month_side_bar_width
1684 + (start * day_width (dcal));
1685 rect->y = dcal->topPadding
1686 + dcal->day_top_bar_height
1687 + MINOR_BORDER_SIZE
1688 + (weekRow * week_height (dcal));
1689 rect->width = (7 - start) * day_width (dcal);
1690 rect->height = week_height (dcal);
1691 *outList = g_list_append (*outList, (gpointer)rect);
1692 rect = NULL;
1693 }
1694
1695 /* Get the middle weeks. */
1696 {
1697 gint i;
1698 gint weekStart = get_week_of_year (dcal, startD) + 1;
1699 gint weekEnd = get_week_of_year (dcal, endD);
1700
1701 for (i = weekStart; i < weekEnd; i++)
1702 {
1703 rect = g_new0 (GdkRectangle, 1);
1704 rect->x = dcal->leftPadding
1705 + MINOR_BORDER_SIZE
1706 + dcal->month_side_bar_width
1707 + (colNum * (col_width (dcal) + COL_BORDER_SIZE));
1708 rect->y = dcal->topPadding
1709 + dcal->day_top_bar_height
1710 + MINOR_BORDER_SIZE
1711 + ((weekRow + (i - weekStart) + 1) * week_height (dcal));
1712 rect->width = week_width (dcal);
1713 rect->height = week_height (dcal);
1714
1715 *outList = g_list_append (*outList, (gpointer)rect);
1716 rect = NULL;
1717 }
1718 }
1719
1720 /* Get the last week. */
1721 {
1722 gint start_week_of_year = get_week_of_year (dcal, startD);
1723 gint end_week_of_year = get_week_of_year (dcal, endD);
1724
1725 rect = g_new0 (GdkRectangle, 1);
1726 rect->x = dcal->leftPadding
1727 + MINOR_BORDER_SIZE
1728 + dcal->month_side_bar_width
1729 + (colNum * (col_width (dcal) + COL_BORDER_SIZE));
1730 rect->y = dcal->topPadding
1731 + MINOR_BORDER_SIZE
1732 + dcal->day_top_bar_height
1733 + ((weekRow
1734 + (end_week_of_year - start_week_of_year))
1735 * week_height (dcal));
1736 rect->width = (((g_date_get_weekday (endD) + 7 - dcal->day_of_week_start) % 7) + 1) * day_width (dcal);
1737 rect->height = week_height (dcal);
1738
1739 *outList = g_list_append (*outList, (gpointer)rect);
1740 rect = NULL;
1741 }
1742
1743 g_date_free (startD);
1744 g_date_free (endD);
1745}
1746
1747/* FIXME: make this more like month_coords */
1748static void
1749doc_coords (GncDenseCal *dcal, int dayOfCal,
1750 int *x1, int *y1, int *x2, int *y2)
1751{
1752 GDate d;
1753 gint docMonth;
1754 gint d_week_of_cal, top_of_col_week_of_cal;
1755 gint colNum, dayCol, weekRow;
1756
1757 /* FIXME: add range checks */
1758 g_date_set_dmy (&d, 1, dcal->month, dcal->year);
1759 g_date_add_days (&d, dayOfCal);
1760 docMonth = g_date_get_month (&d);
1761 if (g_date_get_year (&d) != dcal->year)
1762 {
1763 docMonth += 12;
1764 }
1765 colNum = floor ((float)(docMonth - dcal->month) / (float)dcal->monthsPerCol);
1766 dayCol = g_date_get_weekday (&d) - dcal->day_of_week_start;
1767
1768 if (dayCol < 0)
1769 dayCol = dayCol + 7;
1770
1771 d_week_of_cal = get_week_of_year (dcal, &d);
1772 g_date_set_dmy (&d, 1, dcal->month, dcal->year);
1773 g_date_add_months (&d, (colNum * dcal->monthsPerCol));
1774 top_of_col_week_of_cal = get_week_of_year (dcal, &d);
1775
1776 if (d_week_of_cal < top_of_col_week_of_cal)
1777 {
1778 gint week_offset = get_weeks_in_year (dcal, dcal->year);
1779 d_week_of_cal += week_offset;
1780 }
1781 weekRow = d_week_of_cal - top_of_col_week_of_cal;
1782
1783 /* top-left corner */
1784 /* FIXME: this has the math to make the mark-cells come out right,
1785 * which it shouldn't. */
1786 *x1 = dcal->leftPadding
1787 + MINOR_BORDER_SIZE
1788 + dcal->month_side_bar_width
1789 + (colNum * (col_width (dcal) + COL_BORDER_SIZE))
1790 + (dayCol * day_width (dcal))
1791 + (day_width (dcal) / 4);
1792 *y1 = dcal->topPadding
1793 + MINOR_BORDER_SIZE
1794 + dcal->day_top_bar_height
1795 + (weekRow * week_height (dcal))
1796 + (day_height (dcal) / 4);
1797
1798 *x2 = *x1 + (day_width (dcal) / 2);
1799 *y2 = *y1 + (day_height (dcal) / 2);
1800}
1801
1806static gint
1807wheres_this (GncDenseCal *dcal, int x, int y)
1808{
1809 gint colNum, weekRow, dayCol, dayOfCal;
1810 GDate d, startD;
1811 GtkAllocation alloc;
1812
1813 x -= dcal->leftPadding;
1814 y -= dcal->topPadding;
1815
1816 if ((x < 0) || (y < 0))
1817 {
1818 return -1;
1819 }
1820 gtk_widget_get_allocation (GTK_WIDGET(dcal), &alloc);
1821 if ((x >= alloc.width)
1822 || (y >= alloc.height))
1823 {
1824 return -1;
1825 }
1826
1827 /* "outside of displayed table" check */
1828 if (x >= (num_cols(dcal) * (col_width (dcal) + COL_BORDER_SIZE)))
1829 {
1830 return -1;
1831 }
1832 if (y >= dcal->day_top_bar_height + col_height (dcal))
1833 {
1834 return -1;
1835 }
1836
1837 /* coords -> year-relative-values */
1838 colNum = floor (x / (col_width (dcal) + COL_BORDER_SIZE));
1839
1840 x %= (col_width (dcal) + COL_BORDER_SIZE);
1841 x -= dcal->month_side_bar_width;
1842 if (x < 0)
1843 {
1844 return -1;
1845 }
1846 if (x >= day_width (dcal) * 7)
1847 {
1848 return -1;
1849 }
1850
1851 y -= dcal->day_top_bar_height;
1852 if (y < 0)
1853 {
1854 return -1;
1855 }
1856
1857 dayCol = floor ((float)x / (float)day_width (dcal));
1858 weekRow = floor ((float)y / (float)week_height (dcal));
1859
1860 g_date_set_dmy (&startD, 1, dcal->month, dcal->year);
1861 d = startD;
1862 g_date_add_months (&d, (colNum * dcal->monthsPerCol));
1863
1864 if (dcal->day_of_week_start == G_DATE_SUNDAY)
1865 dayCol -= (g_date_get_weekday (&d) - 0) % 7;
1866 else
1867 dayCol -= (g_date_get_weekday (&d) - 1) % 7;
1868
1869 if (weekRow == 0)
1870 {
1871 if (dayCol < 0)
1872 {
1873 return -1;
1874 }
1875 }
1876 g_date_add_days (&d, dayCol + (weekRow * 7));
1877
1878 /* Check to make sure we're within the column's displayed range. */
1879 {
1880 GDate ccd;
1881 g_date_set_dmy (&ccd, 1, dcal->month, dcal->year);
1882 g_date_add_months (&ccd, (colNum + 1) * dcal->monthsPerCol);
1883 if (g_date_get_julian (&d) >= g_date_get_julian (&ccd))
1884 {
1885 return -1;
1886 }
1887 }
1888
1889 dayOfCal = g_date_get_julian (&d) - g_date_get_julian (&startD);
1890
1891 /* one more check before returning... */
1892 g_date_subtract_months (&d, dcal->numMonths);
1893 if (g_date_get_julian (&d) >= g_date_get_julian (&startD))
1894 {
1895 /* we're past the end of the displayed calendar, thus -1 */
1896 DEBUG("%d >= %d", g_date_get_julian (&d), g_date_get_julian (&startD));
1897 return -1;
1898 }
1899
1900 return dayOfCal;
1901}
1902
1903static gint
1904gdc_get_doc_offset (GncDenseCal *dcal, GDate *d)
1905{
1906 gint toRet;
1907 /* soc == start-of-calendar */
1908 GDate soc;
1909
1910 g_date_clear (&soc, 1);
1911 g_date_set_dmy (&soc, 1, dcal->month, dcal->year);
1912 /* ensure not before calendar start. */
1913 if (g_date_get_julian (d) < g_date_get_julian (&soc))
1914 return -1;
1915 /* do computation here, since we're going to change the
1916 * start-of-calendar date. */
1917 toRet = g_date_get_julian (d) - g_date_get_julian (&soc);
1918 /* ensure not after end of visible calendar. */
1919 g_date_add_months (&soc, dcal->numMonths);
1920 if (g_date_get_julian (d) >= g_date_get_julian (&soc))
1921 return -1;
1922 /* return pre-computed value. */
1923 return toRet;
1924}
1925
1926static void
1927gdc_add_tag_markings (GncDenseCal *cal, guint tag)
1928{
1929 gchar *name, *info;
1930 gint num_marks, idx;
1931 GDate **dates;
1932 GDate *calDate;
1933
1934 // copy the values into the old marking function.
1935 name = gnc_dense_cal_model_get_name (cal->model, tag);
1936 info = gnc_dense_cal_model_get_info (cal->model, tag);
1937 num_marks = gnc_dense_cal_model_get_instance_count (cal->model, tag);
1938
1939 if (num_marks == 0)
1940 goto cleanup;
1941
1942 dates = g_new0 (GDate*, num_marks);
1943 calDate = g_date_new_dmy (1, cal->month, cal->year);
1944
1945 for (idx = 0; idx < num_marks; idx++)
1946 {
1947 dates[idx] = g_date_new ();
1948 gnc_dense_cal_model_get_instance (cal->model, tag, idx, dates[idx]);
1949
1950 }
1951 if (g_date_valid (dates[0]))
1952 {
1953 if (g_date_get_julian (dates[0]) < g_date_get_julian (calDate))
1954 {
1955 /* Oops, first marking is earlier than months displayed.
1956 * Choose new first month and recalculate all markings for all
1957 * tags. Their offsets are all wrong with the newly added month(s).
1958 */
1959 _gnc_dense_cal_set_month (cal, g_date_get_month (dates[0]), FALSE);
1960 _gnc_dense_cal_set_year (cal, g_date_get_year (dates[0]), FALSE);
1961
1962 gdc_remove_markings (cal);
1963 gdc_add_markings (cal);
1964 }
1965 else
1966 gdc_mark_add (cal, tag, name, info, num_marks, dates);
1967 }
1968 else
1969 {
1970 g_warning ("Bad date, skipped.");
1971 }
1972
1973 for (idx = 0; idx < num_marks; idx++)
1974 {
1975 g_date_free (dates[idx]);
1976 }
1977 g_free (dates);
1978 g_date_free (calDate);
1979
1980cleanup:
1981 g_free (info);
1982}
1983
1984static void
1985gdc_add_markings (GncDenseCal *cal)
1986{
1987 GList *tags = gnc_dense_cal_model_get_contained (cal->model);
1988
1989 for (GList *n = tags; n; n = n->next)
1990 gdc_add_tag_markings (cal, GPOINTER_TO_UINT(n->data));
1991
1992 g_list_free (tags);
1993}
1994
1995static void
1996gdc_remove_markings (GncDenseCal *cal)
1997{
1998 GList *tags = gnc_dense_cal_model_get_contained (cal->model);
1999
2000 for (GList *n = tags; n; n = n->next)
2001 gdc_mark_remove (cal, GPOINTER_TO_UINT(n->data), FALSE);
2002
2003 g_list_free (tags);
2004}
2005
2006static void
2007gdc_model_added_cb (GncDenseCalModel *model, guint added_tag, gpointer user_data)
2008{
2009 GncDenseCal *cal = GNC_DENSE_CAL(user_data);
2010 DEBUG("gdc_model_added_cb update");
2011 gdc_add_tag_markings (cal, added_tag);
2012}
2013
2014static void
2015gdc_model_update_cb (GncDenseCalModel *model, guint update_tag, gpointer user_data)
2016{
2017 GncDenseCal *cal = GNC_DENSE_CAL(user_data);
2018 gint num_marks = 0;
2019 DEBUG("gdc_model_update_cb update for tag [%d]", update_tag);
2020 num_marks = gnc_dense_cal_model_get_instance_count (cal->model, update_tag);
2021 // We need to redraw if there are no mark, to ensure they're all erased.
2022 gdc_mark_remove (cal, update_tag, num_marks==0);
2023 gdc_add_tag_markings (cal, update_tag);
2024
2025}
2026
2027static void
2028gdc_model_removing_cb (GncDenseCalModel *model, guint remove_tag, gpointer user_data)
2029{
2030 GncDenseCal *cal = GNC_DENSE_CAL(user_data);
2031 DEBUG("gdc_model_removing_cb update [%d]", remove_tag);
2032 gdc_mark_remove (cal, remove_tag, TRUE);
2033}
2034
2035void
2036gnc_dense_cal_set_model (GncDenseCal *cal, GncDenseCalModel *model)
2037{
2038 if (cal->model != NULL)
2039 {
2040 gdc_remove_markings (cal);
2041 g_object_unref (G_OBJECT(cal->model));
2042 cal->model = NULL;
2043 }
2044 cal->model = model;
2045 g_object_ref (G_OBJECT(model));
2046 g_signal_connect (G_OBJECT(cal->model), "added", (GCallback)gdc_model_added_cb, cal);
2047 g_signal_connect (G_OBJECT(cal->model), "update", (GCallback)gdc_model_update_cb, cal);
2048 g_signal_connect (G_OBJECT(cal->model), "removing", (GCallback)gdc_model_removing_cb, cal);
2049
2050 gdc_add_markings (cal);
2051}
2052
2056static void
2057gdc_mark_add (GncDenseCal *dcal,
2058 guint tag,
2059 gchar *name,
2060 gchar *info,
2061 guint size,
2062 GDate **dateArray)
2063{
2064 guint i;
2065 gint doc;
2066 gdc_mark_data *newMark;
2067 GDate *d;
2068
2069 if (size == 0)
2070 {
2071 g_error ("0 size not allowed");
2072 return;
2073 }
2074
2075 newMark = g_new0 (gdc_mark_data, 1);
2076 newMark->name = NULL;
2077 if (name)
2078 newMark->name = g_strdup (name);
2079 newMark->info = NULL;
2080 if (info)
2081 newMark->info = g_strdup (info);
2082 newMark->tag = tag;
2083 newMark->ourMarks = NULL;
2084 DEBUG("saving mark with tag [%d]", newMark->tag);
2085
2086 for (i = 0; i < size; i++)
2087 {
2088 d = dateArray[i];
2089 doc = gdc_get_doc_offset (dcal, d);
2090 if (doc < 0)
2091 continue;
2092 if (doc >= dcal->numMarks)
2093 {
2094 /* It's not going to get any better, so just
2095 * stop processing. */
2096 break;
2097 }
2098 dcal->marks[doc] = g_list_append (dcal->marks[doc], newMark);
2099 newMark->ourMarks = g_list_append (newMark->ourMarks,
2100 GINT_TO_POINTER(doc));
2101 }
2102 dcal->markData = g_list_append (dcal->markData, (gpointer)newMark);
2103 gnc_dense_cal_draw_to_buffer (dcal);
2104 gtk_widget_queue_draw (GTK_WIDGET(dcal->cal_drawing_area));
2105}
2106
2107static void
2108gdc_mark_remove (GncDenseCal *dcal, guint mark_to_remove, gboolean redraw)
2109{
2110 GList *iter, *calendar_marks;
2111 gint day_of_cal;
2112 gdc_mark_data *mark_data;
2113
2114 /* Ignore non-realistic marks */
2115 if ((gint)mark_to_remove == -1)
2116 {
2117 DEBUG("mark_to_remove = -1");
2118 return;
2119 }
2120
2121 mark_data = NULL;
2122 for (iter = dcal->markData; iter != NULL; iter = iter->next)
2123 {
2124 mark_data = (gdc_mark_data*)iter->data;
2125 if (mark_data->tag == mark_to_remove)
2126 break;
2127 }
2128 if (iter == NULL)
2129 {
2130 PINFO("couldn't find tag [%d]", mark_to_remove);
2131 return;
2132 }
2133 if (mark_data == NULL)
2134 {
2135 DEBUG("mark_data == null");
2136 return;
2137 }
2138
2139 for (calendar_marks = mark_data->ourMarks; calendar_marks != NULL; calendar_marks = calendar_marks->next)
2140 {
2141 day_of_cal = GPOINTER_TO_INT(calendar_marks->data);
2142 dcal->marks[day_of_cal] = g_list_remove (dcal->marks[day_of_cal], mark_data);
2143 }
2144 g_list_free (mark_data->ourMarks);
2145 dcal->markData = g_list_remove (dcal->markData, mark_data);
2146 g_free (mark_data->name);
2147 g_free (mark_data->info);
2148 g_free (mark_data);
2149
2150 if (redraw)
2151 {
2152 gnc_dense_cal_draw_to_buffer (dcal);
2153 gtk_widget_queue_draw (GTK_WIDGET(dcal->cal_drawing_area));
2154 }
2155}
Date and Time handling routines.
All type declarations for the whole Gnucash engine.
gtk helper routines.
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition gnc-date.h:108
void gnc_dow_abbrev(gchar *buf, int buf_len, int dow)
Localized DOW abbreviation.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
size_t qof_print_date_buff(char *buff, const size_t len, time64 t)
Convenience: calls through to qof_print_date_dmy_buff().
Definition gnc-date.cpp:574
gint gnc_start_of_week(void)
returns an integer corresponding to locale start of week
Definition gnc-date.cpp:195
void gnc_gdate_set_today(GDate *gd)
Set a GDate to the current day.
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.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
GList * markData
A GList of gdc_mark_data structs, one for each active/valid markTag.
GList * ourMarks
A GList of the dcal->marks indexes containing this mark.