diff options
Diffstat (limited to 'e-util/e-calendar-item.c')
-rw-r--r-- | e-util/e-calendar-item.c | 3773 |
1 files changed, 3773 insertions, 0 deletions
diff --git a/e-util/e-calendar-item.c b/e-util/e-calendar-item.c new file mode 100644 index 0000000000..3e7715414c --- /dev/null +++ b/e-util/e-calendar-item.c @@ -0,0 +1,3773 @@ +/* + * ECalendarItem - canvas item displaying a calendar. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the program; if not, see <http://www.gnu.org/licenses/> + * + * Authors: + * Damon Chaplin <damon@ximian.com> + * Bolian Yin <bolian.yin@sun.com> + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <libebackend/libebackend.h> + +#include "e-calendar-item.h" + +#include <time.h> +#include <stdlib.h> +#include <string.h> +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <glib/gi18n.h> + +#include "ea-widgets.h" +#include "e-misc-utils.h" + +static const gint e_calendar_item_days_in_month[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 +}; + +#define DAYS_IN_MONTH(year, month) \ + e_calendar_item_days_in_month[month] + (((month) == 1 \ + && ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))) ? 1 : 0) + +static void e_calendar_item_dispose (GObject *object); +static void e_calendar_item_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void e_calendar_item_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void e_calendar_item_realize (GnomeCanvasItem *item); +static void e_calendar_item_unmap (GnomeCanvasItem *item); +static void e_calendar_item_update (GnomeCanvasItem *item, + const cairo_matrix_t *i2c, + gint flags); +static void e_calendar_item_draw (GnomeCanvasItem *item, + cairo_t *cr, + gint x, + gint y, + gint width, + gint height); +static void e_calendar_item_draw_month (ECalendarItem *calitem, + cairo_t *cr, + gint x, + gint y, + gint width, + gint height, + gint row, + gint col); +static void e_calendar_item_draw_day_numbers + (ECalendarItem *calitem, + cairo_t *cr, + gint width, + gint height, + gint row, + gint col, + gint year, + gint month, + gint start_weekday, + gint cells_x, + gint cells_y); +static GnomeCanvasItem *e_calendar_item_point (GnomeCanvasItem *item, + gdouble x, + gdouble y, + gint cx, + gint cy); +static void e_calendar_item_stop_selecting (ECalendarItem *calitem, + guint32 time); +static void e_calendar_item_selection_add_days + (ECalendarItem *calitem, + gint n_days, + gboolean multi_selection); +static gint e_calendar_item_key_press_event (ECalendarItem *item, + GdkEvent *event); +static gint e_calendar_item_event (GnomeCanvasItem *item, + GdkEvent *event); +static void e_calendar_item_bounds (GnomeCanvasItem *item, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2); + +static gboolean e_calendar_item_button_press (ECalendarItem *calitem, + GdkEvent *event); +static gboolean e_calendar_item_button_release (ECalendarItem *calitem, + GdkEvent *event); +static gboolean e_calendar_item_motion (ECalendarItem *calitem, + GdkEvent *event); + +static gboolean e_calendar_item_convert_position_to_day + (ECalendarItem *calitem, + gint x, + gint y, + gboolean round_empty_positions, + gint *month_offset, + gint *day, + gboolean *entire_week); +static void e_calendar_item_get_month_info (ECalendarItem *calitem, + gint row, + gint col, + gint *first_day_offset, + gint *days_in_month, + gint *days_in_prev_month); +static void e_calendar_item_recalc_sizes (ECalendarItem *calitem); + +static void e_calendar_item_get_day_style (ECalendarItem *calitem, + gint year, + gint month, + gint day, + gint day_style, + gboolean today, + gboolean prev_or_next_month, + gboolean selected, + gboolean has_focus, + gboolean drop_target, + GdkColor **bg_color, + GdkColor **fg_color, + GdkColor **box_color, + gboolean *bold, + gboolean *italic); +static void e_calendar_item_check_selection_end + (ECalendarItem *calitem, + gint start_month, + gint start_day, + gint *end_month, + gint *end_day); +static void e_calendar_item_check_selection_start + (ECalendarItem *calitem, + gint *start_month, + gint *start_day, + gint end_month, + gint end_day); +static void e_calendar_item_add_days_to_selection + (ECalendarItem *calitem, + gint days); +static void e_calendar_item_round_up_selection + (ECalendarItem *calitem, + gint *month_offset, + gint *day); +static void e_calendar_item_round_down_selection + (ECalendarItem *calitem, + gint *month_offset, + gint *day); +static gint e_calendar_item_get_inclusive_days + (ECalendarItem *calitem, + gint start_month_offset, + gint start_day, + gint end_month_offset, + gint end_day); +static void e_calendar_item_ensure_valid_day + (ECalendarItem *calitem, + gint *month_offset, + gint *day); +static gboolean e_calendar_item_ensure_days_visible + (ECalendarItem *calitem, + gint start_year, + gint start_month, + gint start_day, + gint end_year, + gint end_month, + gint end_day, + gboolean emission); +static void e_calendar_item_show_popup_menu (ECalendarItem *calitem, + GdkEvent *button_event, + gint month_offset); +static void e_calendar_item_on_menu_item_activate + (GtkWidget *menuitem, + ECalendarItem *calitem); +static void e_calendar_item_position_menu (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data); +static void e_calendar_item_date_range_changed + (ECalendarItem *calitem); +static void e_calendar_item_queue_signal_emission + (ECalendarItem *calitem); +static gboolean e_calendar_item_signal_emission_idle_cb + (gpointer data); +static void e_calendar_item_set_selection_if_emission + (ECalendarItem *calitem, + const GDate *start_date, + const GDate *end_date, + gboolean emission); + +/* Our arguments. */ +enum { + PROP_0, + PROP_YEAR, + PROP_MONTH, + PROP_X1, + PROP_Y1, + PROP_X2, + PROP_Y2, + PROP_FONT_DESC, + PROP_WEEK_NUMBER_FONT, + PROP_WEEK_NUMBER_FONT_DESC, + PROP_ROW_HEIGHT, + PROP_COLUMN_WIDTH, + PROP_MINIMUM_ROWS, + PROP_MINIMUM_COLUMNS, + PROP_MAXIMUM_ROWS, + PROP_MAXIMUM_COLUMNS, + PROP_WEEK_START_DAY, + PROP_SHOW_WEEK_NUMBERS, + PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK, + PROP_MAXIMUM_DAYS_SELECTED, + PROP_DAYS_TO_START_WEEK_SELECTION, + PROP_MOVE_SELECTION_WHEN_MOVING, + PROP_PRESERVE_DAY_WHEN_MOVING, + PROP_DISPLAY_POPUP +}; + +enum { + DATE_RANGE_CHANGED, + SELECTION_CHANGED, + SELECTION_PREVIEW_CHANGED, + LAST_SIGNAL +}; + +static guint e_calendar_item_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE_WITH_CODE ( + ECalendarItem, + e_calendar_item, + GNOME_TYPE_CANVAS_ITEM, + G_IMPLEMENT_INTERFACE ( + E_TYPE_EXTENSIBLE, NULL)) + +static void +e_calendar_item_class_init (ECalendarItemClass *class) +{ + GObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + object_class = G_OBJECT_CLASS (class); + object_class->dispose = e_calendar_item_dispose; + object_class->get_property = e_calendar_item_get_property; + object_class->set_property = e_calendar_item_set_property; + + item_class = GNOME_CANVAS_ITEM_CLASS (class); + item_class->realize = e_calendar_item_realize; + item_class->unmap = e_calendar_item_unmap; + item_class->update = e_calendar_item_update; + item_class->draw = e_calendar_item_draw; + item_class->point = e_calendar_item_point; + item_class->event = e_calendar_item_event; + item_class->bounds = e_calendar_item_bounds; + + class->date_range_changed = NULL; + class->selection_changed = NULL; + class->selection_preview_changed = NULL; + + g_object_class_install_property ( + object_class, + PROP_YEAR, + g_param_spec_int ( + "year", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MONTH, + g_param_spec_int ( + "month", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_X1, + g_param_spec_double ( + "x1", + NULL, + NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0., + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_Y1, + g_param_spec_double ( + "y1", + NULL, + NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0., + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_X2, + g_param_spec_double ( + "x2", + NULL, + NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0., + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_Y2, + g_param_spec_double ( + "y2", + NULL, + NULL, + -G_MAXDOUBLE, + G_MAXDOUBLE, + 0., + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_FONT_DESC, + g_param_spec_boxed ( + "font_desc", + NULL, + NULL, + PANGO_TYPE_FONT_DESCRIPTION, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_WEEK_NUMBER_FONT_DESC, + g_param_spec_boxed ( + "week_number_font_desc", + NULL, + NULL, + PANGO_TYPE_FONT_DESCRIPTION, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_ROW_HEIGHT, + g_param_spec_int ( + "row_height", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_COLUMN_WIDTH, + g_param_spec_int ( + "column_width", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READABLE)); + + g_object_class_install_property ( + object_class, + PROP_MINIMUM_ROWS, + g_param_spec_int ( + "minimum_rows", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MINIMUM_COLUMNS, + g_param_spec_int ( + "minimum_columns", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MAXIMUM_ROWS, + g_param_spec_int ( + "maximum_rows", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MAXIMUM_COLUMNS, + g_param_spec_int ( + "maximum_columns", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_WEEK_START_DAY, + g_param_spec_int ( + "week_start_day", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_SHOW_WEEK_NUMBERS, + g_param_spec_boolean ( + "show_week_numbers", + NULL, + NULL, + TRUE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK, + g_param_spec_boolean ( + "keep_wdays_on_weeknum_click", + NULL, + NULL, + FALSE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MAXIMUM_DAYS_SELECTED, + g_param_spec_int ( + "maximum_days_selected", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_DAYS_TO_START_WEEK_SELECTION, + g_param_spec_int ( + "days_to_start_week_selection", + NULL, + NULL, + G_MININT, + G_MAXINT, + 0, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_MOVE_SELECTION_WHEN_MOVING, + g_param_spec_boolean ( + "move_selection_when_moving", + NULL, + NULL, + TRUE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_PRESERVE_DAY_WHEN_MOVING, + g_param_spec_boolean ( + "preserve_day_when_moving", + NULL, + NULL, + TRUE, + G_PARAM_READWRITE)); + + g_object_class_install_property ( + object_class, + PROP_DISPLAY_POPUP, + g_param_spec_boolean ( + "display_popup", + NULL, + NULL, + TRUE, + G_PARAM_READWRITE)); + + e_calendar_item_signals[DATE_RANGE_CHANGED] = g_signal_new ( + "date_range_changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ECalendarItemClass, date_range_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + e_calendar_item_signals[SELECTION_CHANGED] = g_signal_new ( + "selection_changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (ECalendarItemClass, selection_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + e_calendar_item_signals[SELECTION_PREVIEW_CHANGED] = g_signal_new ( + "selection_preview_changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ECalendarItemClass, selection_preview_changed), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + e_calendar_item_a11y_init (); +} + +static void +e_calendar_item_init (ECalendarItem *calitem) +{ + struct tm *tmp_tm; + time_t t; + + /* Set the default time to the current month. */ + t = time (NULL); + tmp_tm = localtime (&t); + calitem->year = tmp_tm->tm_year + 1900; + calitem->month = tmp_tm->tm_mon; + + calitem->styles = NULL; + + calitem->min_cols = 1; + calitem->min_rows = 1; + calitem->max_cols = -1; + calitem->max_rows = -1; + + calitem->rows = 0; + calitem->cols = 0; + + calitem->show_week_numbers = FALSE; + calitem->keep_wdays_on_weeknum_click = FALSE; + calitem->week_start_day = 0; + calitem->expand = TRUE; + calitem->max_days_selected = 1; + calitem->days_to_start_week_selection = -1; + calitem->move_selection_when_moving = TRUE; + calitem->preserve_day_when_moving = FALSE; + calitem->display_popup = TRUE; + + calitem->x1 = 0.0; + calitem->y1 = 0.0; + calitem->x2 = 0.0; + calitem->y2 = 0.0; + + calitem->selecting = FALSE; + calitem->selecting_axis = NULL; + + calitem->selection_set = FALSE; + + calitem->selection_changed = FALSE; + calitem->date_range_changed = FALSE; + + calitem->style_callback = NULL; + calitem->style_callback_data = NULL; + calitem->style_callback_destroy = NULL; + + calitem->time_callback = NULL; + calitem->time_callback_data = NULL; + calitem->time_callback_destroy = NULL; + + calitem->signal_emission_idle_id = 0; +} + +static void +e_calendar_item_dispose (GObject *object) +{ + ECalendarItem *calitem; + + calitem = E_CALENDAR_ITEM (object); + + e_calendar_item_set_style_callback (calitem, NULL, NULL, NULL); + e_calendar_item_set_get_time_callback (calitem, NULL, NULL, NULL); + + if (calitem->styles) { + g_free (calitem->styles); + calitem->styles = NULL; + } + + if (calitem->signal_emission_idle_id > 0) { + g_source_remove (calitem->signal_emission_idle_id); + calitem->signal_emission_idle_id = -1; + } + + if (calitem->font_desc) { + pango_font_description_free (calitem->font_desc); + calitem->font_desc = NULL; + } + + if (calitem->week_number_font_desc) { + pango_font_description_free (calitem->week_number_font_desc); + calitem->week_number_font_desc = NULL; + } + + if (calitem->selecting_axis) + g_free (calitem->selecting_axis); + + G_OBJECT_CLASS (e_calendar_item_parent_class)->dispose (object); +} + +static void +e_calendar_item_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + ECalendarItem *calitem; + + calitem = E_CALENDAR_ITEM (object); + + switch (property_id) { + case PROP_YEAR: + g_value_set_int (value, calitem->year); + return; + case PROP_MONTH: + g_value_set_int (value, calitem->month); + return; + case PROP_X1: + g_value_set_double (value, calitem->x1); + return; + case PROP_Y1: + g_value_set_double (value, calitem->y1); + return; + case PROP_X2: + g_value_set_double (value, calitem->x2); + return; + case PROP_Y2: + g_value_set_double (value, calitem->y2); + return; + case PROP_FONT_DESC: + g_value_set_boxed (value, calitem->font_desc); + return; + case PROP_WEEK_NUMBER_FONT_DESC: + g_value_set_boxed (value, calitem->week_number_font_desc); + return; + case PROP_ROW_HEIGHT: + e_calendar_item_recalc_sizes (calitem); + g_value_set_int (value, calitem->min_month_height); + return; + case PROP_COLUMN_WIDTH: + e_calendar_item_recalc_sizes (calitem); + g_value_set_int (value, calitem->min_month_width); + return; + case PROP_MINIMUM_ROWS: + g_value_set_int (value, calitem->min_rows); + return; + case PROP_MINIMUM_COLUMNS: + g_value_set_int (value, calitem->min_cols); + return; + case PROP_MAXIMUM_ROWS: + g_value_set_int (value, calitem->max_rows); + return; + case PROP_MAXIMUM_COLUMNS: + g_value_set_int (value, calitem->max_cols); + return; + case PROP_WEEK_START_DAY: + g_value_set_int (value, calitem->week_start_day); + return; + case PROP_SHOW_WEEK_NUMBERS: + g_value_set_boolean (value, calitem->show_week_numbers); + return; + case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK: + g_value_set_boolean (value, calitem->keep_wdays_on_weeknum_click); + return; + case PROP_MAXIMUM_DAYS_SELECTED: + g_value_set_int (value, e_calendar_item_get_max_days_sel (calitem)); + return; + case PROP_DAYS_TO_START_WEEK_SELECTION: + g_value_set_int (value, e_calendar_item_get_days_start_week_sel (calitem)); + return; + case PROP_MOVE_SELECTION_WHEN_MOVING: + g_value_set_boolean (value, calitem->move_selection_when_moving); + return; + case PROP_PRESERVE_DAY_WHEN_MOVING: + g_value_set_boolean (value, calitem->preserve_day_when_moving); + return; + case PROP_DISPLAY_POPUP: + g_value_set_boolean (value, e_calendar_item_get_display_popup (calitem)); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_calendar_item_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GnomeCanvasItem *item; + ECalendarItem *calitem; + PangoFontDescription *font_desc; + gdouble dvalue; + gint ivalue; + gboolean bvalue; + + item = GNOME_CANVAS_ITEM (object); + calitem = E_CALENDAR_ITEM (object); + + switch (property_id) { + case PROP_YEAR: + ivalue = g_value_get_int (value); + e_calendar_item_set_first_month ( + calitem, ivalue, calitem->month); + return; + case PROP_MONTH: + ivalue = g_value_get_int (value); + e_calendar_item_set_first_month ( + calitem, calitem->year, ivalue); + return; + case PROP_X1: + dvalue = g_value_get_double (value); + if (calitem->x1 != dvalue) { + calitem->x1 = dvalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_Y1: + dvalue = g_value_get_double (value); + if (calitem->y1 != dvalue) { + calitem->y1 = dvalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_X2: + dvalue = g_value_get_double (value); + if (calitem->x2 != dvalue) { + calitem->x2 = dvalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_Y2: + dvalue = g_value_get_double (value); + if (calitem->y2 != dvalue) { + calitem->y2 = dvalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_FONT_DESC: + font_desc = g_value_get_boxed (value); + if (calitem->font_desc) + pango_font_description_free (calitem->font_desc); + calitem->font_desc = pango_font_description_copy (font_desc); + gnome_canvas_item_request_update (item); + return; + case PROP_WEEK_NUMBER_FONT_DESC: + font_desc = g_value_get_boxed (value); + if (calitem->week_number_font_desc) + pango_font_description_free (calitem->week_number_font_desc); + calitem->week_number_font_desc = pango_font_description_copy (font_desc); + gnome_canvas_item_request_update (item); + return; + case PROP_MINIMUM_ROWS: + ivalue = g_value_get_int (value); + ivalue = MAX (1, ivalue); + if (calitem->min_rows != ivalue) { + calitem->min_rows = ivalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_MINIMUM_COLUMNS: + ivalue = g_value_get_int (value); + ivalue = MAX (1, ivalue); + if (calitem->min_cols != ivalue) { + calitem->min_cols = ivalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_MAXIMUM_ROWS: + ivalue = g_value_get_int (value); + if (calitem->max_rows != ivalue) { + calitem->max_rows = ivalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_MAXIMUM_COLUMNS: + ivalue = g_value_get_int (value); + if (calitem->max_cols != ivalue) { + calitem->max_cols = ivalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_WEEK_START_DAY: + ivalue = g_value_get_int (value); + if (calitem->week_start_day != ivalue) { + calitem->week_start_day = ivalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_SHOW_WEEK_NUMBERS: + bvalue = g_value_get_boolean (value); + if (calitem->show_week_numbers != bvalue) { + calitem->show_week_numbers = bvalue; + gnome_canvas_item_request_update (item); + } + return; + case PROP_KEEP_WDAYS_ON_WEEKNUM_CLICK: + calitem->keep_wdays_on_weeknum_click = g_value_get_boolean (value); + return; + case PROP_MAXIMUM_DAYS_SELECTED: + ivalue = g_value_get_int (value); + e_calendar_item_set_max_days_sel (calitem, ivalue); + return; + case PROP_DAYS_TO_START_WEEK_SELECTION: + ivalue = g_value_get_int (value); + e_calendar_item_set_days_start_week_sel (calitem, ivalue); + return; + case PROP_MOVE_SELECTION_WHEN_MOVING: + bvalue = g_value_get_boolean (value); + calitem->move_selection_when_moving = bvalue; + return; + case PROP_PRESERVE_DAY_WHEN_MOVING: + bvalue = g_value_get_boolean (value); + calitem->preserve_day_when_moving = bvalue; + return; + case PROP_DISPLAY_POPUP: + bvalue = g_value_get_boolean (value); + e_calendar_item_set_display_popup (calitem, bvalue); + return; + } + + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); +} + +static void +e_calendar_item_realize (GnomeCanvasItem *item) +{ + ECalendarItem *calitem; + + if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->realize) + (* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->realize) (item); + + calitem = E_CALENDAR_ITEM (item); + + e_calendar_item_style_set (GTK_WIDGET (item->canvas), calitem); + + e_extensible_load_extensions (E_EXTENSIBLE (calitem)); +} + +static void +e_calendar_item_unmap (GnomeCanvasItem *item) +{ + ECalendarItem *calitem; + + calitem = E_CALENDAR_ITEM (item); + + if (calitem->selecting) { + gnome_canvas_item_ungrab (item, GDK_CURRENT_TIME); + calitem->selecting = FALSE; + } + + if (GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap) + (* GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class)->unmap) (item); +} + +static void +e_calendar_item_update (GnomeCanvasItem *item, + const cairo_matrix_t *i2c, + gint flags) +{ + GnomeCanvasItemClass *item_class; + ECalendarItem *calitem; + GtkStyle *style; + gint char_height, width, height, space, space_per_cal, space_per_cell; + gint rows, cols, xthickness, ythickness; + PangoFontDescription *font_desc; + PangoContext *pango_context; + PangoFontMetrics *font_metrics; + + item_class = GNOME_CANVAS_ITEM_CLASS (e_calendar_item_parent_class); + if (item_class->update != NULL) + item_class->update (item, i2c, flags); + + calitem = E_CALENDAR_ITEM (item); + style = gtk_widget_get_style (GTK_WIDGET (item->canvas)); + xthickness = style->xthickness; + ythickness = style->ythickness; + + item->x1 = calitem->x1; + item->y1 = calitem->y1; + item->x2 = calitem->x2 >= calitem->x1 ? calitem->x2 : calitem->x1; + item->y2 = calitem->y2 >= calitem->y1 ? calitem->y2 : calitem->y1; + + /* Set up Pango prerequisites */ + font_desc = style->font_desc; + pango_context = gtk_widget_get_pango_context (GTK_WIDGET (item->canvas)); + font_metrics = pango_context_get_metrics ( + pango_context, font_desc, + pango_context_get_language (pango_context)); + + /* + * Calculate the new layout of the calendar. + */ + + /* Make sure the minimum row width & cell height and the widths of + * all the digits and characters are up to date. */ + e_calendar_item_recalc_sizes (calitem); + + /* Calculate how many rows & cols we can fit in. */ + width = item->x2 - item->x1; + height = item->y2 - item->y1; + + width -= xthickness * 2; + height -= ythickness * 2; + + if (calitem->min_month_height == 0) + rows = 1; + else + rows = height / calitem->min_month_height; + rows = MAX (rows, calitem->min_rows); + if (calitem->max_rows > 0) + rows = MIN (rows, calitem->max_rows); + + if (calitem->min_month_width == 0) + cols = 1; + else + cols = width / calitem->min_month_width; + cols = MAX (cols, calitem->min_cols); + if (calitem->max_cols > 0) + cols = MIN (cols, calitem->max_cols); + + if (rows != calitem->rows || cols != calitem->cols) + e_calendar_item_date_range_changed (calitem); + + calitem->rows = rows; + calitem->cols = cols; + + /* Split up the empty space according to the configuration. + * If the calendar is set to expand, we divide the space between the + * cells and the spaces around the calendar, otherwise we place the + * calendars in the center of the available area. */ + + char_height = + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)); + + calitem->month_width = calitem->min_month_width; + calitem->month_height = calitem->min_month_height; + calitem->cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2)) + + E_CALENDAR_ITEM_MIN_CELL_XPAD; + calitem->cell_height = char_height + + E_CALENDAR_ITEM_MIN_CELL_YPAD; + calitem->month_tpad = 0; + calitem->month_bpad = 0; + calitem->month_lpad = 0; + calitem->month_rpad = 0; + + space = height - calitem->rows * calitem->month_height; + if (space > 0) { + space_per_cal = space / calitem->rows; + calitem->month_height += space_per_cal; + + if (calitem->expand) { + space_per_cell = space_per_cal / E_CALENDAR_ROWS_PER_MONTH; + calitem->cell_height += space_per_cell; + space_per_cal -= space_per_cell * E_CALENDAR_ROWS_PER_MONTH; + } + + calitem->month_tpad = space_per_cal / 2; + calitem->month_bpad = space_per_cal - calitem->month_tpad; + } + + space = width - calitem->cols * calitem->month_width; + if (space > 0) { + space_per_cal = space / calitem->cols; + calitem->month_width += space_per_cal; + space -= space_per_cal * calitem->cols; + + if (calitem->expand) { + space_per_cell = space_per_cal / E_CALENDAR_COLS_PER_MONTH; + calitem->cell_width += space_per_cell; + space_per_cal -= space_per_cell * E_CALENDAR_COLS_PER_MONTH; + } + + calitem->month_lpad = space_per_cal / 2; + calitem->month_rpad = space_per_cal - calitem->month_lpad; + } + + space = MAX (0, space); + calitem->x_offset = space / 2; + + gnome_canvas_request_redraw ( + item->canvas, item->x1, item->y1, + item->x2, item->y2); + + pango_font_metrics_unref (font_metrics); +} + +/* + * DRAWING ROUTINES - functions to paint the canvas item. + */ +static void +e_calendar_item_draw (GnomeCanvasItem *canvas_item, + cairo_t *cr, + gint x, + gint y, + gint width, + gint height) +{ + ECalendarItem *calitem; + GtkWidget *widget; + GtkStyleContext *style_context; + gint char_height, row, col, row_y, bar_height, col_x; + const PangoFontDescription *font_desc; + PangoContext *pango_context; + PangoFontMetrics *font_metrics; + GdkRGBA bg_color; + GtkBorder border; + +#if 0 + g_print ( + "In e_calendar_item_draw %i,%i %ix%i\n", + x, y, width, height); +#endif + calitem = E_CALENDAR_ITEM (canvas_item); + + widget = GTK_WIDGET (canvas_item->canvas); + style_context = gtk_widget_get_style_context (widget); + + /* Set up Pango prerequisites */ + font_desc = calitem->font_desc; + if (!font_desc) + font_desc = gtk_style_context_get_font ( + style_context, GTK_STATE_FLAG_NORMAL); + pango_context = gtk_widget_get_pango_context ( + GTK_WIDGET (canvas_item->canvas)); + font_metrics = pango_context_get_metrics ( + pango_context, font_desc, + pango_context_get_language (pango_context)); + + char_height = + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)); + + gtk_style_context_get_background_color ( + style_context, GTK_STATE_NORMAL, &bg_color); + + gtk_style_context_get_border ( + style_context, GTK_STATE_NORMAL, &border); + + /* Clear the entire background. */ + cairo_save (cr); + gdk_cairo_set_source_rgba (cr, &bg_color); + cairo_rectangle ( + cr, calitem->x1 - x, calitem->y1 - y, + calitem->x2 - calitem->x1 + 1, + calitem->y2 - calitem->y1 + 1); + cairo_fill (cr); + cairo_restore (cr); + + /* Draw the shadow around the entire item. */ + gtk_style_context_save (style_context); + gtk_style_context_add_class ( + style_context, GTK_STYLE_CLASS_ENTRY); + cairo_save (cr); + gtk_render_frame ( + style_context, cr, + (gdouble) calitem->x1 - x, + (gdouble) calitem->y1 - y, + (gdouble) calitem->x2 - calitem->x1 + 1, + (gdouble) calitem->y2 - calitem->y1 + 1); + cairo_restore (cr); + gtk_style_context_restore (style_context); + + row_y = canvas_item->y1 + border.top; + bar_height = + border.top + border.bottom + + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height + + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME; + + for (row = 0; row < calitem->rows; row++) { + /* Draw the background for the title bars and the shadow around + * it, and the vertical lines between columns. */ + + cairo_save (cr); + gdk_cairo_set_source_rgba (cr, &bg_color); + cairo_rectangle ( + cr, calitem->x1 + border.left - x, + row_y - y, + calitem->x2 - calitem->x1 + 1 - + (border.left + border.right), + bar_height); + cairo_fill (cr); + cairo_restore (cr); + + gtk_style_context_save (style_context); + gtk_style_context_add_class ( + style_context, GTK_STYLE_CLASS_HEADER); + cairo_save (cr); + gtk_render_frame ( + style_context, cr, + (gdouble) calitem->x1 + border.left - x, + (gdouble) row_y - y, + (gdouble) calitem->x2 - calitem->x1 + 1 - + (border.left + border.right), + (gdouble) bar_height); + cairo_restore (cr); + gtk_style_context_restore (style_context); + + for (col = 0; col < calitem->cols; col++) { + if (col != 0) { + col_x = calitem->x1 + calitem->x_offset + + calitem->month_width * col; + + gtk_style_context_save (style_context); + gtk_style_context_add_class ( + style_context, + GTK_STYLE_CLASS_SEPARATOR); + cairo_save (cr); + gtk_render_line ( + style_context, cr, + (gdouble) col_x - 1 - x, + (gdouble) row_y + border.top + 1 - y, + (gdouble) row_y + bar_height - + border.bottom - 2 - y, + (gdouble) col_x - x); + cairo_restore (cr); + gtk_style_context_restore (style_context); + } + + e_calendar_item_draw_month ( + calitem, cr, x, y, + width, height, row, col); + } + + row_y += calitem->month_height; + } + + pango_font_metrics_unref (font_metrics); +} + +static void +layout_set_day_text (ECalendarItem *calitem, + PangoLayout *layout, + gint day_index) +{ + const gchar *abbr_name; + + /* day_index: 0 = Monday ... 6 = Sunday */ + abbr_name = e_get_weekday_name (day_index + 1, TRUE); + pango_layout_set_text (layout, abbr_name, -1); +} + +static void +e_calendar_item_draw_month (ECalendarItem *calitem, + cairo_t *cr, + gint x, + gint y, + gint width, + gint height, + gint row, + gint col) +{ + GnomeCanvasItem *item; + GtkWidget *widget; + GtkStyle *style; + PangoFontDescription *font_desc; + struct tm tmp_tm; + GdkRectangle clip_rect; + gint char_height, xthickness, ythickness, start_weekday; + gint year, month; + gint month_x, month_y, month_w, month_h; + gint min_x, max_x, text_x, text_y; + gint day, day_index, cells_x, cells_y, min_cell_width, text_width, arrow_button_size; + gint clip_width, clip_height; + gchar buffer[64]; + PangoContext *pango_context; + PangoFontMetrics *font_metrics; + PangoLayout *layout; + +#if 0 + g_print ( + "In e_calendar_item_draw_month: %i,%i %ix%i row:%i col:%i\n", + x, y, width, height, row, col); +#endif + item = GNOME_CANVAS_ITEM (calitem); + widget = GTK_WIDGET (item->canvas); + style = gtk_widget_get_style (widget); + + /* Set up Pango prerequisites */ + font_desc = calitem->font_desc; + if (!font_desc) + font_desc = style->font_desc; + pango_context = gtk_widget_get_pango_context (widget); + font_metrics = pango_context_get_metrics ( + pango_context, font_desc, + pango_context_get_language (pango_context)); + + char_height = + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)); + xthickness = style->xthickness; + ythickness = style->ythickness; + arrow_button_size = + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) + + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME + + 2 * xthickness; + + pango_font_metrics_unref (font_metrics); + + /* Calculate the top-left position of the entire month display. */ + month_x = item->x1 + xthickness + calitem->x_offset + + ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + ? (calitem->cols - 1 - col) : col) * calitem->month_width - x; + month_w = item->x2 - item->x1 - xthickness * 2; + month_w = MIN (month_w, calitem->month_width); + month_y = item->y1 + ythickness + row * calitem->month_height - y; + month_h = item->y2 - item->y1 - ythickness * 2; + month_h = MIN (month_h, calitem->month_height); + + /* Just return if the month is outside the given area. */ + if (month_x >= width || month_x + calitem->month_width <= 0 + || month_y >= height || month_y + calitem->month_height <= 0) + return; + + month = calitem->month + row * calitem->cols + col; + year = calitem->year + month / 12; + month %= 12; + + /* Draw the month name & year, with clipping. Note that the top row + * needs extra space around it for the buttons. */ + + layout = gtk_widget_create_pango_layout (widget, NULL); + + if (row == 0 && col == 0) + min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME_WITH_BUTTON; + else + min_x = E_CALENDAR_ITEM_XPAD_BEFORE_MONTH_NAME; + + max_x = month_w; + if (row == 0 && col == 0) + max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME_WITH_BUTTON; + else + max_x -= E_CALENDAR_ITEM_XPAD_AFTER_MONTH_NAME; + + text_y = month_y + style->ythickness + + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME; + clip_rect.x = month_x + min_x; + clip_rect.x = MAX (0, clip_rect.x); + clip_rect.y = MAX (0, text_y); + + memset (&tmp_tm, 0, sizeof (tmp_tm)); + tmp_tm.tm_year = year - 1900; + tmp_tm.tm_mon = month; + tmp_tm.tm_mday = 1; + tmp_tm.tm_isdst = -1; + mktime (&tmp_tm); + start_weekday = (tmp_tm.tm_wday + 6) % 7; + + if (month_x + max_x - clip_rect.x > 0) { + cairo_save (cr); + + clip_rect.width = month_x + max_x - clip_rect.x; + clip_rect.height = text_y + char_height - clip_rect.y; + gdk_cairo_rectangle (cr, &clip_rect); + cairo_clip (cr); + + gdk_cairo_set_source_color (cr, &style->fg[GTK_STATE_NORMAL]); + + if (row == 0 && col == 0) { + PangoLayout *layout_yr; + gchar buffer_yr[64]; + gdouble max_width; + + layout_yr = gtk_widget_create_pango_layout (widget, NULL); + + /* This is a strftime() format. %B = Month name. */ + e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm); + /* This is a strftime() format. %Y = Year. */ + e_utf8_strftime (buffer_yr, sizeof (buffer_yr), C_("CalItem", "%Y"), &tmp_tm); + + pango_layout_set_font_description (layout, font_desc); + pango_layout_set_text (layout, buffer, -1); + + pango_layout_set_font_description (layout_yr, font_desc); + pango_layout_set_text (layout_yr, buffer_yr, -1); + + if (gtk_widget_get_direction (widget) != GTK_TEXT_DIR_RTL) { + max_width = calitem->max_month_name_width; + pango_layout_get_pixel_size (layout, &text_width, NULL); + + cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y); + pango_cairo_show_layout (cr, layout); + + max_width = calitem->max_digit_width * 5; + pango_layout_get_pixel_size (layout_yr, &text_width, NULL); + + cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y); + pango_cairo_show_layout (cr, layout_yr); + } else { + max_width = calitem->max_digit_width * 5; + pango_layout_get_pixel_size (layout_yr, &text_width, NULL); + + cairo_move_to (cr, month_x + min_x + arrow_button_size + (max_width - text_width) / 2, text_y); + pango_cairo_show_layout (cr, layout_yr); + + max_width = calitem->max_month_name_width; + pango_layout_get_pixel_size (layout, &text_width, NULL); + + cairo_move_to (cr, month_x + month_w - arrow_button_size - (max_width - text_width) / 2 - text_width - min_x, text_y); + pango_cairo_show_layout (cr, layout); + } + + g_object_unref (layout_yr); + } else { + /* This is a strftime() format. %B = Month name, %Y = Year. */ + e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B %Y"), &tmp_tm); + + pango_layout_set_font_description (layout, font_desc); + pango_layout_set_text (layout, buffer, -1); + + /* Ideally we place the text centered in the month, but we + * won't go to the left of the minimum x position. */ + pango_layout_get_pixel_size (layout, &text_width, NULL); + text_x = (calitem->month_width - text_width) / 2; + text_x = MAX (min_x, text_x); + + cairo_move_to (cr, month_x + text_x, text_y); + pango_cairo_show_layout (cr, layout); + } + + cairo_restore (cr); + } + + /* Set the clip rectangle for the main month display. */ + clip_rect.x = MAX (0, month_x); + clip_rect.y = MAX (0, month_y); + clip_width = month_x + month_w - clip_rect.x; + clip_height = month_y + month_h - clip_rect.y; + + if (clip_width <= 0 || clip_height <= 0) { + g_object_unref (layout); + return; + } + + clip_rect.width = clip_width; + clip_rect.height = clip_height; + + cairo_save (cr); + + gdk_cairo_rectangle (cr, &clip_rect); + cairo_clip (cr); + + /* Draw the day initials across the top of the month. */ + min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2)) + + E_CALENDAR_ITEM_MIN_CELL_XPAD; + + cells_x = month_x + E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad + + E_CALENDAR_ITEM_XPAD_BEFORE_CELLS; + if (calitem->show_week_numbers) + cells_x += calitem->max_week_number_digit_width * 2 + + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1; + text_x = cells_x + calitem->cell_width + - (calitem->cell_width - min_cell_width) / 2; + text_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2; + text_y = month_y + ythickness * 2 + + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME + + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad; + + cells_y = text_y + char_height + + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1 + + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS; + + cairo_save (cr); + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]); + cairo_rectangle ( + cr, cells_x , + text_y - E_CALENDAR_ITEM_YPAD_ABOVE_CELLS - 1, + calitem->cell_width * 7 , cells_y - text_y); + cairo_fill (cr); + cairo_restore (cr); + + day_index = calitem->week_start_day; + pango_layout_set_font_description (layout, font_desc); + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + text_x += (7 - 1) * calitem->cell_width; + gdk_cairo_set_source_color (cr, &style->text[GTK_STATE_ACTIVE]); + for (day = 0; day < 7; day++) { + cairo_save (cr); + layout_set_day_text (calitem, layout, day_index); + cairo_move_to ( + cr, + text_x - calitem->day_widths[day_index], + text_y); + pango_cairo_show_layout (cr, layout); + text_x += (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + ? -calitem->cell_width : calitem->cell_width; + day_index++; + if (day_index == 7) + day_index = 0; + cairo_restore (cr); + } + + /* Draw the rectangle around the week number. */ + if (calitem->show_week_numbers) { + cairo_save (cr); + gdk_cairo_set_source_color (cr, &style->base[GTK_STATE_SELECTED]); + cairo_rectangle ( + cr, cells_x, cells_y - (cells_y - text_y + 2) , + -20, E_CALENDAR_ROWS_PER_MONTH * calitem->cell_height + 18); + cairo_fill (cr); + cairo_restore (cr); + } + + e_calendar_item_draw_day_numbers ( + calitem, cr, width, height, row, col, + year, month, start_weekday, cells_x, cells_y); + + g_object_unref (layout); + cairo_restore (cr); +} + +static const gchar * +get_digit_fomat (void) +{ + +#ifdef HAVE_GNU_GET_LIBC_VERSION +#include <gnu/libc-version.h> + + const gchar *libc_version = gnu_get_libc_version (); + gchar **split = g_strsplit (libc_version, ".", -1); + gint major = 0; + gint minor = 0; + gint revision = 0; + + major = atoi (split[0]); + minor = atoi (split[1]); + + if (g_strv_length (split) > 2) + revision = atoi (split[2]); + g_strfreev (split); + + if (major > 2 || minor > 2 || (minor == 2 && revision > 2)) { + return "%Id"; + } +#endif + + return "%d"; +} + +static void +e_calendar_item_draw_day_numbers (ECalendarItem *calitem, + cairo_t *cr, + gint width, + gint height, + gint row, + gint col, + gint year, + gint month, + gint start_weekday, + gint cells_x, + gint cells_y) +{ + GnomeCanvasItem *item; + GtkWidget *widget; + GtkStyle *style; + PangoFontDescription *font_desc; + GdkColor *bg_color, *fg_color, *box_color; + struct tm today_tm; + time_t t; + gint char_height, min_cell_width, min_cell_height; + gint day_num, drow, dcol, day_x, day_y; + gint text_x, text_y; + gint num_chars, digit; + gint week_num, mon, days_from_week_start; + gint years[3], months[3], days_in_month[3]; + gboolean today, selected, has_focus, drop_target = FALSE; + gboolean bold, italic, draw_day, finished = FALSE; + gint today_year, today_month, today_mday, month_offset; + gchar buffer[9]; + gint day_style = 0; + PangoContext *pango_context; + PangoFontMetrics *font_metrics; + PangoLayout *layout; + + item = GNOME_CANVAS_ITEM (calitem); + widget = GTK_WIDGET (item->canvas); + style = gtk_widget_get_style (widget); + + /* Set up Pango prerequisites */ + font_desc = calitem->font_desc; + if (!font_desc) + font_desc = style->font_desc; + + pango_context = gtk_widget_get_pango_context (widget); + font_metrics = pango_context_get_metrics ( + pango_context, font_desc, + pango_context_get_language (pango_context)); + + char_height = + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)); + + min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2)) + + E_CALENDAR_ITEM_MIN_CELL_XPAD; + min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD; + + layout = pango_cairo_create_layout (cr); + + /* Calculate the number of days in the previous, current, and next + * months. */ + years[0] = years[1] = years[2] = year; + months[0] = month - 1; + months[1] = month; + months[2] = month + 1; + if (months[0] == -1) { + months[0] = 11; + years[0]--; + } + if (months[2] == 12) { + months[2] = 0; + years[2]++; + } + + days_in_month[0] = DAYS_IN_MONTH (years[0], months[0]); + days_in_month[1] = DAYS_IN_MONTH (years[1], months[1]); + days_in_month[2] = DAYS_IN_MONTH (years[2], months[2]); + + /* Mon 0 is the previous month, which we may show the end of. Mon 1 is + * the current month, and mon 2 is the next month. */ + mon = 0; + + month_offset = row * calitem->cols + col - 1; + day_num = days_in_month[0]; + days_from_week_start = (start_weekday + 7 - calitem->week_start_day) + % 7; + /* For the top-left month we show the end of the previous month, and + * if the new month starts on the first day of the week we show a + * complete week from the previous month. */ + if (days_from_week_start == 0) { + if (row == 0 && col == 0) { + day_num -= 6; + } else { + mon++; + month_offset++; + day_num = 1; + } + } else { + day_num -= days_from_week_start - 1; + } + + /* Get today's date, so we can highlight it. */ + if (calitem->time_callback) { + today_tm = calitem->time_callback ( + calitem, calitem->time_callback_data); + } else { + t = time (NULL); + today_tm = *localtime (&t); + } + today_year = today_tm.tm_year + 1900; + today_month = today_tm.tm_mon; + today_mday = today_tm.tm_mday; + + /* We usually skip the last days of the previous month (mon = 0), + * except for the top-left month displayed. */ + draw_day = (mon == 1 || (row == 0 && col == 0)); + + for (drow = 0; drow < 6; drow++) { + /* Draw the week number. */ + if (calitem->show_week_numbers) { + week_num = e_calendar_item_get_week_number ( + calitem, day_num, months[mon], years[mon]); + + text_x = cells_x - E_CALENDAR_ITEM_XPAD_BEFORE_CELLS - 1 + - E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS; + text_y = cells_y + drow * calitem->cell_height + + + (calitem->cell_height - min_cell_height + 1) / 2; + + num_chars = 0; + if (week_num >= 10) { + digit = week_num / 10; + text_x -= calitem->week_number_digit_widths[digit]; + num_chars += sprintf ( + &buffer[num_chars], + get_digit_fomat (), digit); + } + + digit = week_num % 10; + text_x -= calitem->week_number_digit_widths[digit] + 6; + num_chars += sprintf ( + &buffer[num_chars], + get_digit_fomat (), digit); + + cairo_save (cr); + gdk_cairo_set_source_color ( + cr, &style->text[GTK_STATE_ACTIVE]); + pango_layout_set_font_description (layout, font_desc); + pango_layout_set_text (layout, buffer, num_chars); + cairo_move_to (cr, text_x, text_y); + pango_cairo_update_layout (cr, layout); + pango_cairo_show_layout (cr, layout); + cairo_restore (cr); + } + + for (dcol = 0; dcol < 7; dcol++) { + if (draw_day) { + day_x = cells_x + + ((gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + ? 7 - 1 - dcol : dcol) * calitem->cell_width; + + day_y = cells_y + drow * calitem->cell_height; + + today = years[mon] == today_year + && months[mon] == today_month + && day_num == today_mday; + + selected = calitem->selection_set + && (calitem->selection_start_month_offset < month_offset + || (calitem->selection_start_month_offset == month_offset + && calitem->selection_start_day <= day_num)) + && (calitem->selection_end_month_offset > month_offset + || (calitem->selection_end_month_offset == month_offset + && calitem->selection_end_day >= day_num)); + + if (calitem->styles) + day_style = calitem->styles[(month_offset + 1) * 32 + day_num]; + + /* Get the colors & style to use for the day.*/ + if ((gtk_widget_has_focus (GTK_WIDGET (item->canvas))) && + item->canvas->focused_item == item) + has_focus = TRUE; + else + has_focus = FALSE; + + bold = FALSE; + italic = FALSE; + + if (calitem->style_callback) + calitem->style_callback ( + calitem, + years[mon], + months[mon], + day_num, + day_style, + today, + mon != 1, + selected, + has_focus, + drop_target, + &bg_color, + &fg_color, + &box_color, + &bold, + &italic, + calitem->style_callback_data); + else + e_calendar_item_get_day_style ( + calitem, + years[mon], + months[mon], + day_num, + day_style, + today, + mon != 1, + selected, + has_focus, + drop_target, + &bg_color, + &fg_color, + &box_color, + &bold, + &italic); + + /* Draw the background, if set. */ + if (bg_color) { + cairo_save (cr); + gdk_cairo_set_source_color (cr, bg_color); + cairo_rectangle ( + cr, day_x , day_y, + calitem->cell_width, + calitem->cell_height); + cairo_fill (cr); + cairo_restore (cr); + } + + /* Draw the box, if set. */ + if (box_color) { + cairo_save (cr); + gdk_cairo_set_source_color (cr, box_color); + cairo_rectangle ( + cr, day_x , day_y, + calitem->cell_width - 1, + calitem->cell_height - 1); + cairo_stroke (cr); + cairo_restore (cr); + } + + /* Draw the 1- or 2-digit day number. */ + day_x += calitem->cell_width - + (calitem->cell_width - + min_cell_width) / 2; + day_x -= E_CALENDAR_ITEM_MIN_CELL_XPAD / 2; + day_y += (calitem->cell_height - min_cell_height + 1) / 2; + day_y += E_CALENDAR_ITEM_MIN_CELL_YPAD / 2; + + num_chars = 0; + if (day_num >= 10) { + digit = day_num / 10; + day_x -= calitem->digit_widths[digit]; + num_chars += sprintf ( + &buffer[num_chars], + get_digit_fomat (), digit); + } + + digit = day_num % 10; + day_x -= calitem->digit_widths[digit]; + num_chars += sprintf ( + &buffer[num_chars], + get_digit_fomat (), digit); + + cairo_save (cr); + if (fg_color) { + gdk_cairo_set_source_color ( + cr, fg_color); + } else { + gdk_cairo_set_source_color ( + cr, &style->fg[GTK_STATE_NORMAL]); + } + + if (bold) { + pango_font_description_set_weight ( + font_desc, PANGO_WEIGHT_BOLD); + } else { + pango_font_description_set_weight ( + font_desc, PANGO_WEIGHT_NORMAL); + } + + if (italic) { + pango_font_description_set_style ( + font_desc, PANGO_STYLE_ITALIC); + } else { + pango_font_description_set_style ( + font_desc, PANGO_STYLE_NORMAL); + } + + pango_layout_set_font_description (layout, font_desc); + pango_layout_set_text (layout, buffer, num_chars); + cairo_move_to (cr, day_x, day_y); + pango_cairo_update_layout (cr, layout); + pango_cairo_show_layout (cr, layout); + cairo_restore (cr); + } + + /* See if we've reached the end of a month. */ + if (day_num == days_in_month[mon]) { + month_offset++; + mon++; + /* We only draw the start of the next month + * for the bottom-right month displayed. */ + if (mon == 2 && (row != calitem->rows - 1 + || col != calitem->cols - 1)) { + /* Set a flag so we exit the loop. */ + finished = TRUE; + break; + } + day_num = 1; + draw_day = TRUE; + } else { + day_num++; + } + } + + /* Exit the loop if the flag is set. */ + if (finished) + break; + } + + /* Reset pango weight and style */ + pango_font_description_set_weight (font_desc, PANGO_WEIGHT_NORMAL); + pango_font_description_set_style (font_desc, PANGO_STYLE_NORMAL); + + g_object_unref (layout); + + pango_font_metrics_unref (font_metrics); +} + +gint +e_calendar_item_get_week_number (ECalendarItem *calitem, + gint day, + gint month, + gint year) +{ + GDate date; + guint weekday, yearday; + gint week_num; + + g_date_clear (&date, 1); + g_date_set_dmy (&date, day, month + 1, year); + + /* This results in a value of 0 (Monday) - 6 (Sunday). + * (or -1 on error - oops!!) */ + weekday = g_date_get_weekday (&date) - 1; + + if (weekday > 0) { + /* we want always point to nearest Monday, as the first day of the week, + * regardless of the calendar's week_start_day */ + if (weekday >= 3) + g_date_add_days (&date, 7 - weekday); + else + g_date_subtract_days (&date, weekday); + } + + /* Calculate the day of the year, from 0 to 365. */ + yearday = g_date_get_day_of_year (&date) - 1; + + /* If the week starts on or after 29th December, it is week 1 of the + * next year, since there are 4 days in the next year. */ + if (g_date_get_month (&date) == 12 && g_date_get_day (&date) >= 29) + return 1; + + /* Calculate the week number, from 0. */ + week_num = yearday / 7; + + /* If the first week starts on or after Jan 5th, then we need to add + * 1 since the previous week will really be the first week. */ + if (yearday % 7 >= 4) + week_num++; + + /* Add 1 so week numbers are from 1 to 53. */ + return week_num + 1; +} + +/* This is supposed to return the nearest item the the point and the distance. + * Since we are the only item we just return ourself and 0 for the distance. + * This is needed so that we get button/motion events. */ +static GnomeCanvasItem * +e_calendar_item_point (GnomeCanvasItem *item, + gdouble x, + gdouble y, + gint cx, + gint cy) +{ + return item; +} + +static void +e_calendar_item_stop_selecting (ECalendarItem *calitem, + guint32 time) +{ + if (!calitem->selecting) + return; + + gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM (calitem), time); + + calitem->selecting = FALSE; + + /* If the user selects the grayed dates before the first month or + * after the last month, we move backwards or forwards one month. + * The set_month () call should take care of updating the selection. */ + if (calitem->selection_end_month_offset == -1) + e_calendar_item_set_first_month ( + calitem, calitem->year, + calitem->month - 1); + else if (calitem->selection_start_month_offset == calitem->rows * calitem->cols) + e_calendar_item_set_first_month ( + calitem, calitem->year, + calitem->month + 1); + + calitem->selection_changed = TRUE; + if (calitem->selecting_axis) { + g_free (calitem->selecting_axis); + calitem->selecting_axis = NULL; + } + + e_calendar_item_queue_signal_emission (calitem); + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); +} + +static void +e_calendar_item_selection_add_days (ECalendarItem *calitem, + gint n_days, + gboolean multi_selection) +{ + GDate gdate_start, gdate_end; + + g_return_if_fail (E_IS_CALENDAR_ITEM (calitem)); + + if (!e_calendar_item_get_selection (calitem, &gdate_start, &gdate_end)) { + /* We set the date to the first day of the month */ + g_date_set_dmy (&gdate_start, 1, calitem->month + 1, calitem->year); + gdate_end = gdate_start; + } + + if (multi_selection && calitem->max_days_selected > 1) { + gint days_between; + + days_between = g_date_days_between (&gdate_start, &gdate_end); + if (!calitem->selecting_axis) { + calitem->selecting_axis = g_new (GDate, 1); + *(calitem->selecting_axis) = gdate_start; + } + if ((days_between != 0 && + g_date_compare (calitem->selecting_axis, &gdate_end) == 0) || + (days_between == 0 && n_days < 0)) { + if (days_between - n_days > calitem->max_days_selected - 1) + n_days = days_between + 1 - calitem->max_days_selected; + g_date_add_days (&gdate_start, n_days); + } + else { + if (days_between + n_days > calitem->max_days_selected - 1) + n_days = calitem->max_days_selected - 1 - days_between; + g_date_add_days (&gdate_end, n_days); + } + + if (g_date_compare (&gdate_end, &gdate_start) < 0) { + GDate tmp_date; + tmp_date = gdate_start; + gdate_start = gdate_end; + gdate_end = tmp_date; + } + } + else { + /* clear "selecting_axis", it is only for mulit-selecting */ + if (calitem->selecting_axis) { + g_free (calitem->selecting_axis); + calitem->selecting_axis = NULL; + } + g_date_add_days (&gdate_start, n_days); + gdate_end = gdate_start; + } + + calitem->selecting = TRUE; + + e_calendar_item_set_selection_if_emission ( + calitem, &gdate_start, &gdate_end, FALSE); + + g_signal_emit_by_name (calitem, "selection_preview_changed"); +} + +static gint +e_calendar_item_key_press_event (ECalendarItem *calitem, + GdkEvent *event) +{ + guint keyval = event->key.keyval; + gboolean multi_selection = FALSE; + + if (event->key.state & GDK_CONTROL_MASK || + event->key.state & GDK_MOD1_MASK) + return FALSE; + + multi_selection = event->key.state & GDK_SHIFT_MASK; + switch (keyval) { + case GDK_KEY_Up: + e_calendar_item_selection_add_days ( + calitem, -7, + multi_selection); + break; + case GDK_KEY_Down: + e_calendar_item_selection_add_days ( + calitem, 7, + multi_selection); + break; + case GDK_KEY_Left: + e_calendar_item_selection_add_days ( + calitem, -1, + multi_selection); + break; + case GDK_KEY_Right: + e_calendar_item_selection_add_days ( + calitem, 1, + multi_selection); + break; + case GDK_KEY_space: + case GDK_KEY_Return: + e_calendar_item_stop_selecting (calitem, event->key.time); + break; + default: + return FALSE; + } + return TRUE; +} + +static gint +e_calendar_item_event (GnomeCanvasItem *item, + GdkEvent *event) +{ + ECalendarItem *calitem; + + calitem = E_CALENDAR_ITEM (item); + + switch (event->type) { + case GDK_BUTTON_PRESS: + return e_calendar_item_button_press (calitem, event); + case GDK_BUTTON_RELEASE: + return e_calendar_item_button_release (calitem, event); + case GDK_MOTION_NOTIFY: + return e_calendar_item_motion (calitem, event); + case GDK_FOCUS_CHANGE: + gnome_canvas_item_request_update (item); + return FALSE; + case GDK_KEY_PRESS: + return e_calendar_item_key_press_event (calitem, event); + default: + break; + } + + return FALSE; +} + +static void +e_calendar_item_bounds (GnomeCanvasItem *item, + gdouble *x1, + gdouble *y1, + gdouble *x2, + gdouble *y2) +{ + ECalendarItem *calitem; + + g_return_if_fail (E_IS_CALENDAR_ITEM (item)); + + calitem = E_CALENDAR_ITEM (item); + *x1 = calitem->x1; + *y1 = calitem->y1; + *x2 = calitem->x2; + *y2 = calitem->y2; +} + +/* This checks if any fonts have changed, and if so it recalculates the + * text sizes and the minimum month size. */ +static void +e_calendar_item_recalc_sizes (ECalendarItem *calitem) +{ + GnomeCanvasItem *canvas_item; + GtkStyle *style; + gint day, max_day_width, digit, max_digit_width, max_week_number_digit_width; + gint char_height, width, min_cell_width, min_cell_height; + gchar buffer[64]; + struct tm tmp_tm; + PangoFontDescription *font_desc, *wkfont_desc; + PangoContext *pango_context; + PangoFontMetrics *font_metrics; + PangoLayout *layout; + + canvas_item = GNOME_CANVAS_ITEM (calitem); + style = gtk_widget_get_style (GTK_WIDGET (canvas_item->canvas)); + + if (!style) + return; + + /* Set up Pango prerequisites */ + font_desc = calitem->font_desc; + wkfont_desc = calitem->week_number_font_desc; + if (!font_desc) + font_desc = style->font_desc; + + pango_context = gtk_widget_create_pango_context ( + GTK_WIDGET (canvas_item->canvas)); + font_metrics = pango_context_get_metrics ( + pango_context, font_desc, + pango_context_get_language (pango_context)); + layout = pango_layout_new (pango_context); + + char_height = + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)); + + max_day_width = 0; + for (day = 0; day < 7; day++) { + layout_set_day_text (calitem, layout, day); + pango_layout_get_pixel_size (layout, &width, NULL); + + calitem->day_widths[day] = width; + max_day_width = MAX (max_day_width, width); + } + calitem->max_day_width = max_day_width; + + max_digit_width = 0; + max_week_number_digit_width = 0; + for (digit = 0; digit < 10; digit++) { + gchar locale_digit[5]; + gint locale_digit_len; + + locale_digit_len = sprintf (locale_digit, get_digit_fomat (), digit); + + pango_layout_set_text (layout, locale_digit, locale_digit_len); + pango_layout_get_pixel_size (layout, &width, NULL); + + calitem->digit_widths[digit] = width; + max_digit_width = MAX (max_digit_width, width); + + if (wkfont_desc) { + pango_context_set_font_description (pango_context, wkfont_desc); + pango_layout_context_changed (layout); + + pango_layout_set_text (layout, locale_digit, locale_digit_len); + pango_layout_get_pixel_size (layout, &width, NULL); + + calitem->week_number_digit_widths[digit] = width; + max_week_number_digit_width = MAX (max_week_number_digit_width, width); + + pango_context_set_font_description (pango_context, font_desc); + pango_layout_context_changed (layout); + } else { + calitem->week_number_digit_widths[digit] = width; + max_week_number_digit_width = max_digit_width; + } + } + calitem->max_digit_width = max_digit_width; + calitem->max_week_number_digit_width = max_week_number_digit_width; + + min_cell_width = MAX (calitem->max_day_width, (calitem->max_digit_width * 2)) + + E_CALENDAR_ITEM_MIN_CELL_XPAD; + min_cell_height = char_height + E_CALENDAR_ITEM_MIN_CELL_YPAD; + + calitem->min_month_width = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + + E_CALENDAR_ITEM_XPAD_BEFORE_CELLS + min_cell_width * 7 + + E_CALENDAR_ITEM_XPAD_AFTER_CELLS; + if (calitem->show_week_numbers) { + calitem->min_month_width += calitem->max_week_number_digit_width * 2 + + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1; + } + + calitem->min_month_height = style->ythickness * 2 + + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + char_height + + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME + 1 + + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + + char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1 + + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS + min_cell_height * 6 + + E_CALENDAR_ITEM_YPAD_BELOW_CELLS; + + calitem->max_month_name_width = 50; + memset (&tmp_tm, 0, sizeof (tmp_tm)); + tmp_tm.tm_year = 2000 - 100; + tmp_tm.tm_mday = 1; + tmp_tm.tm_isdst = -1; + for (tmp_tm.tm_mon = 0; tmp_tm.tm_mon < 12; tmp_tm.tm_mon++) { + mktime (&tmp_tm); + + e_utf8_strftime (buffer, sizeof (buffer), C_("CalItem", "%B"), &tmp_tm); + + pango_layout_set_text (layout, buffer, -1); + pango_layout_get_pixel_size (layout, &width, NULL); + + if (width > calitem->max_month_name_width) + calitem->max_month_name_width = width; + } + + g_object_unref (layout); + g_object_unref (pango_context); + pango_font_metrics_unref (font_metrics); +} + +static void +e_calendar_item_get_day_style (ECalendarItem *calitem, + gint year, + gint month, + gint day, + gint day_style, + gboolean today, + gboolean prev_or_next_month, + gboolean selected, + gboolean has_focus, + gboolean drop_target, + GdkColor **bg_color, + GdkColor **fg_color, + GdkColor **box_color, + gboolean *bold, + gboolean *italic) +{ + GtkWidget *widget; + GtkStyle *style; + + widget = GTK_WIDGET (GNOME_CANVAS_ITEM (calitem)->canvas); + style = gtk_widget_get_style (widget); + + *bg_color = NULL; + *fg_color = NULL; + *box_color = NULL; + + *bold = (day_style & E_CALENDAR_ITEM_MARK_BOLD) == + E_CALENDAR_ITEM_MARK_BOLD; + *italic = (day_style & E_CALENDAR_ITEM_MARK_ITALIC) == + E_CALENDAR_ITEM_MARK_ITALIC; + + if (today) + *box_color = &calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX]; + + if (prev_or_next_month) + *fg_color = &style->mid[gtk_widget_get_state (widget)]; + + if (selected) { + if (has_focus) { + *fg_color = &style->text[GTK_STATE_SELECTED]; + *bg_color = &style->base[GTK_STATE_SELECTED]; + } else { + *fg_color = &style->text[GTK_STATE_ACTIVE]; + *bg_color = &style->base[GTK_STATE_ACTIVE]; + + if ((*bg_color)->red == style->base[GTK_STATE_NORMAL].red && + (*bg_color)->green == style->base[GTK_STATE_NORMAL].green && + (*bg_color)->blue == style->base[GTK_STATE_NORMAL].blue) { + *fg_color = &style->text[GTK_STATE_SELECTED]; + *bg_color = &style->base[GTK_STATE_SELECTED]; + } + } + } +} + +static gboolean +e_calendar_item_button_press (ECalendarItem *calitem, + GdkEvent *button_event) +{ + GdkGrabStatus grab_status; + GdkDevice *event_device; + guint event_button = 0; + guint32 event_time; + gdouble event_x_win = 0; + gdouble event_y_win = 0; + gint month_offset, day, add_days = 0; + gboolean all_week, round_up_end = FALSE, round_down_start = FALSE; + + gdk_event_get_button (button_event, &event_button); + gdk_event_get_coords (button_event, &event_x_win, &event_y_win); + event_device = gdk_event_get_device (button_event); + event_time = gdk_event_get_time (button_event); + + if (event_button == 4) + e_calendar_item_set_first_month ( + calitem, calitem->year, + calitem->month - 1); + else if (event_button == 5) + e_calendar_item_set_first_month ( + calitem, calitem->year, + calitem->month + 1); + + if (!e_calendar_item_convert_position_to_day (calitem, + event_x_win, + event_y_win, + TRUE, + &month_offset, &day, + &all_week)) + return FALSE; + + if (event_button == 3 && day == -1 + && e_calendar_item_get_display_popup (calitem)) { + e_calendar_item_show_popup_menu ( + calitem, button_event, month_offset); + return TRUE; + } + + if (event_button != 1 || day == -1) + return FALSE; + + if (calitem->max_days_selected < 1) + return TRUE; + + grab_status = gnome_canvas_item_grab ( + GNOME_CANVAS_ITEM (calitem), + GDK_POINTER_MOTION_MASK | + GDK_BUTTON_RELEASE_MASK, + NULL, + event_device, + event_time); + + if (grab_status != GDK_GRAB_SUCCESS) + return FALSE; + + if (all_week && calitem->keep_wdays_on_weeknum_click) { + gint tmp_start_moff, tmp_start_day; + + tmp_start_moff = calitem->selection_start_month_offset; + tmp_start_day = calitem->selection_start_day; + e_calendar_item_round_down_selection ( + calitem, &tmp_start_moff, &tmp_start_day); + + e_calendar_item_round_down_selection (calitem, &month_offset, &day); + month_offset += calitem->selection_start_month_offset - tmp_start_moff; + day += calitem->selection_start_day - tmp_start_day; + + /* keep same count of days selected */ + add_days = e_calendar_item_get_inclusive_days ( + calitem, + calitem->selection_start_month_offset, + calitem->selection_start_day, + calitem->selection_end_month_offset, + calitem->selection_end_day) - 1; + } + + calitem->selection_set = TRUE; + calitem->selection_start_month_offset = month_offset; + calitem->selection_start_day = day; + calitem->selection_end_month_offset = month_offset; + calitem->selection_end_day = day; + + if (add_days > 0) + e_calendar_item_add_days_to_selection (calitem, add_days); + + calitem->selection_real_start_month_offset = month_offset; + calitem->selection_real_start_day = day; + + calitem->selection_from_full_week = FALSE; + calitem->selecting = TRUE; + calitem->selection_dragging_end = TRUE; + + if (all_week && !calitem->keep_wdays_on_weeknum_click) { + calitem->selection_from_full_week = TRUE; + round_up_end = TRUE; + } + + if (calitem->days_to_start_week_selection == 1) { + round_down_start = TRUE; + round_up_end = TRUE; + } + + /* Don't round up or down if we can't select a week or more, + * or when keeping week days. */ + if (calitem->max_days_selected < 7 || + (all_week && calitem->keep_wdays_on_weeknum_click)) { + round_down_start = FALSE; + round_up_end = FALSE; + } + + if (round_up_end) + e_calendar_item_round_up_selection ( + calitem, &calitem->selection_end_month_offset, + &calitem->selection_end_day); + + if (round_down_start) + e_calendar_item_round_down_selection ( + calitem, &calitem->selection_start_month_offset, + &calitem->selection_start_day); + + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); + + return TRUE; +} + +static gboolean +e_calendar_item_button_release (ECalendarItem *calitem, + GdkEvent *button_event) +{ + guint32 event_time; + + event_time = gdk_event_get_time (button_event); + e_calendar_item_stop_selecting (calitem, event_time); + + return FALSE; +} + +static gboolean +e_calendar_item_motion (ECalendarItem *calitem, + GdkEvent *event) +{ + gint start_month, start_day, end_month, end_day, month_offset, day; + gint tmp_month, tmp_day, days_in_selection; + gboolean all_week, round_up_end = FALSE, round_down_start = FALSE; + + if (!calitem->selecting) + return FALSE; + + if (!e_calendar_item_convert_position_to_day (calitem, + event->button.x, + event->button.y, + TRUE, + &month_offset, &day, + &all_week)) + return FALSE; + + if (day == -1) + return FALSE; + + if (calitem->selection_dragging_end) { + start_month = calitem->selection_real_start_month_offset; + start_day = calitem->selection_real_start_day; + end_month = month_offset; + end_day = day; + } else { + start_month = month_offset; + start_day = day; + end_month = calitem->selection_real_start_month_offset; + end_day = calitem->selection_real_start_day; + } + + if (start_month > end_month || (start_month == end_month + && start_day > end_day)) { + tmp_month = start_month; + tmp_day = start_day; + start_month = end_month; + start_day = end_day; + end_month = tmp_month; + end_day = tmp_day; + + calitem->selection_dragging_end = + !calitem->selection_dragging_end; + } + + if (calitem->days_to_start_week_selection > 0) { + days_in_selection = e_calendar_item_get_inclusive_days ( + calitem, start_month, start_day, end_month, end_day); + if (days_in_selection >= calitem->days_to_start_week_selection) { + round_down_start = TRUE; + round_up_end = TRUE; + } + } + + /* If we are over a week number and we are dragging the end of the + * selection, we round up to the end of this week. */ + if (all_week && calitem->selection_dragging_end) + round_up_end = TRUE; + + /* If the selection was started from a week number and we are dragging + * the start of the selection, we need to round up the end to include + * all of the original week selected. */ + if (calitem->selection_from_full_week + && !calitem->selection_dragging_end) + round_up_end = TRUE; + + /* Don't round up or down if we can't select a week or more. */ + if (calitem->max_days_selected < 7) { + round_down_start = FALSE; + round_up_end = FALSE; + } + + if (round_up_end) + e_calendar_item_round_up_selection ( + calitem, &end_month, + &end_day); + if (round_down_start) + e_calendar_item_round_down_selection ( + calitem, &start_month, + &start_day); + + /* Check we don't go over the maximum number of days to select. */ + if (calitem->selection_dragging_end) { + e_calendar_item_check_selection_end ( + calitem, + start_month, + start_day, + &end_month, + &end_day); + } else { + e_calendar_item_check_selection_start ( + calitem, + &start_month, + &start_day, + end_month, + end_day); + } + + if (start_month == calitem->selection_start_month_offset + && start_day == calitem->selection_start_day + && end_month == calitem->selection_end_month_offset + && end_day == calitem->selection_end_day) + return FALSE; + + calitem->selection_start_month_offset = start_month; + calitem->selection_start_day = start_day; + calitem->selection_end_month_offset = end_month; + calitem->selection_end_day = end_day; + + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); + + return TRUE; +} + +static void +e_calendar_item_check_selection_end (ECalendarItem *calitem, + gint start_month, + gint start_day, + gint *end_month, + gint *end_day) +{ + gint year, month, max_month, max_day, days_in_month; + + if (calitem->max_days_selected <= 0) + return; + + year = calitem->year; + month = calitem->month + start_month; + e_calendar_item_normalize_date (calitem, &year, &month); + + max_month = start_month; + max_day = start_day + calitem->max_days_selected - 1; + + for (;;) { + days_in_month = DAYS_IN_MONTH (year, month); + if (max_day <= days_in_month) + break; + max_month++; + month++; + if (month == 12) { + year++; + month = 0; + } + max_day -= days_in_month; + } + + if (*end_month > max_month) { + *end_month = max_month; + *end_day = max_day; + } else if (*end_month == max_month && *end_day > max_day) { + *end_day = max_day; + } +} + +static void +e_calendar_item_check_selection_start (ECalendarItem *calitem, + gint *start_month, + gint *start_day, + gint end_month, + gint end_day) +{ + gint year, month, min_month, min_day, days_in_month; + + if (calitem->max_days_selected <= 0) + return; + + year = calitem->year; + month = calitem->month + end_month; + e_calendar_item_normalize_date (calitem, &year, &month); + + min_month = end_month; + min_day = end_day - calitem->max_days_selected + 1; + + while (min_day <= 0) { + min_month--; + month--; + if (month == -1) { + year--; + month = 11; + } + days_in_month = DAYS_IN_MONTH (year, month); + min_day += days_in_month; + } + + if (*start_month < min_month) { + *start_month = min_month; + *start_day = min_day; + } else if (*start_month == min_month && *start_day < min_day) { + *start_day = min_day; + } +} + +/* Converts a position within the item to a month & day. + * The month returned is 0 for the top-left month displayed. + * If the position is over the month heading -1 is returned for the day. + * If the position is over a week number the first day of the week is returned + * and entire_week is set to TRUE. + * It returns FALSE if the position is completely outside all months. */ +static gboolean +e_calendar_item_convert_position_to_day (ECalendarItem *calitem, + gint event_x, + gint event_y, + gboolean round_empty_positions, + gint *month_offset, + gint *day, + gboolean *entire_week) +{ + GnomeCanvasItem *item; + GtkWidget *widget; + GtkStyle *style; + gint xthickness, ythickness, char_height; + gint x, y, row, col, cells_x, cells_y, day_row, day_col; + gint first_day_offset, days_in_month, days_in_prev_month; + gint week_num_x1, week_num_x2; + PangoFontDescription *font_desc; + PangoContext *pango_context; + PangoFontMetrics *font_metrics; + + item = GNOME_CANVAS_ITEM (calitem); + widget = GTK_WIDGET (item->canvas); + style = gtk_widget_get_style (widget); + + font_desc = calitem->font_desc; + if (!font_desc) + font_desc = style->font_desc; + pango_context = gtk_widget_create_pango_context (widget); + font_metrics = pango_context_get_metrics ( + pango_context, font_desc, + pango_context_get_language (pango_context)); + + char_height = + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)); + xthickness = style->xthickness; + ythickness = style->ythickness; + + pango_font_metrics_unref (font_metrics); + + *entire_week = FALSE; + + x = event_x - xthickness - calitem->x_offset; + y = event_y - ythickness; + + if (x < 0 || y < 0) + return FALSE; + + row = y / calitem->month_height; + col = x / calitem->month_width; + + if (row >= calitem->rows || col >= calitem->cols) + return FALSE; + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + col = calitem->cols - 1 - col; + + *month_offset = row * calitem->cols + col; + + x = x % calitem->month_width; + y = y % calitem->month_height; + + if (y < ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME) { + *day = -1; + return TRUE; + } + + cells_y = ythickness * 2 + E_CALENDAR_ITEM_YPAD_ABOVE_MONTH_NAME + + char_height + E_CALENDAR_ITEM_YPAD_BELOW_MONTH_NAME + + E_CALENDAR_ITEM_YPAD_ABOVE_DAY_LETTERS + calitem->month_tpad + + char_height + E_CALENDAR_ITEM_YPAD_BELOW_DAY_LETTERS + 1 + + E_CALENDAR_ITEM_YPAD_ABOVE_CELLS; + y -= cells_y; + if (y < 0) + return FALSE; + day_row = y / calitem->cell_height; + if (day_row >= E_CALENDAR_ROWS_PER_MONTH) + return FALSE; + + week_num_x1 = E_CALENDAR_ITEM_XPAD_BEFORE_WEEK_NUMBERS + calitem->month_lpad; + + if (calitem->show_week_numbers) { + week_num_x2 = week_num_x1 + + calitem->max_week_number_digit_width * 2; + if (x >= week_num_x1 && x < week_num_x2) + *entire_week = TRUE; + cells_x = week_num_x2 + E_CALENDAR_ITEM_XPAD_AFTER_WEEK_NUMBERS + 1; + } else { + cells_x = week_num_x1; + } + + if (*entire_week) { + day_col = 0; + } else { + cells_x += E_CALENDAR_ITEM_XPAD_BEFORE_CELLS; + x -= cells_x; + if (x < 0) + return FALSE; + day_col = x / calitem->cell_width; + if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL) + day_col = E_CALENDAR_COLS_PER_MONTH - 1 - day_col; + if (day_col >= E_CALENDAR_COLS_PER_MONTH) + return FALSE; + } + + *day = day_row * E_CALENDAR_COLS_PER_MONTH + day_col; + + e_calendar_item_get_month_info ( + calitem, row, col, &first_day_offset, + &days_in_month, &days_in_prev_month); + if (*day < first_day_offset) { + if (*entire_week || (row == 0 && col == 0)) { + (*month_offset)--; + *day = days_in_prev_month + 1 - first_day_offset + + *day; + return TRUE; + } else if (round_empty_positions) { + *day = first_day_offset; + } else { + return FALSE; + } + } + + *day -= first_day_offset - 1; + + if (*day > days_in_month) { + if (row == calitem->rows - 1 && col == calitem->cols - 1) { + (*month_offset)++; + *day -= days_in_month; + return TRUE; + } else if (round_empty_positions) { + *day = days_in_month; + } else { + return FALSE; + } + } + + return TRUE; +} + +static void +e_calendar_item_get_month_info (ECalendarItem *calitem, + gint row, + gint col, + gint *first_day_offset, + gint *days_in_month, + gint *days_in_prev_month) +{ + gint year, month, start_weekday, first_day_of_month; + struct tm tmp_tm = { 0 }; + + month = calitem->month + row * calitem->cols + col; + year = calitem->year + month / 12; + month = month % 12; + + *days_in_month = DAYS_IN_MONTH (year, month); + if (month == 0) + *days_in_prev_month = DAYS_IN_MONTH (year - 1, 11); + else + *days_in_prev_month = DAYS_IN_MONTH (year, month - 1); + + tmp_tm.tm_year = year - 1900; + tmp_tm.tm_mon = month; + tmp_tm.tm_mday = 1; + tmp_tm.tm_isdst = -1; + mktime (&tmp_tm); + + /* Convert to 0 (Monday) to 6 (Sunday). */ + start_weekday = (tmp_tm.tm_wday + 6) % 7; + + first_day_of_month = (start_weekday + 7 - calitem->week_start_day) % 7; + + if (row == 0 && col == 0 && first_day_of_month == 0) + *first_day_offset = 7; + else + *first_day_offset = first_day_of_month; +} + +void +e_calendar_item_get_first_month (ECalendarItem *calitem, + gint *year, + gint *month) +{ + *year = calitem->year; + *month = calitem->month; +} + +static void +e_calendar_item_preserve_day_selection (ECalendarItem *calitem, + gint selected_day, + gint *month_offset, + gint *day) +{ + gint year, month, weekday, days, days_in_month; + struct tm tmp_tm = { 0 }; + + year = calitem->year; + month = calitem->month + *month_offset; + e_calendar_item_normalize_date (calitem, &year, &month); + + tmp_tm.tm_year = year - 1900; + tmp_tm.tm_mon = month; + tmp_tm.tm_mday = *day; + tmp_tm.tm_isdst = -1; + mktime (&tmp_tm); + + /* Convert to 0 (Monday) to 6 (Sunday). */ + weekday = (tmp_tm.tm_wday + 6) % 7; + + /* Calculate how many days to the start of the row. */ + days = (weekday + 7 - selected_day) % 7; + + *day -= days; + if (*day <= 0) { + month--; + if (month == -1) { + year--; + month = 11; + } + days_in_month = DAYS_IN_MONTH (year, month); + (*month_offset)--; + *day += days_in_month; + } +} + +/* This also handles values of month < 0 or > 11 by updating the year. */ +void +e_calendar_item_set_first_month (ECalendarItem *calitem, + gint year, + gint month) +{ + gint new_year, new_month, months_diff, num_months; + gint old_days_in_selection, new_days_in_selection; + + new_year = year; + new_month = month; + e_calendar_item_normalize_date (calitem, &new_year, &new_month); + + if (calitem->year == new_year && calitem->month == new_month) + return; + + /* Update the selection. */ + num_months = calitem->rows * calitem->cols; + months_diff = (new_year - calitem->year) * 12 + + new_month - calitem->month; + + if (calitem->selection_set) { + if (!calitem->move_selection_when_moving + || (calitem->selection_start_month_offset - months_diff >= 0 + && calitem->selection_end_month_offset - months_diff < num_months)) { + calitem->selection_start_month_offset -= months_diff; + calitem->selection_end_month_offset -= months_diff; + calitem->selection_real_start_month_offset -= months_diff; + + calitem->year = new_year; + calitem->month = new_month; + } else { + gint selected_day; + struct tm tmp_tm = { 0 }; + + old_days_in_selection = e_calendar_item_get_inclusive_days ( + calitem, + calitem->selection_start_month_offset, + calitem->selection_start_day, + calitem->selection_end_month_offset, + calitem->selection_end_day); + + /* Calculate the currently selected day */ + tmp_tm.tm_year = calitem->year - 1900; + tmp_tm.tm_mon = calitem->month + calitem->selection_start_month_offset; + tmp_tm.tm_mday = calitem->selection_start_day; + tmp_tm.tm_isdst = -1; + mktime (&tmp_tm); + + selected_day = (tmp_tm.tm_wday + 6) % 7; + + /* Make sure the selection will be displayed. */ + if (calitem->selection_start_month_offset < 0 + || calitem->selection_start_month_offset >= num_months) { + calitem->selection_end_month_offset -= + calitem->selection_start_month_offset; + calitem->selection_start_month_offset = 0; + } + + /* We want to ensure that the same number of days are + * selected after we have moved the selection. */ + calitem->year = new_year; + calitem->month = new_month; + + e_calendar_item_ensure_valid_day ( + calitem, &calitem->selection_start_month_offset, + &calitem->selection_start_day); + e_calendar_item_ensure_valid_day ( + calitem, &calitem->selection_end_month_offset, + &calitem->selection_end_day); + + if (calitem->preserve_day_when_moving) { + e_calendar_item_preserve_day_selection ( + calitem, selected_day, + &calitem->selection_start_month_offset, + &calitem->selection_start_day); + } + + new_days_in_selection = e_calendar_item_get_inclusive_days ( + calitem, + calitem->selection_start_month_offset, + calitem->selection_start_day, + calitem->selection_end_month_offset, + calitem->selection_end_day); + + if (old_days_in_selection != new_days_in_selection) + e_calendar_item_add_days_to_selection ( + calitem, old_days_in_selection - + new_days_in_selection); + + /* Flag that we need to emit the "selection_changed" + * signal. We don't want to emit it here since setting + * the "year" and "month" args would result in 2 + * signals emitted. */ + calitem->selection_changed = TRUE; + } + } else { + calitem->year = new_year; + calitem->month = new_month; + } + + e_calendar_item_date_range_changed (calitem); + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); +} + +/* Get the maximum number of days selectable */ +gint +e_calendar_item_get_max_days_sel (ECalendarItem *calitem) +{ + return calitem->max_days_selected; +} + +/* Set the maximum number of days selectable */ +void +e_calendar_item_set_max_days_sel (ECalendarItem *calitem, + gint days) +{ + calitem->max_days_selected = MAX (0, days); + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); +} + +/* Get the maximum number of days before whole weeks are selected */ +gint +e_calendar_item_get_days_start_week_sel (ECalendarItem *calitem) +{ + return calitem->days_to_start_week_selection; +} + +/* Set the maximum number of days before whole weeks are selected */ +void +e_calendar_item_set_days_start_week_sel (ECalendarItem *calitem, + gint days) +{ + calitem->days_to_start_week_selection = days; +} + +gboolean +e_calendar_item_get_display_popup (ECalendarItem *calitem) +{ + return calitem->display_popup; +} + +void +e_calendar_item_set_display_popup (ECalendarItem *calitem, + gboolean display) +{ + calitem->display_popup = display; +} + +/* This will make sure that the given year & month are valid, i.e. if month + * is < 0 or > 11 the year and month will be updated accordingly. */ +void +e_calendar_item_normalize_date (ECalendarItem *calitem, + gint *year, + gint *month) +{ + if (*month >= 0) { + *year += *month / 12; + *month = *month % 12; + } else { + *year += *month / 12 - 1; + *month = *month % 12; + if (*month != 0) + *month += 12; + } +} + +/* Adds or subtracts days from the selection. It is used when we switch months + * and the selection extends past the end of a month but we want to keep the + * number of days selected the same. days should not be more than 30. */ +static void +e_calendar_item_add_days_to_selection (ECalendarItem *calitem, + gint days) +{ + gint year, month, days_in_month; + + year = calitem->year; + month = calitem->month + calitem->selection_end_month_offset; + e_calendar_item_normalize_date (calitem, &year, &month); + + calitem->selection_end_day += days; + if (calitem->selection_end_day <= 0) { + month--; + e_calendar_item_normalize_date (calitem, &year, &month); + calitem->selection_end_month_offset--; + calitem->selection_end_day += DAYS_IN_MONTH (year, month); + } else { + days_in_month = DAYS_IN_MONTH (year, month); + if (calitem->selection_end_day > days_in_month) { + calitem->selection_end_month_offset++; + calitem->selection_end_day -= days_in_month; + } + } +} + +/* Gets the range of dates actually shown. Months are 0 to 11. + * This also includes the last days of the previous month and the first days + * of the following month, which are normally shown in gray. + * It returns FALSE if no dates are currently shown. */ +gboolean +e_calendar_item_get_date_range (ECalendarItem *calitem, + gint *start_year, + gint *start_month, + gint *start_day, + gint *end_year, + gint *end_month, + gint *end_day) +{ + gint first_day_offset, days_in_month, days_in_prev_month; + + if (calitem->rows == 0 || calitem->cols == 0) + return FALSE; + + /* Calculate the first day shown. This will be one of the greyed-out + * days before the first full month begins. */ + e_calendar_item_get_month_info ( + calitem, 0, 0, &first_day_offset, + &days_in_month, &days_in_prev_month); + *start_year = calitem->year; + *start_month = calitem->month - 1; + if (*start_month == -1) { + (*start_year)--; + *start_month = 11; + } + *start_day = days_in_prev_month + 1 - first_day_offset; + + /* Calculate the last day shown. This will be one of the greyed-out + * days after the last full month ends. */ + e_calendar_item_get_month_info ( + calitem, calitem->rows - 1, + calitem->cols - 1, &first_day_offset, + &days_in_month, &days_in_prev_month); + *end_month = calitem->month + calitem->rows * calitem->cols; + *end_year = calitem->year + *end_month / 12; + *end_month %= 12; + *end_day = E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH + - first_day_offset - days_in_month; + + return TRUE; +} + +/* Simple way to mark days so they appear bold. + * A more flexible interface may be added later. */ +void +e_calendar_item_clear_marks (ECalendarItem *calitem) +{ + GnomeCanvasItem *item; + + item = GNOME_CANVAS_ITEM (calitem); + + g_free (calitem->styles); + calitem->styles = NULL; + + gnome_canvas_request_redraw ( + item->canvas, item->x1, item->y1, + item->x2, item->y2); +} + +/* add_day_style - whether bit-or with the actual style or change the style fully */ +void +e_calendar_item_mark_day (ECalendarItem *calitem, + gint year, + gint month, + gint day, + guint8 day_style, + gboolean add_day_style) +{ + gint month_offset; + gint index; + + month_offset = (year - calitem->year) * 12 + month - calitem->month; + if (month_offset < -1 || month_offset > calitem->rows * calitem->cols) + return; + + if (!calitem->styles) + calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32); + + index = (month_offset + 1) * 32 + day; + calitem->styles[index] = day_style | + (add_day_style ? calitem->styles[index] : 0); + + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); +} + +void +e_calendar_item_mark_days (ECalendarItem *calitem, + gint start_year, + gint start_month, + gint start_day, + gint end_year, + gint end_month, + gint end_day, + guint8 day_style, + gboolean add_day_style) +{ + gint month_offset, end_month_offset, day; + + month_offset = (start_year - calitem->year) * 12 + start_month + - calitem->month; + day = start_day; + if (month_offset > calitem->rows * calitem->cols) + return; + if (month_offset < -1) { + month_offset = -1; + day = 1; + } + + end_month_offset = (end_year - calitem->year) * 12 + end_month + - calitem->month; + if (end_month_offset < -1) + return; + if (end_month_offset > calitem->rows * calitem->cols) { + end_month_offset = calitem->rows * calitem->cols; + end_day = 31; + } + + if (month_offset > end_month_offset) + return; + + if (!calitem->styles) + calitem->styles = g_new0 (guint8, (calitem->rows * calitem->cols + 2) * 32); + + for (;;) { + gint index; + + if (month_offset == end_month_offset && day > end_day) + break; + + if (month_offset < -1 || month_offset > calitem->rows * calitem->cols) + g_warning ("Bad month offset: %i\n", month_offset); + if (day < 1 || day > 31) + g_warning ("Bad day: %i\n", day); + +#if 0 + g_print ("Marking Month:%i Day:%i\n", month_offset, day); +#endif + index = (month_offset + 1) * 32 + day; + calitem->styles[index] = day_style | + (add_day_style ? calitem->styles[index] : 0); + + day++; + if (day == 32) { + month_offset++; + day = 1; + if (month_offset > end_month_offset) + break; + } + } + + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); +} + +/* Rounds up the given day to the end of the week. */ +static void +e_calendar_item_round_up_selection (ECalendarItem *calitem, + gint *month_offset, + gint *day) +{ + gint year, month, weekday, days, days_in_month; + struct tm tmp_tm = { 0 }; + + year = calitem->year; + month = calitem->month + *month_offset; + e_calendar_item_normalize_date (calitem, &year, &month); + + tmp_tm.tm_year = year - 1900; + tmp_tm.tm_mon = month; + tmp_tm.tm_mday = *day; + tmp_tm.tm_isdst = -1; + mktime (&tmp_tm); + + /* Convert to 0 (Monday) to 6 (Sunday). */ + weekday = (tmp_tm.tm_wday + 6) % 7; + + /* Calculate how many days to the end of the row. */ + days = (calitem->week_start_day + 6 - weekday) % 7; + + *day += days; + days_in_month = DAYS_IN_MONTH (year, month); + if (*day > days_in_month) { + (*month_offset)++; + *day -= days_in_month; + } +} + +/* Rounds down the given day to the start of the week. */ +static void +e_calendar_item_round_down_selection (ECalendarItem *calitem, + gint *month_offset, + gint *day) +{ + gint year, month, weekday, days, days_in_month; + struct tm tmp_tm = { 0 }; + + year = calitem->year; + month = calitem->month + *month_offset; + e_calendar_item_normalize_date (calitem, &year, &month); + + tmp_tm.tm_year = year - 1900; + tmp_tm.tm_mon = month; + tmp_tm.tm_mday = *day; + tmp_tm.tm_isdst = -1; + mktime (&tmp_tm); + + /* Convert to 0 (Monday) to 6 (Sunday). */ + weekday = (tmp_tm.tm_wday + 6) % 7; + + /* Calculate how many days to the start of the row. */ + days = (weekday + 7 - calitem->week_start_day) % 7; + + *day -= days; + if (*day <= 0) { + month--; + if (month == -1) { + year--; + month = 11; + } + days_in_month = DAYS_IN_MONTH (year, month); + (*month_offset)--; + *day += days_in_month; + } +} + +static gint +e_calendar_item_get_inclusive_days (ECalendarItem *calitem, + gint start_month_offset, + gint start_day, + gint end_month_offset, + gint end_day) +{ + gint start_year, start_month, end_year, end_month, days = 0; + + start_year = calitem->year; + start_month = calitem->month + start_month_offset; + e_calendar_item_normalize_date (calitem, &start_year, &start_month); + + end_year = calitem->year; + end_month = calitem->month + end_month_offset; + e_calendar_item_normalize_date (calitem, &end_year, &end_month); + + while (start_year < end_year || start_month < end_month) { + days += DAYS_IN_MONTH (start_year, start_month); + start_month++; + if (start_month == 12) { + start_year++; + start_month = 0; + } + } + + days += end_day - start_day + 1; + + return days; +} + +/* If the day is off the end of the month it is set to the last day of the + * month. */ +static void +e_calendar_item_ensure_valid_day (ECalendarItem *calitem, + gint *month_offset, + gint *day) +{ + gint year, month, days_in_month; + + year = calitem->year; + month = calitem->month + *month_offset; + e_calendar_item_normalize_date (calitem, &year, &month); + + days_in_month = DAYS_IN_MONTH (year, month); + if (*day > days_in_month) + *day = days_in_month; +} + +gboolean +e_calendar_item_get_selection (ECalendarItem *calitem, + GDate *start_date, + GDate *end_date) +{ + gint start_year, start_month, start_day; + gint end_year, end_month, end_day; + + g_date_clear (start_date, 1); + g_date_clear (end_date, 1); + + if (!calitem->selection_set) + return FALSE; + + start_year = calitem->year; + start_month = calitem->month + calitem->selection_start_month_offset; + e_calendar_item_normalize_date (calitem, &start_year, &start_month); + start_day = calitem->selection_start_day; + + end_year = calitem->year; + end_month = calitem->month + calitem->selection_end_month_offset; + e_calendar_item_normalize_date (calitem, &end_year, &end_month); + end_day = calitem->selection_end_day; + + g_date_set_dmy (start_date, start_day, start_month + 1, start_year); + g_date_set_dmy (end_date, end_day, end_month + 1, end_year); + + return TRUE; +} + +static void +e_calendar_item_set_selection_if_emission (ECalendarItem *calitem, + const GDate *start_date, + const GDate *end_date, + gboolean emission) +{ + gint start_year, start_month, start_day; + gint end_year, end_month, end_day; + gint new_start_month_offset, new_start_day; + gint new_end_month_offset, new_end_day; + gboolean need_update; + + g_return_if_fail (E_IS_CALENDAR_ITEM (calitem)); + + /* If start_date is NULL, we clear the selection without changing the + * month shown. */ + if (start_date == NULL) { + calitem->selection_set = FALSE; + calitem->selection_changed = TRUE; + e_calendar_item_queue_signal_emission (calitem); + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); + return; + } + + if (end_date == NULL) + end_date = start_date; + + g_return_if_fail (g_date_compare (start_date, end_date) <= 0); + + start_year = g_date_get_year (start_date); + start_month = g_date_get_month (start_date) - 1; + start_day = g_date_get_day (start_date); + end_year = g_date_get_year (end_date); + end_month = g_date_get_month (end_date) - 1; + end_day = g_date_get_day (end_date); + + need_update = e_calendar_item_ensure_days_visible ( + calitem, + start_year, + start_month, + start_day, + end_year, + end_month, + end_day, + emission); + + new_start_month_offset = (start_year - calitem->year) * 12 + + start_month - calitem->month; + new_start_day = start_day; + + /* This may go outside the visible months, but we don't care. */ + new_end_month_offset = (end_year - calitem->year) * 12 + + end_month - calitem->month; + new_end_day = end_day; + + if (!calitem->selection_set + || calitem->selection_start_month_offset != new_start_month_offset + || calitem->selection_start_day != new_start_day + || calitem->selection_end_month_offset != new_end_month_offset + || calitem->selection_end_day != new_end_day) { + need_update = TRUE; + if (emission) { + calitem->selection_changed = TRUE; + e_calendar_item_queue_signal_emission (calitem); + } + calitem->selection_set = TRUE; + calitem->selection_start_month_offset = new_start_month_offset; + calitem->selection_start_day = new_start_day; + calitem->selection_end_month_offset = new_end_month_offset; + calitem->selection_end_day = new_end_day; + + calitem->selection_real_start_month_offset = new_start_month_offset; + calitem->selection_real_start_day = new_start_day; + calitem->selection_from_full_week = FALSE; + } + + if (need_update) { + g_signal_emit ( + calitem, + e_calendar_item_signals[DATE_RANGE_CHANGED], 0); + gnome_canvas_item_request_update (GNOME_CANVAS_ITEM (calitem)); + } +} + +void +e_calendar_item_style_set (GtkWidget *widget, + ECalendarItem *calitem) +{ + GtkStyle *style; + GdkColor *color; + + style = gtk_widget_get_style (widget); + + color = &style->bg[GTK_STATE_SELECTED]; + calitem->colors[E_CALENDAR_ITEM_COLOR_TODAY_BOX] = *color; + + color = &style->base[GTK_STATE_NORMAL]; + calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_FG] = *color; + + color = &style->bg[GTK_STATE_SELECTED]; + calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG_FOCUSED] = *color; + + color = &style->fg[GTK_STATE_INSENSITIVE]; + calitem->colors[E_CALENDAR_ITEM_COLOR_SELECTION_BG] = *color; + + color = &style->fg[GTK_STATE_INSENSITIVE]; + calitem->colors[E_CALENDAR_ITEM_COLOR_PREV_OR_NEXT_MONTH_FG] = *color; + + e_calendar_item_recalc_sizes (calitem); +} + +void +e_calendar_item_set_selection (ECalendarItem *calitem, + const GDate *start_date, + const GDate *end_date) +{ + /* If the user is in the middle of a selection, we must abort it. */ + if (calitem->selecting) { + gnome_canvas_item_ungrab ( + GNOME_CANVAS_ITEM (calitem), + GDK_CURRENT_TIME); + calitem->selecting = FALSE; + } + + e_calendar_item_set_selection_if_emission (calitem, + start_date, end_date, + TRUE); +} + +/* This tries to ensure that the given time range is visible. If the range + * given is longer than we can show, only the start of it will be visible. + * Note that this will not update the selection. That should be done somewhere + * else. It returns TRUE if the visible range has been changed. */ +static gboolean +e_calendar_item_ensure_days_visible (ECalendarItem *calitem, + gint start_year, + gint start_month, + gint start_day, + gint end_year, + gint end_month, + gint end_day, + gboolean emission) +{ + gint current_end_year, current_end_month; + gint months_shown; + gint first_day_offset, days_in_month, days_in_prev_month; + gboolean need_update = FALSE; + + months_shown = calitem->rows * calitem->cols; + + /* Calculate the range of months currently displayed. */ + current_end_year = calitem->year; + current_end_month = calitem->month + months_shown - 1; + e_calendar_item_normalize_date ( + calitem, ¤t_end_year, + ¤t_end_month); + + /* Try to ensure that the end month is shown. */ + if ((end_year == current_end_year + 1 && + current_end_month == 11 && end_month == 0) || + (end_year == current_end_year && end_month == current_end_month + 1)) { + /* See if the end of the selection will fit in the + * leftover days of the month after the last one shown. */ + calitem->month += (months_shown - 1); + e_calendar_item_normalize_date ( + calitem, &calitem->year, + &calitem->month); + + e_calendar_item_get_month_info ( + calitem, 0, 0, + &first_day_offset, + &days_in_month, + &days_in_prev_month); + + if (end_day >= E_CALENDAR_ROWS_PER_MONTH * E_CALENDAR_COLS_PER_MONTH - + first_day_offset - days_in_month) { + need_update = TRUE; + + calitem->year = end_year; + calitem->month = end_month - months_shown + 1; + } else { + calitem->month -= (months_shown - 1); + } + + e_calendar_item_normalize_date ( + calitem, &calitem->year, + &calitem->month); + } + else if (end_year > current_end_year || + (end_year == current_end_year && end_month > current_end_month)) { + /* The selection will definitely not fit in the leftover days + * of the month after the last one shown. */ + need_update = TRUE; + + calitem->year = end_year; + calitem->month = end_month - months_shown + 1; + + e_calendar_item_normalize_date ( + calitem, &calitem->year, + &calitem->month); + } + + /* Now try to ensure that the start month is shown. We do this after + * the end month so that the start month will always be shown. */ + if (start_year < calitem->year + || (start_year == calitem->year + && start_month < calitem->month)) { + need_update = TRUE; + + /* First we see if the start of the selection will fit in the + * leftover days of the month before the first one shown. */ + calitem->year = start_year; + calitem->month = start_month + 1; + e_calendar_item_normalize_date ( + calitem, &calitem->year, + &calitem->month); + + e_calendar_item_get_month_info ( + calitem, 0, 0, + &first_day_offset, + &days_in_month, + &days_in_prev_month); + + if (start_day <= days_in_prev_month - first_day_offset) { + calitem->year = start_year; + calitem->month = start_month; + } + } + + if (need_update && emission) + e_calendar_item_date_range_changed (calitem); + + return need_update; +} + +static gboolean +destroy_menu_idle_cb (gpointer menu) +{ + gtk_widget_destroy (menu); + + return FALSE; +} + +static void +deactivate_menu_cb (GtkWidget *menu) +{ + g_signal_handlers_disconnect_by_func (menu, deactivate_menu_cb, NULL); + + g_idle_add (destroy_menu_idle_cb, menu); +} + +static void +e_calendar_item_show_popup_menu (ECalendarItem *calitem, + GdkEvent *button_event, + gint month_offset) +{ + GtkWidget *menu, *submenu, *menuitem, *label; + gint year, month; + const gchar *name; + gchar buffer[64]; + guint event_button = 0; + guint32 event_time; + + menu = gtk_menu_new (); + + for (year = calitem->year - 2; year <= calitem->year + 2; year++) { + g_snprintf (buffer, 64, "%i", year); + menuitem = gtk_menu_item_new_with_label (buffer); + gtk_widget_show (menuitem); + gtk_container_add (GTK_CONTAINER (menu), menuitem); + + submenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menuitem), submenu); + + g_object_set_data ( + G_OBJECT (submenu), "year", + GINT_TO_POINTER (year)); + g_object_set_data ( + G_OBJECT (submenu), "month_offset", + GINT_TO_POINTER (month_offset)); + + for (month = 0; month < 12; month++) { + name = e_get_month_name (month + 1, FALSE); + + menuitem = gtk_menu_item_new (); + gtk_widget_show (menuitem); + gtk_container_add (GTK_CONTAINER (submenu), menuitem); + + label = gtk_label_new (name); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_widget_show (label); + gtk_container_add (GTK_CONTAINER (menuitem), label); + + g_object_set_data ( + G_OBJECT (menuitem), "month", + GINT_TO_POINTER (month)); + + g_signal_connect ( + menuitem, "activate", + G_CALLBACK (e_calendar_item_on_menu_item_activate), + calitem); + } + } + + g_signal_connect ( + menu, "deactivate", + G_CALLBACK (deactivate_menu_cb), NULL); + + gdk_event_get_button (button_event, &event_button); + event_time = gdk_event_get_time (button_event); + + gtk_menu_popup ( + GTK_MENU (menu), NULL, NULL, + e_calendar_item_position_menu, calitem, + event_button, event_time); +} + +static void +e_calendar_item_on_menu_item_activate (GtkWidget *menuitem, + ECalendarItem *calitem) +{ + GtkWidget *parent; + gint year, month_offset, month; + gpointer data; + + parent = gtk_widget_get_parent (menuitem); + data = g_object_get_data (G_OBJECT (parent), "year"); + year = GPOINTER_TO_INT (data); + + parent = gtk_widget_get_parent (menuitem); + data = g_object_get_data (G_OBJECT (parent), "month_offset"); + month_offset = GPOINTER_TO_INT (data); + + data = g_object_get_data (G_OBJECT (menuitem), "month"); + month = GPOINTER_TO_INT (data); + + month -= month_offset; + e_calendar_item_normalize_date (calitem, &year, &month); + e_calendar_item_set_first_month (calitem, year, month); +} + +static void +e_calendar_item_position_menu (GtkMenu *menu, + gint *x, + gint *y, + gboolean *push_in, + gpointer user_data) +{ + GtkRequisition requisition; + gint screen_width, screen_height; + + gtk_widget_get_preferred_size (GTK_WIDGET (menu), &requisition, NULL); + + *x -= (gtk_widget_get_direction(GTK_WIDGET(menu)) == GTK_TEXT_DIR_RTL) + ? requisition.width - 2 + : 2; + *y -= requisition.height / 2; + + screen_width = gdk_screen_width (); + screen_height = gdk_screen_height (); + + *x = CLAMP (*x, 0, screen_width - requisition.width); + *y = CLAMP (*y, 0, screen_height - requisition.height); +} + +/* Sets the function to call to get the colors to use for a particular day. */ +void +e_calendar_item_set_style_callback (ECalendarItem *calitem, + ECalendarItemStyleCallback cb, + gpointer data, + GDestroyNotify destroy) +{ + g_return_if_fail (E_IS_CALENDAR_ITEM (calitem)); + + if (calitem->style_callback_data && calitem->style_callback_destroy) + (*calitem->style_callback_destroy) (calitem->style_callback_data); + + calitem->style_callback = cb; + calitem->style_callback_data = data; + calitem->style_callback_destroy = destroy; +} + +static void +e_calendar_item_date_range_changed (ECalendarItem *calitem) +{ + g_free (calitem->styles); + calitem->styles = NULL; + calitem->date_range_changed = TRUE; + e_calendar_item_queue_signal_emission (calitem); +} + +static void +e_calendar_item_queue_signal_emission (ECalendarItem *calitem) +{ + if (calitem->signal_emission_idle_id == 0) { + calitem->signal_emission_idle_id = g_idle_add_full ( + G_PRIORITY_HIGH, (GSourceFunc) + e_calendar_item_signal_emission_idle_cb, + calitem, NULL); + } +} + +static gboolean +e_calendar_item_signal_emission_idle_cb (gpointer data) +{ + ECalendarItem *calitem; + + g_return_val_if_fail (E_IS_CALENDAR_ITEM (data), FALSE); + + calitem = E_CALENDAR_ITEM (data); + + calitem->signal_emission_idle_id = 0; + + /* We ref the calitem & check in case it gets destroyed, since we + * were getting a free memory write here. */ + g_object_ref ((calitem)); + + if (calitem->date_range_changed) { + calitem->date_range_changed = FALSE; + g_signal_emit (calitem, e_calendar_item_signals[DATE_RANGE_CHANGED], 0); + } + + if (calitem->selection_changed) { + calitem->selection_changed = FALSE; + g_signal_emit (calitem, e_calendar_item_signals[SELECTION_CHANGED], 0); + } + + g_object_unref ((calitem)); + + return FALSE; +} + +/* Sets a callback to use to get the current time. This is useful if the + * application needs to use its own timezone data rather than rely on the + * Unix timezone. */ +void +e_calendar_item_set_get_time_callback (ECalendarItem *calitem, + ECalendarItemGetTimeCallback cb, + gpointer data, + GDestroyNotify destroy) +{ + g_return_if_fail (E_IS_CALENDAR_ITEM (calitem)); + + if (calitem->time_callback_data && calitem->time_callback_destroy) + (*calitem->time_callback_destroy) (calitem->time_callback_data); + + calitem->time_callback = cb; + calitem->time_callback_data = data; + calitem->time_callback_destroy = destroy; +} |