/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Author : * Damon Chaplin * * Copyright 1999, Helix Code, Inc. * Copyright 1999, Ximian, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * 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 General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ /* * EDayViewTimeItem - canvas item which displays the times down the left of * the EDayView. */ #include #include #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 static void e_day_view_time_item_class_init (EDayViewTimeItemClass *class); static void e_day_view_time_item_init (EDayViewTimeItem *dvtmitem); static void e_day_view_time_item_set_arg (GtkObject *o, GtkArg *arg, guint arg_id); static void e_day_view_time_item_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags); static void e_day_view_time_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height); static double e_day_view_time_item_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item); 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 mins_per_row); static void e_day_view_time_item_show_popup_menu (EDayViewTimeItem *dvtmitem, GdkEvent *event); static void e_day_view_time_item_on_set_divisions (GtkWidget *item, EDayViewTimeItem *dvtmitem); static void e_day_view_time_item_on_button_press (EDayViewTimeItem *dvtmitem, GdkEvent *event); static void e_day_view_time_item_on_button_release (EDayViewTimeItem *dvtmitem, GdkEvent *event); static void e_day_view_time_item_on_motion_notify (EDayViewTimeItem *dvtmitem, GdkEvent *event); static gint e_day_view_time_item_convert_position_to_row (EDayViewTimeItem *dvtmitem, gint y); static GnomeCanvasItemClass *parent_class; /* The arguments we take */ enum { ARG_0, ARG_DAY_VIEW }; GtkType e_day_view_time_item_get_type (void) { static GtkType e_day_view_time_item_type = 0; if (!e_day_view_time_item_type) { GtkTypeInfo e_day_view_time_item_info = { "EDayViewTimeItem", sizeof (EDayViewTimeItem), sizeof (EDayViewTimeItemClass), (GtkClassInitFunc) e_day_view_time_item_class_init, (GtkObjectInitFunc) e_day_view_time_item_init, NULL, /* reserved_1 */ NULL, /* reserved_2 */ (GtkClassInitFunc) NULL }; e_day_view_time_item_type = gtk_type_unique (gnome_canvas_item_get_type (), &e_day_view_time_item_info); } return e_day_view_time_item_type; } static void e_day_view_time_item_class_init (EDayViewTimeItemClass *class) { GtkObjectClass *object_class; GnomeCanvasItemClass *item_class; parent_class = gtk_type_class (gnome_canvas_item_get_type()); object_class = (GtkObjectClass *) class; item_class = (GnomeCanvasItemClass *) class; gtk_object_add_arg_type ("EDayViewTimeItem::day_view", GTK_TYPE_POINTER, GTK_ARG_WRITABLE, ARG_DAY_VIEW); object_class->set_arg = e_day_view_time_item_set_arg; /* GnomeCanvasItem method overrides */ 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; } static void e_day_view_time_item_init (EDayViewTimeItem *dvtmitem) { dvtmitem->dragging_selection = FALSE; } static void e_day_view_time_item_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) { GnomeCanvasItem *item; EDayViewTimeItem *dvtmitem; item = GNOME_CANVAS_ITEM (o); dvtmitem = E_DAY_VIEW_TIME_ITEM (o); switch (arg_id){ case ARG_DAY_VIEW: dvtmitem->day_view = GTK_VALUE_POINTER (*arg); break; } } static void e_day_view_time_item_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) { if (GNOME_CANVAS_ITEM_CLASS (parent_class)->update) (* GNOME_CANVAS_ITEM_CLASS (parent_class)->update) (item, affine, clip_path, flags); /* The item covers the entire canvas area. */ item->x1 = 0; item->y1 = 0; item->x2 = INT_MAX; item->y2 = INT_MAX; } /* 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 *dvtmitem) { EDayView *day_view; GtkStyle *style; GdkFont *small_font, *large_font; 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 = dvtmitem->day_view; g_return_val_if_fail (day_view != NULL, 0); style = GTK_WIDGET (day_view)->style; g_return_val_if_fail (style != NULL, 0); small_font = style->font; g_return_val_if_fail (small_font != NULL, 0); large_font = day_view->large_font; g_return_val_if_fail (large_font != NULL, 0); for (digit = '0'; digit <= '9'; digit++) { large_digit_width = gdk_char_width (large_font, digit); 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; dvtmitem->column_width = MAX (column_width_default, column_width_60_min_rows); return dvtmitem->column_width; } /* * DRAWING ROUTINES - functions to paint the canvas item. */ static void e_day_view_time_item_draw (GnomeCanvasItem *canvas_item, GdkDrawable *drawable, int x, int y, int width, int height) { EDayView *day_view; EDayViewTimeItem *dvtmitem; GtkStyle *style; GdkFont *small_font, *large_font; GdkGC *fg_gc, *dark_gc; gchar buffer[64], *suffix; 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; dvtmitem = E_DAY_VIEW_TIME_ITEM (canvas_item); day_view = dvtmitem->day_view; g_return_if_fail (day_view != NULL); style = GTK_WIDGET (day_view)->style; small_font = style->font; large_font = day_view->large_font; fg_gc = style->fg_gc[GTK_STATE_NORMAL]; dark_gc = style->dark_gc[GTK_STATE_NORMAL]; /* The start and end of the long horizontal line between hours. */ long_line_x1 = E_DVTMI_TIME_GRID_X_PAD - x; long_line_x2 = dvtmitem->column_width - E_DVTMI_TIME_GRID_X_PAD - x; if (day_view->mins_per_row == 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; /* The offset of the large hour string from the top of the row. */ large_hour_y_offset = large_font->ascent + E_DVTMI_LARGE_HOUR_Y_PAD; /* The offset of the small time/minute string from top of row. */ small_font_y_offset = small_font->ascent + 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, large_hour_y_offset + large_font->descent); /* 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) { /* 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, day_view->mins_per_row); 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 (day_view->mins_per_row == 60) { /* 60 minute intervals - draw a long horizontal line between hours and display as one long string, e.g. "14:00" or "2 pm". */ gdk_draw_line (drawable, dark_gc, long_line_x1, row_y, long_line_x2, row_y); if (day_view->use_24_hour_format) { g_snprintf (buffer, sizeof (buffer), "%i:%02i", display_hour, minute); } else { g_snprintf (buffer, sizeof (buffer), "%i %s", display_hour, suffix); } minute_width = gdk_string_width (small_font, buffer); gdk_draw_string (drawable, small_font, fg_gc, minute_x2 - minute_width, row_y + small_font_y_offset, buffer); } 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. */ gdk_draw_line (drawable, dark_gc, long_line_x1, row_y, long_line_x2, row_y); g_snprintf (buffer, sizeof (buffer), "%i", display_hour); hour_width = gdk_string_width (large_font, buffer); gdk_draw_string (drawable, large_font, fg_gc, large_hour_x2 - hour_width, row_y + large_hour_y_offset, buffer); } else { /* Within the hour - draw a short line before the time. */ gdk_draw_line (drawable, dark_gc, short_line_x1, row_y, long_line_x2, row_y); } /* Normally we display the minute in each interval, but when using 30-minute intervals we don't display the '30'. */ if (day_view->mins_per_row != 30 || minute != 30) { /* In 12-hour format we display 'am' or 'pm' instead of '00'. */ if (minute == 0 && !day_view->use_24_hour_format) { strcpy (buffer, suffix); } else { g_snprintf (buffer, sizeof (buffer), "%02i", minute); } minute_width = gdk_string_width (small_font, buffer); gdk_draw_string (drawable, small_font, fg_gc, minute_x2 - minute_width, row_y + small_font_y_offset, buffer); } } e_day_view_time_item_increment_time (&hour, &minute, day_view->mins_per_row); } } /* Increment the time by the 5/10/15/30/60 minute interval. Note that mins_per_row 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 mins_per_row) { *minute += mins_per_row; 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 double e_day_view_time_item_point (GnomeCanvasItem *item, double x, double y, int cx, int cy, GnomeCanvasItem **actual_item) { *actual_item = item; return 0.0; } static gint e_day_view_time_item_event (GnomeCanvasItem *item, GdkEvent *event) { EDayViewTimeItem *dvtmitem; dvtmitem = 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 (dvtmitem, event); } else if (event->button.button == 3) { e_day_view_time_item_show_popup_menu (dvtmitem, event); return TRUE; } break; case GDK_BUTTON_RELEASE: if (event->button.button == 1) e_day_view_time_item_on_button_release (dvtmitem, event); break; case GDK_MOTION_NOTIFY: e_day_view_time_item_on_motion_notify (dvtmitem, event); break; default: break; } return FALSE; } static void e_day_view_time_item_show_popup_menu (EDayViewTimeItem *dvtmitem, GdkEvent *event) { static gint divisions[] = { 60, 30, 15, 10, 5 }; EDayView *day_view; gint num_divisions = sizeof (divisions) / sizeof (divisions[0]); GtkWidget *menu, *item; gchar buffer[256]; GSList *group = NULL; gint current_divisions, i; day_view = dvtmitem->day_view; g_return_if_fail (day_view != NULL); current_divisions = e_day_view_get_mins_per_row (day_view); menu = gtk_menu_new (); /* Make sure the menu is destroyed when it disappears. */ e_auto_kill_popup_menu_on_hide (GTK_MENU (menu)); for (i = 0; i < num_divisions; i++) { g_snprintf (buffer, sizeof (buffer), _("%02i minute divisions"), divisions[i]); item = gtk_radio_menu_item_new_with_label (group, buffer); group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (item)); gtk_widget_show (item); gtk_menu_append (GTK_MENU (menu), item); if (current_divisions == divisions[i]) gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); gtk_object_set_data (GTK_OBJECT (item), "divisions", GINT_TO_POINTER (divisions[i])); gtk_signal_connect (GTK_OBJECT (item), "toggled", e_day_view_time_item_on_set_divisions, dvtmitem); } 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 *dvtmitem) { EDayView *day_view; gint divisions; day_view = dvtmitem->day_view; g_return_if_fail (day_view != NULL); if (!GTK_CHECK_MENU_ITEM (item)->active) return; divisions = GPOINTER_TO_INT (gtk_object_get_data (GTK_OBJECT (item), "divisions")); e_day_view_set_mins_per_row (day_view, divisions); calendar_config_set_time_divisions (divisions); } static void e_day_view_time_item_on_button_press (EDayViewTimeItem *dvtmitem, GdkEvent *event) { EDayView *day_view; GnomeCanvas *canvas; gint row; day_view = dvtmitem->day_view; g_return_if_fail (day_view != NULL); canvas = GNOME_CANVAS_ITEM (dvtmitem)->canvas; row = e_day_view_time_item_convert_position_to_row (dvtmitem, event->button.y); if (row == -1) return; if (!GTK_WIDGET_HAS_FOCUS (day_view)) gtk_widget_grab_focus (GTK_WIDGET (day_view)); if (gdk_pointer_grab (GTK_LAYOUT (canvas)->bin_window, FALSE, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, FALSE, NULL, event->button.time) == 0) { e_day_view_start_selection (day_view, -1, row); dvtmitem->dragging_selection = TRUE; } } static void e_day_view_time_item_on_button_release (EDayViewTimeItem *dvtmitem, GdkEvent *event) { EDayView *day_view; day_view = dvtmitem->day_view; g_return_if_fail (day_view != NULL); if (dvtmitem->dragging_selection) { gdk_pointer_ungrab (event->button.time); e_day_view_finish_selection (day_view); e_day_view_stop_auto_scroll (day_view); } dvtmitem->dragging_selection = FALSE; } static void e_day_view_time_item_on_motion_notify (EDayViewTimeItem *dvtmitem, GdkEvent *event) { EDayView *day_view; GnomeCanvas *canvas; gdouble window_y; gint y, row; if (!dvtmitem->dragging_selection) return; day_view = dvtmitem->day_view; g_return_if_fail (day_view != NULL); canvas = GNOME_CANVAS_ITEM (dvtmitem)->canvas; y = event->motion.y; row = e_day_view_time_item_convert_position_to_row (dvtmitem, 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 *dvtmitem, gint y) { EDayView *day_view; gint row; day_view = dvtmitem->day_view; 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; }