/* * EMeetingTimeSelectorItem - A GnomeCanvasItem which is used for both the main * display canvas and the top display (with the dates, times & All Attendees). * I didn't make these separate GnomeCanvasItems since they share a lot of * code. * * 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 * * Authors: * Damon Chaplin * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) */ #ifdef HAVE_CONFIG_H #include #endif #include #include #include "calendar-config.h" #include "e-meeting-time-sel-item.h" #include "e-meeting-time-sel.h" /* Initially the grid lines were drawn at the bottom of cells, but this didn't * line up well with the GtkEntry widgets, which in the default theme draw a * black shadow line across the top. So I've switched our code to draw the * lines across the top of cells. */ #define E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM 0 static void e_meeting_time_selector_item_dispose (GObject *object); static void e_meeting_time_selector_item_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void e_meeting_time_selector_item_update (GnomeCanvasItem *item, const cairo_matrix_t *i2c, gint flags); static void e_meeting_time_selector_item_draw (GnomeCanvasItem *item, cairo_t *cr, gint x, gint y, gint width, gint height); static GnomeCanvasItem *e_meeting_time_selector_item_point (GnomeCanvasItem *item, double x, double y, gint cx, gint cy); static gint e_meeting_time_selector_item_event (GnomeCanvasItem *item, GdkEvent *event); static gint e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item, GdkEvent *event); static gint e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item, GdkEvent *event); static gint e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item, GdkEvent *event); static void e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height); static void e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint y, gint width, gint height); static void e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height); static void e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height); static gint e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item, GDate *date, gint row); static void e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, gint row, gint x, gint y, gint width, gint first_period, EMeetingFreeBusyType busy_type); static EMeetingTimeSelectorPosition e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, gint x, gint y); static gboolean e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts, gint row, gint x, gint width, gint *start_x, gint *end_x); enum { PROP_0, PROP_MEETING_TIME_SELECTOR }; G_DEFINE_TYPE (EMeetingTimeSelectorItem, e_meeting_time_selector_item, GNOME_TYPE_CANVAS_ITEM) static void e_meeting_time_selector_item_class_init (EMeetingTimeSelectorItemClass *class) { GObjectClass *object_class; GnomeCanvasItemClass *item_class; object_class = G_OBJECT_CLASS (class); object_class->dispose = e_meeting_time_selector_item_dispose; object_class->set_property = e_meeting_time_selector_item_set_property; item_class = GNOME_CANVAS_ITEM_CLASS (class); item_class->update = e_meeting_time_selector_item_update; item_class->draw = e_meeting_time_selector_item_draw; item_class->point = e_meeting_time_selector_item_point; item_class->event = e_meeting_time_selector_item_event; g_object_class_install_property ( object_class, PROP_MEETING_TIME_SELECTOR, g_param_spec_pointer ( "meeting_time_selector", NULL, NULL, G_PARAM_WRITABLE)); } static void e_meeting_time_selector_item_init (EMeetingTimeSelectorItem *mts_item) { GnomeCanvasItem *item = GNOME_CANVAS_ITEM (mts_item); mts_item->mts = NULL; /* Create the cursors. */ mts_item->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR); mts_item->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); mts_item->busy_cursor = gdk_cursor_new (GDK_WATCH); mts_item->last_cursor_set = NULL; item->x1 = 0; item->y1 = 0; item->x2 = 0; item->y2 = 0; } static void e_meeting_time_selector_item_dispose (GObject *object) { EMeetingTimeSelectorItem *mts_item; mts_item = E_MEETING_TIME_SELECTOR_ITEM (object); if (mts_item->normal_cursor) { g_object_unref (mts_item->normal_cursor); mts_item->normal_cursor = NULL; } if (mts_item->resize_cursor) { g_object_unref (mts_item->resize_cursor); mts_item->resize_cursor = NULL; } if (mts_item->busy_cursor) { g_object_unref (mts_item->busy_cursor); mts_item->busy_cursor = NULL; } G_OBJECT_CLASS (e_meeting_time_selector_item_parent_class)->dispose (object); } static void e_meeting_time_selector_item_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { EMeetingTimeSelectorItem *mts_item; mts_item = E_MEETING_TIME_SELECTOR_ITEM (object); switch (property_id) { case PROP_MEETING_TIME_SELECTOR: mts_item->mts = g_value_get_pointer (value); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void e_meeting_time_selector_item_update (GnomeCanvasItem *item, const cairo_matrix_t *i2c, gint flags) { if (GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_item_parent_class)->update) (* GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_item_parent_class)->update) (item, i2c, flags); /* The grid 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 draw_strikeout_box (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, gint x, gint y, gint width, gint height) { GnomeCanvas *canvas = GNOME_CANVAS_ITEM (mts_item)->canvas; EMeetingTimeSelector *mts = mts_item->mts; cairo_save (cr); cairo_rectangle (cr, x, y, width, height); cairo_clip (cr); cairo_translate (cr, -canvas->draw_xofs, -canvas->draw_yofs); cairo_set_source (cr, mts->no_info_pattern); cairo_paint (cr); cairo_restore (cr); } static void e_meeting_time_selector_item_draw (GnomeCanvasItem *item, cairo_t *cr, gint x, gint y, gint width, gint height) { EMeetingTimeSelector *mts; EMeetingTimeSelectorItem *mts_item; EMeetingAttendee *ia; gint day_x, meeting_start_x, meeting_end_x, bar_y, bar_height; gint row, row_y, start_x, end_x; GDate date, last_date, current_date; gboolean is_display_top, show_meeting_time; mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); mts = mts_item->mts; g_return_if_fail (mts != NULL); is_display_top = (GTK_WIDGET (item->canvas) == mts->display_top) ? TRUE : FALSE; /* Calculate the first and last visible days and positions. */ e_meeting_time_selector_calculate_day_and_position ( mts, x, &date, &day_x); e_meeting_time_selector_calculate_day_and_position ( mts, x + width, &last_date, NULL); /* For the top display draw the 'All Attendees' row background. */ cairo_save (cr); if (is_display_top) { gdk_cairo_set_source_color (cr, &mts->all_attendees_bg_color); cairo_rectangle ( cr, 0, mts->row_height * 2 - y, width, mts->row_height); cairo_fill (cr); } else { gdk_cairo_set_source_color (cr, &mts->bg_color); cairo_rectangle (cr, 0, 0, width, height); cairo_fill (cr); } cairo_restore (cr); /* Calculate the x coordinates of the meeting time. */ show_meeting_time = e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x); /* Draw the meeting time background. */ if (show_meeting_time && (meeting_end_x - 1 >= x) && (meeting_start_x + 1 < x + width) && (meeting_end_x - meeting_start_x > 2)) { cairo_save (cr); gdk_cairo_set_source_color (cr, &mts->meeting_time_bg_color); if (is_display_top) { cairo_rectangle ( cr, meeting_start_x + 1 - x, mts->row_height * 2 - y, meeting_end_x - meeting_start_x - 2, mts->row_height); cairo_fill (cr); } else { cairo_rectangle ( cr, meeting_start_x + 1 - x, 0, meeting_end_x - meeting_start_x - 2, height); cairo_fill (cr); } cairo_restore (cr); } /* For the main display draw the no-info pattern background for attendee's * that have no calendar information. */ if (!is_display_top) { gdk_cairo_set_source_color (cr, &mts->grid_color); row = y / mts->row_height; row_y = row * mts->row_height - y; while (row < e_meeting_store_count_actual_attendees (mts->model) && row_y < height) { ia = e_meeting_store_find_attendee_at_row (mts->model, row); if (e_meeting_attendee_get_has_calendar_info (ia)) { if (e_meeting_time_selector_item_calculate_busy_range (mts, row, x, width, &start_x, &end_x)) { if (start_x >= width || end_x <= 0) { draw_strikeout_box (mts_item, cr, 0, row_y, width, mts->row_height); } else { if (start_x >= 0) { draw_strikeout_box (mts_item, cr, 0, row_y, start_x, mts->row_height); cairo_move_to (cr, start_x, row_y); cairo_line_to (cr, start_x, row_y + mts->row_height); cairo_stroke (cr); } if (end_x <= width) { draw_strikeout_box (mts_item, cr, end_x, row_y, width - end_x, mts->row_height); cairo_move_to (cr, end_x, row_y); cairo_line_to (cr, end_x, row_y + mts->row_height); cairo_stroke (cr); } } } } else { draw_strikeout_box (mts_item, cr, 0, row_y, width, mts->row_height); } row++; row_y += mts->row_height; } } /* Now paint the visible days one by one. */ current_date = date; for (;;) { /* Currently we use the same GnomeCanvasItem class for the * top display and the main display. We may use separate * classes in future if necessary. */ if (is_display_top) e_meeting_time_selector_item_paint_day_top (mts_item, cr, ¤t_date, day_x, y, width, height); else e_meeting_time_selector_item_paint_day (mts_item, cr, ¤t_date, day_x, y, width, height); day_x += mts_item->mts->day_width; if (g_date_compare (¤t_date, &last_date) == 0) break; g_date_add_days (¤t_date, 1); } /* Draw the busy periods. */ if (is_display_top) e_meeting_time_selector_item_paint_all_attendees_busy_periods (mts_item, cr, &date, x, y, width, height); else e_meeting_time_selector_item_paint_busy_periods (mts_item, cr, &date, x, y, width, height); /* Draw the currently-selected meeting time vertical bars. */ if (show_meeting_time) { if (is_display_top) { bar_y = mts->row_height * 2 - y; bar_height = mts->row_height; } else { bar_y = 0; bar_height = height; } cairo_save (cr); gdk_cairo_set_source_color (cr, &mts->grid_color); if ((meeting_start_x + 2 >= x) && (meeting_start_x - 2 < x + width)) { cairo_rectangle ( cr, meeting_start_x - 2 - x, bar_y, 5, bar_height); cairo_fill (cr); } if ((meeting_end_x + 2 >= x) && (meeting_end_x - 2 < x + width)) { cairo_rectangle ( cr, meeting_end_x - 2 - x, bar_y, 5, bar_height); cairo_fill (cr); } cairo_restore (cr); } } static void e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height) { EMeetingTimeSelector *mts; gint y, grid_x; gchar *str; gint hour, hour_x, hour_y; PangoLayout *layout; struct tm tm_time; cairo_save (cr); cairo_set_line_width (cr, 1.0); mts = mts_item->mts; layout = gtk_widget_create_pango_layout (GTK_WIDGET (mts), NULL); /* Draw the horizontal lines. */ y = mts->row_height - 1 - scroll_y; gdk_cairo_set_source_color (cr, &mts->grid_color); cairo_move_to (cr, x + 0.5, y + 0.5); cairo_rel_line_to (cr, mts->day_width - 1, 0); cairo_stroke (cr); gdk_cairo_set_source_color (cr, &mts->grid_shadow_color); cairo_move_to (cr, x + 0.5, y + 1.5); cairo_rel_line_to (cr, mts->day_width - 1, 0); cairo_stroke (cr); gdk_cairo_set_source_color (cr, &mts->grid_color); y += mts->row_height; cairo_move_to (cr, x + 0.5, y + 0.5); cairo_rel_line_to (cr, mts->day_width - 1, 0); y += mts->row_height; cairo_move_to (cr, x + 0.5, y + 0.5); cairo_rel_line_to (cr, mts->day_width - 1, 0); /* Draw the vertical grid lines. */ for (grid_x = mts->col_width - 1; grid_x < mts->day_width - mts->col_width; grid_x += mts->col_width) { cairo_move_to (cr, x + grid_x + 0.5, mts->row_height * 2 - 1 - scroll_y + 0.5); cairo_line_to (cr, x + grid_x + 0.5, height + 0.5); } grid_x = mts->day_width - 2; cairo_move_to (cr, x + grid_x + 0.5, 0.5); cairo_rel_line_to (cr, 0, height); grid_x++; cairo_move_to (cr, x + grid_x + 0.5, 0.5); cairo_rel_line_to (cr, 0, height); cairo_stroke (cr); g_date_to_struct_tm (date, &tm_time); str = e_datetime_format_format_tm ("calendar", "table", DTFormatKindDate, &tm_time); g_return_if_fail (str != NULL); /* Draw the date. Set a clipping rectangle so we don't draw over the * next day. */ if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY && !e_datetime_format_includes_day_name ("calendar", "table", DTFormatKindDate)) { gchar buffer[128]; gchar *tmp; g_date_strftime (buffer, sizeof (buffer), "%a", date); tmp = str; str = g_strconcat (buffer, " ", str, NULL); g_free (tmp); } cairo_save (cr); cairo_rectangle (cr, x, -scroll_y, mts->day_width - 2, mts->row_height - 2); cairo_clip (cr); pango_layout_set_text (layout, str, -1); cairo_move_to (cr, x + 2, 4 - scroll_y); pango_cairo_show_layout (cr, layout); cairo_restore (cr); /* Draw the hours. */ hour = mts->first_hour_shown; hour_x = x + 2; hour_y = mts->row_height + 4 - scroll_y; while (hour < mts->last_hour_shown) { if (e_meeting_time_selector_get_use_24_hour_format (mts)) pango_layout_set_text (layout, EMeetingTimeSelectorHours[hour], -1); else pango_layout_set_text (layout, EMeetingTimeSelectorHours12[hour], -1); cairo_move_to (cr, hour_x, hour_y); pango_cairo_show_layout (cr, layout); hour += mts->zoomed_out ? 3 : 1; hour_x += mts->col_width; } g_object_unref (layout); cairo_restore (cr); g_free (str); } /* This paints the colored bars representing busy periods for the combined * list of attendees. For now we just paint the bars for each attendee of * each other. If we want to speed it up we could optimise it later. */ static void e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height) { EMeetingTimeSelector *mts; EMeetingFreeBusyType busy_type; gint row, y; gint *first_periods; mts = mts_item->mts; /* Calculate the y coordinate to paint the row at in the drawable. */ y = 2 * mts->row_height - scroll_y - 1; /* Get the first visible busy periods for all the attendees. */ first_periods = g_new (gint, e_meeting_store_count_actual_attendees (mts->model)); for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++) first_periods[row] = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row); for (busy_type = 0; busy_type < E_MEETING_FREE_BUSY_LAST; busy_type++) { gdk_cairo_set_source_color (cr, &mts->busy_colors[busy_type]); for (row = 0; row < e_meeting_store_count_actual_attendees (mts->model); row++) { if (first_periods[row] == -1) continue; e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, cr, x, y, width, row, first_periods[row], busy_type); } } g_free (first_periods); } static void e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height) { EMeetingTimeSelector *mts; gint grid_x, grid_y, attendee_index, unused_y; cairo_save (cr); cairo_set_line_width (cr, 1.0); mts = mts_item->mts; /* Draw the grid lines. The grid lines around unused rows are drawn in * a different color. */ /* Draw the horizontal grid lines. */ attendee_index = scroll_y / mts->row_height; #if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM for (grid_y = mts->row_height - 1 - (scroll_y % mts->row_height); #else for (grid_y = - (scroll_y % mts->row_height); #endif grid_y < height; grid_y += mts->row_height) { if (attendee_index <= e_meeting_store_count_actual_attendees (mts->model)) { gdk_cairo_set_source_color (cr, &mts->grid_color); } else { gdk_cairo_set_source_color (cr, &mts->grid_unused_color); } cairo_move_to (cr, 0, grid_y); cairo_rel_line_to (cr, width, 0); cairo_stroke (cr); attendee_index++; } /* Draw the vertical grid lines. */ unused_y = (e_meeting_store_count_actual_attendees (mts->model) * mts->row_height) - scroll_y; if (unused_y >= 0) { gdk_cairo_set_source_color (cr, &mts->grid_color); for (grid_x = mts->col_width - 1; grid_x < mts->day_width - mts->col_width; grid_x += mts->col_width) { cairo_move_to (cr, x + grid_x, 0); cairo_line_to (cr, x + grid_x, unused_y - 1); } cairo_stroke (cr); cairo_rectangle ( cr, x + mts->day_width - 2, 0, 2, unused_y); cairo_fill (cr); } if (unused_y < height) { gdk_cairo_set_source_color (cr, &mts->grid_unused_color); for (grid_x = mts->col_width - 1; grid_x < mts->day_width - mts->col_width; grid_x += mts->col_width) { cairo_move_to (cr, x + grid_x, unused_y); cairo_line_to (cr, x + grid_x, height); } cairo_stroke (cr); cairo_rectangle ( cr, x + mts->day_width - 2, unused_y, 2, height - unused_y); cairo_fill (cr); } cairo_restore (cr); } /* This paints the colored bars representing busy periods for the individual * attendees. */ static void e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, GDate *date, gint x, gint scroll_y, gint width, gint height) { EMeetingTimeSelector *mts; EMeetingFreeBusyType busy_type; gint row, y, first_period; mts = mts_item->mts; /* Calculate the first visible attendee row. */ row = scroll_y / mts->row_height; /* Calculate the y coordinate to paint the row at in the drawable. */ y = row * mts->row_height - scroll_y; /* Step through the attendees painting the busy periods. */ while (y < height && row < e_meeting_store_count_actual_attendees (mts->model)) { /* Find the first visible busy period. */ first_period = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row); if (first_period != -1) { /* Paint the different types of busy periods, in * reverse order of precedence, so the highest * precedences are displayed. */ for (busy_type = 0; busy_type < E_MEETING_FREE_BUSY_LAST; busy_type++) { gdk_cairo_set_source_color (cr, &mts->busy_colors[busy_type]); e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, cr, x, y, width, row, first_period, busy_type); } } y += mts->row_height; row++; } } /* This subtracts the attendees longest_period_in_days from the given date, * and does a binary search of the attendee's busy periods array to find the * first one which could possible end on the given day or later. * If none are found it returns -1. */ static gint e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item, GDate *date, gint row) { EMeetingTimeSelector *mts; EMeetingAttendee *ia; const GArray *busy_periods; EMeetingFreeBusyPeriod *period; gint period_num; mts = mts_item->mts; ia = e_meeting_store_find_attendee_at_row (mts->model, row); period_num = e_meeting_attendee_find_first_busy_period (ia, date); if (period_num == -1) return -1; /* Check if the period starts after the end of the current canvas * scroll area. */ busy_periods = e_meeting_attendee_get_busy_periods (ia); period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num); if (g_date_compare (&mts->last_date_shown, &period->start.date) < 0) return -1; return period_num; } /* This paints the visible busy periods for one attendee which are of a certain * busy type, e.g out of office. It is passed the index of the first visible * busy period of the attendee and continues until it runs off the screen. */ static void e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item, cairo_t *cr, gint x, gint y, gint width, gint row, gint first_period, EMeetingFreeBusyType busy_type) { EMeetingTimeSelector *mts; EMeetingAttendee *ia; const GArray *busy_periods; EMeetingFreeBusyPeriod *period; gint period_num, x1, x2, x2_within_day, x2_within_col; mts = mts_item->mts; ia = e_meeting_store_find_attendee_at_row (mts->model, row); busy_periods = e_meeting_attendee_get_busy_periods (ia); for (period_num = first_period; period_num < busy_periods->len; period_num++) { period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num); if (period->busy_type != busy_type) continue; /* Convert the period start and end times to x coordinates. */ x1 = e_meeting_time_selector_calculate_time_position (mts, &period->start); /* If the period is off the right of the area being drawn, we * are finished. */ if (x1 >= x + width) return; x2 = e_meeting_time_selector_calculate_time_position (mts, &period->end); /* If the period is off the left edge of the area skip it. */ if (x2 <= x) continue; /* We paint from x1 to x2 - 1, so that for example a time * from 5:00-6:00 is distinct from 6:00-7:00. * We never finish on a grid line separating days, and we only * ever paint on a normal vertical grid line if the period is * only 1 pixel wide. */ x2_within_day = x2 % mts->day_width; if (x2_within_day == 0) { x2 -= 2; } else if (x2_within_day == mts->day_width - 1) { x2 -= 1; } else { x2_within_col = x2_within_day % mts->col_width; if (x2_within_col == 0 && x2 > x1 + 1) x2 -= 1; } /* Paint the rectangle. We leave a gap of 2 pixels at the * top and bottom, remembering that the grid is painted along * the top/bottom line of each row. */ if (x2 - x1 > 0) { #if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM cairo_rectangle ( cr, x1 - x, y + 2, x2 - x1, mts->row_height - 5); #else cairo_rectangle ( cr, x1 - x, y + 3, x2 - x1, mts->row_height - 5); #endif cairo_fill (cr); } } } /* * CANVAS ITEM ROUTINES - functions to be a GnomeCanvasItem. */ static GnomeCanvasItem * e_meeting_time_selector_item_point (GnomeCanvasItem *item, gdouble x, gdouble y, gint cx, gint cy) { return item; } static gint e_meeting_time_selector_item_event (GnomeCanvasItem *item, GdkEvent *event) { EMeetingTimeSelectorItem *mts_item; mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); switch (event->type) { case GDK_BUTTON_PRESS: return e_meeting_time_selector_item_button_press ( mts_item, event); case GDK_BUTTON_RELEASE: return e_meeting_time_selector_item_button_release ( mts_item, event); case GDK_MOTION_NOTIFY: return e_meeting_time_selector_item_motion_notify ( mts_item, event); default: break; } return FALSE; } /* This handles all button press events for the item. If the cursor is over * one of the meeting time vertical bars we start a drag. If not we set the * meeting time to the nearest half-hour interval. * Note that GnomeCanvas converts the event coords to world coords, * i.e. relative to the entire canvas scroll area. */ static gint e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item, GdkEvent *event) { EMeetingTimeSelector *mts; EMeetingTime start_time, end_time; EMeetingTimeSelectorPosition position; GDate *start_date, *end_date; gint x, y; mts = mts_item->mts; x = (gint) event->button.x; y = (gint) event->button.y; /* Check if we are starting a drag of the vertical meeting time bars.*/ position = e_meeting_time_selector_item_get_drag_position ( mts_item, x, y); if (position != E_MEETING_TIME_SELECTOR_POS_NONE) { GdkGrabStatus grab_status; GdkDevice *event_device; guint32 event_time; event_device = gdk_event_get_device (event); event_time = gdk_event_get_time (event); grab_status = gnome_canvas_item_grab ( GNOME_CANVAS_ITEM (mts_item), GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, mts_item->resize_cursor, event_device, event_time); if (grab_status == GDK_GRAB_SUCCESS) { mts->dragging_position = position; return TRUE; } } /* Convert the x coordinate into a EMeetingTimeSelectorTime. */ e_meeting_time_selector_calculate_time (mts, x, &start_time); start_date = &start_time.date; end_date = &end_time.date; /* Find the nearest half-hour or hour interval, depending on whether * zoomed_out is set. */ if (!mts->all_day) { gint astart_year, astart_month, astart_day, astart_hour, astart_minute; gint aend_year, aend_month, aend_day, aend_hour, aend_minute; gint hdiff, mdiff; GDate asdate, aedate; e_meeting_time_selector_get_meeting_time ( mts_item->mts, &astart_year, &astart_month, &astart_day, &astart_hour, &astart_minute, &aend_year, &aend_month, &aend_day, &aend_hour, &aend_minute); if (mts->zoomed_out) start_time.minute = 0; else start_time.minute -= start_time.minute % 30; g_date_set_dmy (&asdate, astart_day, astart_month, astart_year); g_date_set_dmy (&aedate, aend_day, aend_month, aend_year); end_time = start_time; mdiff = end_time.minute + aend_minute - astart_minute; hdiff = end_time.hour + aend_hour - astart_hour + (24 * g_date_days_between (&asdate, &aedate)); while (mdiff < 0) { mdiff += 60; hdiff -= 1; } while (mdiff > 60) { mdiff -= 60; hdiff += 1; } while (hdiff < 0) { hdiff += 24; g_date_subtract_days (end_date, 1); } while (hdiff >= 24) { hdiff -= 24; g_date_add_days (end_date, 1); } end_time.minute = mdiff; end_time.hour = hdiff; } else { start_time.hour = 0; start_time.minute = 0; end_time = start_time; g_date_add_days (&end_time.date, 1); } /* Fix any overflows. */ e_meeting_time_selector_fix_time_overflows (&end_time); /* Set the new meeting time. */ e_meeting_time_selector_set_meeting_time ( mts_item->mts, g_date_get_year (start_date), g_date_get_month (start_date), g_date_get_day (start_date), start_time.hour, start_time.minute, g_date_get_year (end_date), g_date_get_month (end_date), g_date_get_day (end_date), end_time.hour, end_time.minute); return FALSE; } /* This handles all button release events for the item. If we were dragging, * we finish the drag. */ static gint e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item, GdkEvent *event) { EMeetingTimeSelector *mts; mts = mts_item->mts; /* Reset any drag. */ if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) { mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE; e_meeting_time_selector_remove_timeout (mts); gnome_canvas_item_ungrab ( GNOME_CANVAS_ITEM (mts_item), event->button.time); } return FALSE; } /* This handles all motion notify events for the item. If button1 is pressed * we check if a drag is in progress. If not, we set the cursor if we are over * the meeting time vertical bars. Note that GnomeCanvas doesn't use motion * hints, which may affect performance. */ static gint e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item, GdkEvent *event) { EMeetingTimeSelector *mts; EMeetingTimeSelectorPosition position; GdkCursor *cursor; gint x, y; mts = mts_item->mts; x = (gint) event->motion.x; y = (gint) event->motion.y; if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) { e_meeting_time_selector_drag_meeting_time (mts, x); return TRUE; } position = e_meeting_time_selector_item_get_drag_position ( mts_item, x, y); /* Determine which cursor should be used. */ if (position != E_MEETING_TIME_SELECTOR_POS_NONE) cursor = mts_item->resize_cursor; /* If the Main window shows busy cursor show the same */ else if (mts_item->mts->last_cursor_set == GDK_WATCH) cursor = mts_item->busy_cursor; else cursor = mts_item->normal_cursor; /* Only set the cursor if it is different to the last one we set. */ if (mts_item->last_cursor_set != cursor) { GdkWindow *window; GnomeCanvas *canvas; mts_item->last_cursor_set = cursor; canvas = GNOME_CANVAS_ITEM (mts_item)->canvas; window = gtk_widget_get_window (GTK_WIDGET (canvas)); gdk_window_set_cursor (window, cursor); } return FALSE; } static EMeetingTimeSelectorPosition e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, gint x, gint y) { EMeetingTimeSelector *mts; gboolean is_display_top; gint meeting_start_x, meeting_end_x; mts = mts_item->mts; is_display_top = (GTK_WIDGET (GNOME_CANVAS_ITEM (mts_item)->canvas) == mts->display_top) ? TRUE : FALSE; if (is_display_top && y < mts->row_height * 2) return E_MEETING_TIME_SELECTOR_POS_NONE; if (!e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x)) return E_MEETING_TIME_SELECTOR_POS_NONE; if (x >= meeting_end_x - 2 && x <= meeting_end_x + 2) return E_MEETING_TIME_SELECTOR_POS_END; if (x >= meeting_start_x - 2 && x <= meeting_start_x + 2) return E_MEETING_TIME_SELECTOR_POS_START; return E_MEETING_TIME_SELECTOR_POS_NONE; } static gboolean e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts, gint row, gint x, gint width, gint *start_x, gint *end_x) { EMeetingAttendee *ia; EMeetingTime busy_periods_start; EMeetingTime busy_periods_end; ia = e_meeting_store_find_attendee_at_row (mts->model, row); busy_periods_start = e_meeting_attendee_get_start_busy_range (ia); busy_periods_end = e_meeting_attendee_get_end_busy_range (ia); *start_x = -1; *end_x = -1; if (!g_date_valid (&busy_periods_start.date) || !g_date_valid (&busy_periods_end.date)) return FALSE; *start_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_start) - x - 1; *end_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_end) - x; return TRUE; } void e_meeting_time_selector_item_set_normal_cursor (EMeetingTimeSelectorItem *mts_item) { GnomeCanvas *canvas; GdkWindow *window; g_return_if_fail (IS_E_MEETING_TIME_SELECTOR_ITEM (mts_item)); canvas = GNOME_CANVAS_ITEM (mts_item)->canvas; window = gtk_widget_get_window (GTK_WIDGET (canvas)); if (window) gdk_window_set_cursor (window, mts_item->normal_cursor); }