/* * EDayViewTimeItem - canvas item which displays the times down the left of * the EDayView. * * 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. * * 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 General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, see . * * Authors: * Damon Chaplin * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include "e-day-view-time-item.h" #include "calendar-config.h" /* The spacing between items in the time column. GRID_X_PAD is the space down * either side of the column, i.e. outside the main horizontal grid lines. * HOUR_L_PAD & HOUR_R_PAD are the spaces on the left & right side of the * big hour number (this is inside the horizontal grid lines). * MIN_X_PAD is the spacing either side of the minute number. The smaller * horizontal grid lines match with this. * 60_MIN_X_PAD is the space either side of the HH:MM display used when * we are displaying 60 mins per row (inside the main grid lines). * LARGE_HOUR_Y_PAD is the offset of the large hour string from the top of the * row. * SMALL_FONT_Y_PAD is the offset of the small time/minute string from the top * of the row. */ #define E_DVTMI_TIME_GRID_X_PAD 4 #define E_DVTMI_HOUR_L_PAD 4 #define E_DVTMI_HOUR_R_PAD 2 #define E_DVTMI_MIN_X_PAD 2 #define E_DVTMI_60_MIN_X_PAD 4 #define E_DVTMI_LARGE_HOUR_Y_PAD 1 #define E_DVTMI_SMALL_FONT_Y_PAD 1 #define E_DAY_VIEW_TIME_ITEM_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_DAY_VIEW_TIME_ITEM, EDayViewTimeItemPrivate)) struct _EDayViewTimeItemPrivate { /* The parent EDayView widget. */ EDayView *day_view; /* The width of the time column. */ gint column_width; /* TRUE if we are currently dragging the selection times. */ gboolean dragging_selection; /* The second timezone if shown, or else NULL. */ icaltimezone *second_zone; }; static void e_day_view_time_item_update (GnomeCanvasItem *item, const cairo_matrix_t *i2c, gint flags); static void e_day_view_time_item_draw (GnomeCanvasItem *item, cairo_t *cr, gint x, gint y, gint width, gint height); static GnomeCanvasItem * e_day_view_time_item_point (GnomeCanvasItem *item, gdouble x, gdouble y, gint cx, gint cy); static gint e_day_view_time_item_event (GnomeCanvasItem *item, GdkEvent *event); static void e_day_view_time_item_increment_time (gint *hour, gint *minute, gint time_divisions); static void e_day_view_time_item_show_popup_menu (EDayViewTimeItem *time_item, GdkEvent *event); static void e_day_view_time_item_on_set_divisions (GtkWidget *item, EDayViewTimeItem *time_item); static void e_day_view_time_item_on_button_press (EDayViewTimeItem *time_item, GdkEvent *event); static void e_day_view_time_item_on_button_release (EDayViewTimeItem *time_item, GdkEvent *event); static void e_day_view_time_item_on_motion_notify (EDayViewTimeItem *time_item, GdkEvent *event); static gint e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *time_item, gint y); static void edvti_second_zone_changed_cb (GSettings *settings, const gchar *key, gpointer user_data); enum { PROP_0, PROP_DAY_VIEW }; G_DEFINE_TYPE ( EDayViewTimeItem, e_day_view_time_item, GNOME_TYPE_CANVAS_ITEM) static void day_view_time_item_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_DAY_VIEW: e_day_view_time_item_set_day_view ( E_DAY_VIEW_TIME_ITEM (object), g_value_get_object (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void day_view_time_item_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_DAY_VIEW: g_value_set_object ( value, e_day_view_time_item_get_day_view ( E_DAY_VIEW_TIME_ITEM (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void day_view_time_item_dispose (GObject *object) { EDayViewTimeItemPrivate *priv; priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (object); if (priv->day_view != NULL) { g_object_unref (priv->day_view); priv->day_view = NULL; } /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_day_view_time_item_parent_class)->dispose (object); } static void day_view_time_item_finalize (GObject *object) { EDayViewTimeItem *time_item; time_item = E_DAY_VIEW_TIME_ITEM (object); calendar_config_remove_notification ( (CalendarConfigChangedFunc) edvti_second_zone_changed_cb, time_item); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_day_view_time_item_parent_class)->finalize (object); } static void e_day_view_time_item_class_init (EDayViewTimeItemClass *class) { GObjectClass *object_class; GnomeCanvasItemClass *item_class; g_type_class_add_private (class, sizeof (EDayViewTimeItemPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = day_view_time_item_set_property; object_class->get_property = day_view_time_item_get_property; object_class->dispose = day_view_time_item_dispose; object_class->finalize = day_view_time_item_finalize; item_class = GNOME_CANVAS_ITEM_CLASS (class); item_class->update = e_day_view_time_item_update; item_class->draw = e_day_view_time_item_draw; item_class->point = e_day_view_time_item_point; item_class->event = e_day_view_time_item_event; g_object_class_install_property ( object_class, PROP_DAY_VIEW, g_param_spec_object ( "day-view", "Day View", NULL, E_TYPE_DAY_VIEW, G_PARAM_READWRITE)); } static void e_day_view_time_item_init (EDayViewTimeItem *time_item) { gchar *last; time_item->priv = E_DAY_VIEW_TIME_ITEM_GET_PRIVATE (time_item); last = calendar_config_get_day_second_zone (); if (last) { if (*last) time_item->priv->second_zone = icaltimezone_get_builtin_timezone (last); g_free (last); } calendar_config_add_notification_day_second_zone ( (CalendarConfigChangedFunc) edvti_second_zone_changed_cb, time_item); } static void e_day_view_time_item_update (GnomeCanvasItem *item, const cairo_matrix_t *i2c, gint flags) { if (GNOME_CANVAS_ITEM_CLASS (e_day_view_time_item_parent_class)->update) (* GNOME_CANVAS_ITEM_CLASS (e_day_view_time_item_parent_class)->update) (item, i2c, flags); /* The item covers the entire canvas area. */ item->x1 = 0; item->y1 = 0; item->x2 = INT_MAX; item->y2 = INT_MAX; } /* * DRAWING ROUTINES - functions to paint the canvas item. */ static void edvti_draw_zone (GnomeCanvasItem *canvas_item, cairo_t *cr, gint x, gint y, gint width, gint height, gint x_offset, icaltimezone *use_zone) { EDayView *day_view; EDayViewTimeItem *time_item; ECalendarView *cal_view; ECalModel *model; GtkStyle *style; const gchar *suffix; gchar buffer[64], *midnight_day = NULL, *midnight_month = NULL; gint time_divisions; gint hour, display_hour, minute, row; gint row_y, start_y, large_hour_y_offset, small_font_y_offset; gint long_line_x1, long_line_x2, short_line_x1; gint large_hour_x2, minute_x2; gint hour_width, minute_width, suffix_width; gint max_suffix_width, max_minute_or_suffix_width; PangoLayout *layout; PangoContext *context; PangoFontDescription *small_font_desc; PangoFontMetrics *large_font_metrics, *small_font_metrics; GdkColor fg, dark; GdkColor mb_color; time_item = E_DAY_VIEW_TIME_ITEM (canvas_item); day_view = e_day_view_time_item_get_day_view (time_item); g_return_if_fail (day_view != NULL); cal_view = E_CALENDAR_VIEW (day_view); model = e_calendar_view_get_model (cal_view); time_divisions = e_calendar_view_get_time_divisions (cal_view); style = gtk_widget_get_style (GTK_WIDGET (day_view)); small_font_desc = style->font_desc; context = gtk_widget_get_pango_context (GTK_WIDGET (day_view)); large_font_metrics = pango_context_get_metrics ( context, day_view->large_font_desc, pango_context_get_language (context)); small_font_metrics = pango_context_get_metrics ( context, small_font_desc, pango_context_get_language (context)); fg = style->fg[GTK_STATE_NORMAL]; dark = style->dark[GTK_STATE_NORMAL]; /* The start and end of the long horizontal line between hours. */ long_line_x1 = (use_zone ? 0 : E_DVTMI_TIME_GRID_X_PAD) - x + x_offset; long_line_x2 = time_item->priv->column_width - E_DVTMI_TIME_GRID_X_PAD - x - (use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0) + x_offset; if (time_divisions == 60) { /* The right edge of the complete time string in 60-min * divisions, e.g. "14:00" or "2 pm". */ minute_x2 = long_line_x2 - E_DVTMI_60_MIN_X_PAD; /* These aren't used for 60-minute divisions, but we initialize * them to keep gcc happy. */ short_line_x1 = 0; large_hour_x2 = 0; } else { max_suffix_width = MAX ( day_view->am_string_width, day_view->pm_string_width); max_minute_or_suffix_width = MAX ( max_suffix_width, day_view->max_minute_width); /* The start of the short horizontal line between the periods * within each hour. */ short_line_x1 = long_line_x2 - E_DVTMI_MIN_X_PAD * 2 - max_minute_or_suffix_width; /* The right edge of the large hour string. */ large_hour_x2 = short_line_x1 - E_DVTMI_HOUR_R_PAD; /* The right edge of the minute part of the time. */ minute_x2 = long_line_x2 - E_DVTMI_MIN_X_PAD; } /* Start with the first hour & minute shown in the EDayView. */ hour = day_view->first_hour_shown; minute = day_view->first_minute_shown; if (use_zone) { /* shift time with a difference between * local time and the other timezone */ icaltimezone *cal_zone; struct icaltimetype tt; gint diff; struct tm mn; cal_zone = e_calendar_view_get_timezone ( E_CALENDAR_VIEW (day_view)); tt = icaltime_from_timet_with_zone ( day_view->day_starts[0], 0, cal_zone); /* diff is number of minutes */ diff =(icaltimezone_get_utc_offset (use_zone, &tt, NULL) - icaltimezone_get_utc_offset (cal_zone, &tt, NULL)) / 60; tt = icaltime_from_timet_with_zone (day_view->day_starts[0], 0, cal_zone); tt.is_date = FALSE; icaltime_set_timezone (&tt, cal_zone); tt = icaltime_convert_to_zone (tt, use_zone); if (diff != 0) { /* shows the next midnight */ icaltime_adjust (&tt, 1, 0, 0, 0); } mn = icaltimetype_to_tm (&tt); /* up to two characters/numbers */ e_utf8_strftime (buffer, sizeof (buffer), "%d", &mn); midnight_day = g_strdup (buffer); /* up to three characters, abbreviated month name */ e_utf8_strftime (buffer, sizeof (buffer), "%b", &mn); midnight_month = g_strdup (buffer); minute += (diff % 60); hour += (diff / 60) + (minute / 60); minute = minute % 60; if (minute < 0) { hour--; minute += 60; } hour = (hour + 48) % 24; } /* The offset of the large hour string from the top of the row. */ large_hour_y_offset = E_DVTMI_LARGE_HOUR_Y_PAD; /* The offset of the small time/minute string from top of row. */ small_font_y_offset = E_DVTMI_SMALL_FONT_Y_PAD; /* Calculate the minimum y position of the first row we need to draw. * This is normally one row height above the 0 position, but if we * are using the large font we may have to go back a bit further. */ start_y = 0 - MAX (day_view->row_height, (pango_font_metrics_get_ascent (large_font_metrics) + pango_font_metrics_get_descent (large_font_metrics)) / PANGO_SCALE + E_DVTMI_LARGE_HOUR_Y_PAD); /* Draw the Marcus Bains Line first, so it appears under other elements. */ if (e_day_view_marcus_bains_get_show_line (day_view)) { struct icaltimetype time_now; const gchar *marcus_bains_time_bar_color; gint marcus_bains_y; cairo_save (cr); gdk_cairo_set_source_color ( cr, &day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE]); marcus_bains_time_bar_color = e_day_view_marcus_bains_get_time_bar_color (day_view); if (marcus_bains_time_bar_color == NULL) marcus_bains_time_bar_color = ""; if (gdk_color_parse (marcus_bains_time_bar_color, &mb_color)) { gdk_cairo_set_source_color (cr, &mb_color); } else { mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE]; } time_now = icaltime_current_time_with_zone ( e_calendar_view_get_timezone ( E_CALENDAR_VIEW (day_view))); marcus_bains_y = (time_now.hour * 60 + time_now.minute) * day_view->row_height / time_divisions - y; cairo_set_line_width (cr, 1.5); cairo_move_to ( cr, long_line_x1 - (use_zone ? E_DVTMI_TIME_GRID_X_PAD : 0), marcus_bains_y); cairo_line_to (cr, long_line_x2, marcus_bains_y); cairo_stroke (cr); cairo_restore (cr); } else { const gchar *marcus_bains_time_bar_color; marcus_bains_time_bar_color = e_day_view_marcus_bains_get_time_bar_color (day_view); if (marcus_bains_time_bar_color == NULL) marcus_bains_time_bar_color = ""; mb_color = day_view->colors[E_DAY_VIEW_COLOR_MARCUS_BAINS_LINE]; if (gdk_color_parse (marcus_bains_time_bar_color, &mb_color)) gdk_cairo_set_source_color (cr, &mb_color); } /* Step through each row, drawing the times and the horizontal lines * between them. */ for (row = 0, row_y = 0 - y; row < day_view->rows && row_y < height; row++, row_y += day_view->row_height) { gboolean show_midnight_date; show_midnight_date = use_zone && hour == 0 && (minute == 0 || time_divisions == 60) && midnight_day && midnight_month; /* If the row is above the first row we want to draw just * increment the time and skip to the next row. */ if (row_y < start_y) { e_day_view_time_item_increment_time ( &hour, &minute, time_divisions); continue; } /* Calculate the actual hour number to display. For 12-hour * format we convert 0-23 to 12-11am / 12 - 11pm. */ e_day_view_convert_time_to_display ( day_view, hour, &display_hour, &suffix, &suffix_width); if (time_divisions == 60) { /* 60 minute intervals - draw a long horizontal line * between hours and display as one long string, * e.g. "14:00" or "2 pm". */ cairo_save (cr); gdk_cairo_set_source_color (cr, &dark); cairo_save (cr); cairo_set_line_width (cr, 0.7); cairo_move_to (cr, long_line_x1, row_y); cairo_line_to (cr, long_line_x2, row_y); cairo_stroke (cr); cairo_restore (cr); if (show_midnight_date) { g_snprintf (buffer, sizeof (buffer), "%s %s", midnight_day, midnight_month); } else if (e_cal_model_get_use_24_hour_format (model)) { g_snprintf ( buffer, sizeof (buffer), "%i:%02i", display_hour, minute); } else { g_snprintf ( buffer, sizeof (buffer), "%i %s", display_hour, suffix); } cairo_save (cr); if (show_midnight_date) gdk_cairo_set_source_color (cr, &mb_color); else gdk_cairo_set_source_color (cr, &fg); layout = pango_cairo_create_layout (cr); pango_layout_set_text (layout, buffer, -1); pango_layout_get_pixel_size (layout, &minute_width, NULL); cairo_translate ( cr, minute_x2 - minute_width, row_y + small_font_y_offset); pango_cairo_update_layout (cr, layout); pango_cairo_show_layout (cr, layout); cairo_restore (cr); g_object_unref (layout); } else { /* 5/10/15/30 minute intervals. */ if (minute == 0) { /* On the hour - draw a long horizontal line * before the hour and display the hour in the * large font. */ cairo_save (cr); gdk_cairo_set_source_color (cr, &dark); if (show_midnight_date) g_snprintf (buffer, sizeof (buffer), "%s", midnight_day); else g_snprintf ( buffer, sizeof (buffer), "%i", display_hour); cairo_set_line_width (cr, 0.7); cairo_move_to (cr, long_line_x1, row_y); cairo_line_to (cr, long_line_x2, row_y); cairo_stroke (cr); cairo_restore (cr); cairo_save (cr); if (show_midnight_date) gdk_cairo_set_source_color (cr, &mb_color); else gdk_cairo_set_source_color (cr, &fg); layout = pango_cairo_create_layout (cr); pango_layout_set_text (layout, buffer, -1); pango_layout_set_font_description ( layout, day_view->large_font_desc); pango_layout_get_pixel_size ( layout, &hour_width, NULL); cairo_translate ( cr, large_hour_x2 - hour_width, row_y + large_hour_y_offset); pango_cairo_update_layout (cr, layout); pango_cairo_show_layout (cr, layout); cairo_restore (cr); g_object_unref (layout); } else { /* Within the hour - draw a short line before * the time. */ cairo_save (cr); gdk_cairo_set_source_color (cr, &dark); cairo_set_line_width (cr, 0.7); cairo_move_to (cr, short_line_x1, row_y); cairo_line_to (cr, long_line_x2, row_y); cairo_stroke (cr); cairo_restore (cr); } /* Normally we display the minute in each * interval, but when using 30-minute intervals * we don't display the '30'. */ if (time_divisions != 30 || minute != 30) { /* In 12-hour format we display 'am' or 'pm' * instead of '00'. */ if (show_midnight_date) g_snprintf (buffer, sizeof (buffer), "%s", midnight_month); else if (minute == 0 && !e_cal_model_get_use_24_hour_format (model)) { g_snprintf (buffer, sizeof (buffer), "%s", suffix); } else { g_snprintf ( buffer, sizeof (buffer), "%02i", minute); } cairo_save (cr); if (show_midnight_date) gdk_cairo_set_source_color (cr, &mb_color); else gdk_cairo_set_source_color (cr, &fg); layout = pango_cairo_create_layout (cr); pango_layout_set_text (layout, buffer, -1); pango_layout_set_font_description ( layout, day_view->small_font_desc); pango_layout_get_pixel_size ( layout, &minute_width, NULL); cairo_translate ( cr, minute_x2 - minute_width, row_y + small_font_y_offset); pango_cairo_update_layout (cr, layout); pango_cairo_show_layout (cr, layout); cairo_restore (cr); g_object_unref (layout); } } e_day_view_time_item_increment_time ( &hour, &minute, time_divisions); } pango_font_metrics_unref (large_font_metrics); pango_font_metrics_unref (small_font_metrics); g_free (midnight_day); g_free (midnight_month); } static void e_day_view_time_item_draw (GnomeCanvasItem *canvas_item, cairo_t *cr, gint x, gint y, gint width, gint height) { EDayViewTimeItem *time_item; time_item = E_DAY_VIEW_TIME_ITEM (canvas_item); g_return_if_fail (time_item != NULL); edvti_draw_zone (canvas_item, cr, x, y, width, height, 0, NULL); if (time_item->priv->second_zone) edvti_draw_zone ( canvas_item, cr, x, y, width, height, time_item->priv->column_width, time_item->priv->second_zone); } /* Increment the time by the 5/10/15/30/60 minute interval. * Note that time_divisions is never > 60, so we never have to * worry about adding more than 60 minutes. */ static void e_day_view_time_item_increment_time (gint *hour, gint *minute, gint time_divisions) { *minute += time_divisions; if (*minute >= 60) { *minute -= 60; /* Currently we never wrap around to the next day, but * we may do if we display extra timezones. */ *hour = (*hour + 1) % 24; } } static GnomeCanvasItem * e_day_view_time_item_point (GnomeCanvasItem *item, gdouble x, gdouble y, gint cx, gint cy) { return item; } static gint e_day_view_time_item_event (GnomeCanvasItem *item, GdkEvent *event) { EDayViewTimeItem *time_item; time_item = E_DAY_VIEW_TIME_ITEM (item); switch (event->type) { case GDK_BUTTON_PRESS: if (event->button.button == 1) { e_day_view_time_item_on_button_press (time_item, event); } else if (event->button.button == 3) { e_day_view_time_item_show_popup_menu (time_item, event); return TRUE; } break; case GDK_BUTTON_RELEASE: if (event->button.button == 1) e_day_view_time_item_on_button_release ( time_item, event); break; case GDK_MOTION_NOTIFY: e_day_view_time_item_on_motion_notify (time_item, event); break; default: break; } return FALSE; } static void edvti_second_zone_changed_cb (GSettings *settings, const gchar *key, gpointer user_data) { EDayViewTimeItem *time_item = user_data; EDayView *day_view; icaltimezone *second_zone; gchar *location; g_return_if_fail (user_data != NULL); g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item)); location = calendar_config_get_day_second_zone (); second_zone = location ? icaltimezone_get_builtin_timezone (location) : NULL; g_free (location); if (second_zone == time_item->priv->second_zone) return; time_item->priv->second_zone = second_zone; day_view = e_day_view_time_item_get_day_view (time_item); gtk_widget_set_size_request ( day_view->time_canvas, e_day_view_time_item_get_column_width (time_item), -1); gtk_widget_queue_draw (day_view->time_canvas); } static void edvti_on_select_zone (GtkWidget *item, EDayViewTimeItem *time_item) { calendar_config_select_day_second_zone (); } static void edvti_on_set_zone (GtkWidget *item, EDayViewTimeItem *time_item) { if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item))) return; calendar_config_set_day_second_zone ( g_object_get_data (G_OBJECT (item), "timezone")); } static void e_day_view_time_item_show_popup_menu (EDayViewTimeItem *time_item, GdkEvent *event) { static gint divisions[] = { 60, 30, 15, 10, 5 }; EDayView *day_view; ECalendarView *cal_view; GtkWidget *menu, *item, *submenu; gchar buffer[256]; GSList *group = NULL, *recent_zones, *s; gint current_divisions, i; icaltimezone *zone; day_view = e_day_view_time_item_get_day_view (time_item); g_return_if_fail (day_view != NULL); cal_view = E_CALENDAR_VIEW (day_view); current_divisions = e_calendar_view_get_time_divisions (cal_view); menu = gtk_menu_new (); /* Make sure the menu is destroyed when it disappears. */ g_signal_connect ( menu, "selection-done", G_CALLBACK (gtk_widget_destroy), NULL); for (i = 0; i < G_N_ELEMENTS (divisions); i++) { g_snprintf ( buffer, sizeof (buffer), /* Translators: %02i is the number of minutes; * this is a context menu entry to change the * length of the time division in the calendar * day view, e.g. a day is displayed in * 24 "60 minute divisions" or * 48 "30 minute divisions". */ _("%02i minute divisions"), divisions[i]); item = gtk_radio_menu_item_new_with_label (group, buffer); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); if (current_divisions == divisions[i]) gtk_check_menu_item_set_active ( GTK_CHECK_MENU_ITEM (item), TRUE); g_object_set_data ( G_OBJECT (item), "divisions", GINT_TO_POINTER (divisions[i])); g_signal_connect ( item, "toggled", G_CALLBACK (e_day_view_time_item_on_set_divisions), time_item); } item = gtk_separator_menu_item_new (); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); submenu = gtk_menu_new (); item = gtk_menu_item_new_with_label (_("Show the second time zone")); gtk_widget_show (item); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); zone = e_calendar_view_get_timezone (E_CALENDAR_VIEW (day_view)); if (zone) item = gtk_menu_item_new_with_label (icaltimezone_get_display_name (zone)); else item = gtk_menu_item_new_with_label ("---"); gtk_widget_set_sensitive (item, FALSE); gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); item = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); group = NULL; item = gtk_radio_menu_item_new_with_label (group, C_("cal-second-zone", "None")); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); if (!time_item->priv->second_zone) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); g_signal_connect ( item, "toggled", G_CALLBACK (edvti_on_set_zone), time_item); recent_zones = calendar_config_get_day_second_zones (); for (s = recent_zones; s != NULL; s = s->next) { zone = icaltimezone_get_builtin_timezone (s->data); if (!zone) continue; item = gtk_radio_menu_item_new_with_label ( group, icaltimezone_get_display_name (zone)); group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); /* both comes from builtin, thus no problem to compare pointers */ if (zone == time_item->priv->second_zone) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); g_object_set_data_full ( G_OBJECT (item), "timezone", g_strdup (s->data), g_free); g_signal_connect ( item, "toggled", G_CALLBACK (edvti_on_set_zone), time_item); } calendar_config_free_day_second_zones (recent_zones); item = gtk_separator_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); item = gtk_menu_item_new_with_label (_("Select...")); g_signal_connect ( item, "activate", G_CALLBACK (edvti_on_select_zone), time_item); gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); gtk_widget_show_all (submenu); gtk_menu_popup ( GTK_MENU (menu), NULL, NULL, NULL, NULL, event->button.button, event->button.time); } static void e_day_view_time_item_on_set_divisions (GtkWidget *item, EDayViewTimeItem *time_item) { EDayView *day_view; ECalendarView *cal_view; gint divisions; day_view = e_day_view_time_item_get_day_view (time_item); g_return_if_fail (day_view != NULL); if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (item))) return; divisions = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "divisions")); cal_view = E_CALENDAR_VIEW (day_view); e_calendar_view_set_time_divisions (cal_view, divisions); } static void e_day_view_time_item_on_button_press (EDayViewTimeItem *time_item, GdkEvent *event) { GdkWindow *window; EDayView *day_view; GnomeCanvas *canvas; GdkGrabStatus grab_status; GdkDevice *event_device; guint32 event_time; gint row; day_view = e_day_view_time_item_get_day_view (time_item); g_return_if_fail (day_view != NULL); canvas = GNOME_CANVAS_ITEM (time_item)->canvas; row = e_day_view_time_item_convert_position_to_row ( time_item, event->button.y); if (row == -1) return; if (!gtk_widget_has_focus (GTK_WIDGET (day_view))) gtk_widget_grab_focus (GTK_WIDGET (day_view)); window = gtk_layout_get_bin_window (GTK_LAYOUT (canvas)); event_device = gdk_event_get_device (event); event_time = gdk_event_get_time (event); grab_status = gdk_device_grab ( event_device, window, GDK_OWNERSHIP_NONE, FALSE, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, event_time); if (grab_status == GDK_GRAB_SUCCESS) { e_day_view_start_selection (day_view, -1, row); time_item->priv->dragging_selection = TRUE; } } static void e_day_view_time_item_on_button_release (EDayViewTimeItem *time_item, GdkEvent *event) { EDayView *day_view; day_view = e_day_view_time_item_get_day_view (time_item); g_return_if_fail (day_view != NULL); if (time_item->priv->dragging_selection) { GdkDevice *event_device; guint32 event_time; event_device = gdk_event_get_device (event); event_time = gdk_event_get_time (event); gdk_device_ungrab (event_device, event_time); e_day_view_finish_selection (day_view); e_day_view_stop_auto_scroll (day_view); } time_item->priv->dragging_selection = FALSE; } static void e_day_view_time_item_on_motion_notify (EDayViewTimeItem *time_item, GdkEvent *event) { EDayView *day_view; GnomeCanvas *canvas; gdouble window_y; gint y, row; if (!time_item->priv->dragging_selection) return; day_view = e_day_view_time_item_get_day_view (time_item); g_return_if_fail (day_view != NULL); canvas = GNOME_CANVAS_ITEM (time_item)->canvas; y = event->motion.y; row = e_day_view_time_item_convert_position_to_row (time_item, y); if (row != -1) { gnome_canvas_world_to_window ( canvas, 0, event->motion.y, NULL, &window_y); e_day_view_update_selection (day_view, -1, row); e_day_view_check_auto_scroll (day_view, -1, (gint) window_y); } } /* Returns the row corresponding to the y position, or -1. */ static gint e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *time_item, gint y) { EDayView *day_view; gint row; day_view = e_day_view_time_item_get_day_view (time_item); g_return_val_if_fail (day_view != NULL, -1); if (y < 0) return -1; row = y / day_view->row_height; if (row >= day_view->rows) return -1; return row; } EDayView * e_day_view_time_item_get_day_view (EDayViewTimeItem *time_item) { g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL); return time_item->priv->day_view; } void e_day_view_time_item_set_day_view (EDayViewTimeItem *time_item, EDayView *day_view) { g_return_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item)); g_return_if_fail (E_IS_DAY_VIEW (day_view)); if (time_item->priv->day_view == day_view) return; if (time_item->priv->day_view != NULL) g_object_unref (time_item->priv->day_view); time_item->priv->day_view = g_object_ref (day_view); g_object_notify (G_OBJECT (time_item), "day-view"); } /* Returns the minimum width needed for the column, by adding up all the * maximum widths of the strings. The string widths are all calculated in * the style_set handlers of EDayView and EDayViewTimeCanvas. */ gint e_day_view_time_item_get_column_width (EDayViewTimeItem *time_item) { EDayView *day_view; GtkStyle *style; gint digit, large_digit_width, max_large_digit_width = 0; gint max_suffix_width, max_minute_or_suffix_width; gint column_width_default, column_width_60_min_rows; day_view = e_day_view_time_item_get_day_view (time_item); g_return_val_if_fail (day_view != NULL, 0); style = gtk_widget_get_style (GTK_WIDGET (day_view)); g_return_val_if_fail (style != NULL, 0); /* Find the maximum width a digit can have. FIXME: We could use pango's * approximation function, but I worry it won't be precise enough. Also * it needs a language tag that I don't know where to get. */ for (digit = '0'; digit <= '9'; digit++) { PangoLayout *layout; gchar digit_str[2]; digit_str[0] = digit; digit_str[1] = '\0'; layout = gtk_widget_create_pango_layout (GTK_WIDGET (day_view), digit_str); pango_layout_set_font_description (layout, day_view->large_font_desc); pango_layout_get_pixel_size (layout, &large_digit_width, NULL); g_object_unref (layout); max_large_digit_width = MAX ( max_large_digit_width, large_digit_width); } /* Calculate the width of each time column, using the maximum of the * default format with large hour numbers, and the 60-min divisions * format which uses small text. */ max_suffix_width = MAX ( day_view->am_string_width, day_view->pm_string_width); max_minute_or_suffix_width = MAX ( max_suffix_width, day_view->max_minute_width); column_width_default = max_large_digit_width * 2 + max_minute_or_suffix_width + E_DVTMI_MIN_X_PAD * 2 + E_DVTMI_HOUR_L_PAD + E_DVTMI_HOUR_R_PAD + E_DVTMI_TIME_GRID_X_PAD * 2; column_width_60_min_rows = day_view->max_small_hour_width + day_view->colon_width + max_minute_or_suffix_width + E_DVTMI_60_MIN_X_PAD * 2 + E_DVTMI_TIME_GRID_X_PAD * 2; time_item->priv->column_width = MAX (column_width_default, column_width_60_min_rows); if (time_item->priv->second_zone) return (2 * time_item->priv->column_width) - E_DVTMI_TIME_GRID_X_PAD; return time_item->priv->column_width; } icaltimezone * e_day_view_time_item_get_second_zone (EDayViewTimeItem *time_item) { g_return_val_if_fail (E_IS_DAY_VIEW_TIME_ITEM (time_item), NULL); return time_item->priv->second_zone; }