aboutsummaryrefslogtreecommitdiffstats
path: root/e-util/e-calendar-item.c
diff options
context:
space:
mode:
Diffstat (limited to 'e-util/e-calendar-item.c')
-rw-r--r--e-util/e-calendar-item.c3773
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, &current_end_year,
+ &current_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;
+}