From c485df7554158379bbbd3c60d232a0612f786437 Mon Sep 17 00:00:00 2001 From: Damon Chaplin Date: Sun, 19 Dec 1999 21:13:19 +0000 Subject: added widgets/meeting-time-sel/Makefile 1999-12-19 Damon Chaplin * configure.in (AC_OUTPUT): added widgets/meeting-time-sel/Makefile In widgets directory: 1999-12-19 Damon Chaplin * Makefile.am (SUBDIRS): created SUBDIRS with meeting-time-sel. * meeting-time-sel/Makefile.am: * meeting-time-sel/.cvsignore: * meeting-time-sel/e-meeting-time-sel.h: * meeting-time-sel/e-meeting-time-sel.c: * meeting-time-sel/e-meeting-time-sel-item.h: * meeting-time-sel/e-meeting-time-sel-item.c: * meeting-time-sel/e-meeting-time-sel-list-item.h: * meeting-time-sel/e-meeting-time-sel-list-item.c: * meeting-time-sel/e-meeting-time-sel-mail.xpm: * meeting-time-sel/e-meeting-time-sel-no-mail.xpm: * meeting-time-sel/test-meeting-time-sel.c: new files implementing the meeting time selector. svn path=/trunk/; revision=1505 --- widgets/meeting-time-sel/e-meeting-time-sel-item.c | 924 +++++++++++++++++++++ 1 file changed, 924 insertions(+) create mode 100644 widgets/meeting-time-sel/e-meeting-time-sel-item.c (limited to 'widgets/meeting-time-sel/e-meeting-time-sel-item.c') diff --git a/widgets/meeting-time-sel/e-meeting-time-sel-item.c b/widgets/meeting-time-sel/e-meeting-time-sel-item.c new file mode 100644 index 0000000000..cb716d6f08 --- /dev/null +++ b/widgets/meeting-time-sel/e-meeting-time-sel-item.c @@ -0,0 +1,924 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin + * + * Copyright 1999, Helix Code, 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 + */ + +/* + * 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. + */ + +#include +#include +#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_class_init (EMeetingTimeSelectorItemClass *mts_item_class); +static void e_meeting_time_selector_item_init (EMeetingTimeSelectorItem *mts_item); +static void e_meeting_time_selector_item_destroy (GtkObject *object); + +static void e_meeting_time_selector_item_set_arg (GtkObject *o, GtkArg *arg, + guint arg_id); +static void e_meeting_time_selector_item_realize (GnomeCanvasItem *item); +static void e_meeting_time_selector_item_unrealize (GnomeCanvasItem *item); +static void e_meeting_time_selector_item_update (GnomeCanvasItem *item, + double *affine, + ArtSVP *clip_path, int flags); +static void e_meeting_time_selector_item_draw (GnomeCanvasItem *item, + GdkDrawable *drawable, + int x, int y, + int width, int height); +static double e_meeting_time_selector_item_point (GnomeCanvasItem *item, + double x, double y, + int cx, int cy, + GnomeCanvasItem **actual_item); +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, + GdkDrawable *drawable, + GDate *date, + int x, int scroll_y, + int width, int height); +static void e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item, GdkDrawable *drawable, GDate *date, int x, int y, int width, int height); +static void e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item, + GdkDrawable *drawable, + GDate *date, + int x, int scroll_y, + int width, int height); +static void e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item, GdkDrawable *drawable, GDate *date, int x, int scroll_y, int width, int 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, GdkDrawable *drawable, int row, int x, int y, int width, int first_period, EMeetingTimeSelectorBusyType busy_type); + +static EMeetingTimeSelectorPosition e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, gint x, gint y); + +static GnomeCanvasItemClass *parent_class; + +/* The arguments we take */ +enum { + ARG_0, + ARG_MEETING_TIME_SELECTOR +}; + + +GtkType +e_meeting_time_selector_item_get_type (void) +{ + static GtkType e_meeting_time_selector_item_type = 0; + + if (!e_meeting_time_selector_item_type) { + GtkTypeInfo e_meeting_time_selector_item_info = { + "EMeetingTimeSelectorItem", + sizeof (EMeetingTimeSelectorItem), + sizeof (EMeetingTimeSelectorItemClass), + (GtkClassInitFunc) e_meeting_time_selector_item_class_init, + (GtkObjectInitFunc) e_meeting_time_selector_item_init, + NULL, /* reserved_1 */ + NULL, /* reserved_2 */ + (GtkClassInitFunc) NULL + }; + + e_meeting_time_selector_item_type = gtk_type_unique (gnome_canvas_item_get_type (), &e_meeting_time_selector_item_info); + } + + return e_meeting_time_selector_item_type; +} + + +static void +e_meeting_time_selector_item_class_init (EMeetingTimeSelectorItemClass *mts_item_class) +{ + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + parent_class = gtk_type_class (gnome_canvas_item_get_type()); + + object_class = (GtkObjectClass *) mts_item_class; + item_class = (GnomeCanvasItemClass *) mts_item_class; + + gtk_object_add_arg_type ("EMeetingTimeSelectorItem::meeting_time_selector", + GTK_TYPE_POINTER, GTK_ARG_WRITABLE, + ARG_MEETING_TIME_SELECTOR); + + object_class->destroy = e_meeting_time_selector_item_destroy; + object_class->set_arg = e_meeting_time_selector_item_set_arg; + + /* GnomeCanvasItem method overrides */ + item_class->realize = e_meeting_time_selector_item_realize; + item_class->unrealize = e_meeting_time_selector_item_unrealize; + 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; +} + + +static void +e_meeting_time_selector_item_init (EMeetingTimeSelectorItem *mts_item) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (mts_item); + + mts_item->mts = NULL; + + mts_item->main_gc = NULL; + + /* Create the cursors. */ + mts_item->normal_cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW); + mts_item->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); + 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_destroy (GtkObject *object) +{ + EMeetingTimeSelectorItem *mts_item; + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (object); + + gdk_cursor_destroy (mts_item->normal_cursor); + gdk_cursor_destroy (mts_item->resize_cursor); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (*GTK_OBJECT_CLASS (parent_class)->destroy)(object); +} + + +static void +e_meeting_time_selector_item_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) +{ + GnomeCanvasItem *item; + EMeetingTimeSelectorItem *mts_item; + + item = GNOME_CANVAS_ITEM (o); + mts_item = E_MEETING_TIME_SELECTOR_ITEM (o); + + switch (arg_id){ + case ARG_MEETING_TIME_SELECTOR: + mts_item->mts = GTK_VALUE_POINTER (*arg); + break; + } +} + + +static void +e_meeting_time_selector_item_realize (GnomeCanvasItem *item) +{ + GnomeCanvas *canvas; + GdkWindow *window; + EMeetingTimeSelectorItem *mts_item; + + if (GNOME_CANVAS_ITEM_CLASS (parent_class)->realize) + (*GNOME_CANVAS_ITEM_CLASS (parent_class)->realize)(item); + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); + + canvas = item->canvas; + window = GTK_WIDGET (canvas)->window; + + mts_item->main_gc = gdk_gc_new (window); +} + + +static void +e_meeting_time_selector_item_unrealize (GnomeCanvasItem *item) +{ + EMeetingTimeSelectorItem *mts_item; + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); + + gdk_gc_unref (mts_item->main_gc); + mts_item->main_gc = NULL; + + if (GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize) + (*GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize)(item); +} + + +static void +e_meeting_time_selector_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 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 +e_meeting_time_selector_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height) +{ + EMeetingTimeSelector *mts; + EMeetingTimeSelectorItem *mts_item; + EMeetingTimeSelectorAttendee *attendee; + gint day_x, meeting_start_x, meeting_end_x, bar_y, bar_height; + gint row, row_y; + GDate date, last_date, current_date; + gboolean is_display_top, show_meeting_time; + GdkGC *gc; + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); + mts = mts_item->mts; + g_return_if_fail (mts != NULL); + gc = mts_item->main_gc; + + 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. */ + if (is_display_top) { + gdk_gc_set_foreground (gc, &mts->all_attendees_bg_color); + gdk_draw_rectangle (drawable, gc, TRUE, + 0, mts->row_height * 2 - y, + width, mts->row_height); + } else { + gdk_gc_set_foreground (gc, &mts->bg_color); + gdk_draw_rectangle (drawable, gc, TRUE, 0, 0, width, height); + } + + /* 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)) { + gdk_gc_set_foreground (gc, &mts->meeting_time_bg_color); + if (is_display_top) + gdk_draw_rectangle (drawable, gc, TRUE, + meeting_start_x + 1 - x, mts->row_height * 2 - y, + meeting_end_x - meeting_start_x - 2, height); + else + gdk_draw_rectangle (drawable, gc, TRUE, + meeting_start_x + 1 - x, 0, + meeting_end_x - meeting_start_x - 2, height); + } + + /* For the main display draw the stipple background for attendee's + that have no calendar information. */ + if (!is_display_top) { + gdk_gc_set_foreground (gc, &mts->grid_color); + gdk_gc_set_background (gc, &mts->stipple_bg_color); + gdk_gc_set_stipple (gc, mts->stipple); + gnome_canvas_set_stipple_origin (item->canvas, gc); + gdk_gc_set_fill (gc, GDK_OPAQUE_STIPPLED); + row = y / mts->row_height; + row_y = row * mts->row_height - y; + while (row < mts->attendees->len && row_y < height) { + attendee = &g_array_index (mts->attendees, + EMeetingTimeSelectorAttendee, row); + if (!attendee->has_calendar_info) { + gdk_draw_rectangle (drawable, gc, TRUE, + 0, row_y, + width, mts->row_height); + } + row++; + row_y += mts->row_height; + } + gdk_gc_set_fill (gc, GDK_SOLID); + } + + /* 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, drawable, ¤t_date, day_x, y, width, height); + else + e_meeting_time_selector_item_paint_day (mts_item, drawable, ¤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, drawable, &date, x, y, width, height); + else + e_meeting_time_selector_item_paint_busy_periods (mts_item, drawable, &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; + } + + gdk_gc_set_foreground (gc, &mts->grid_color); + + if ((meeting_start_x + 2 >= x) + && (meeting_start_x - 2 < x + width)) { + gdk_draw_rectangle (drawable, gc, TRUE, + meeting_start_x - 2 - x, bar_y, + 5, bar_height); + } + + if ((meeting_end_x + 2 >= x) + && (meeting_end_x - 2 < x + width)) { + gdk_draw_rectangle (drawable, gc, TRUE, + meeting_end_x - 2 - x, bar_y, + 5, bar_height); + } + } +} + + +static void +e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item, + GdkDrawable *drawable, GDate *date, + int x, int scroll_y, + int width, int height) +{ + EMeetingTimeSelector *mts; + GdkGC *gc; + GdkFont *font; + gint y, grid_x; + gchar buffer[128]; + gint hour, hour_x, hour_y; + GdkRectangle clip_rect; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + gdk_gc_set_foreground (gc, &mts->grid_color); + + /* Draw the horizontal lines. */ + y = mts->row_height - 1 - scroll_y; + gdk_draw_line (drawable, gc, x, y, x + mts->day_width - 1, y); + gdk_gc_set_foreground (gc, &mts->grid_shadow_color); + gdk_draw_line (drawable, gc, x, y + 1, x + mts->day_width - 1, y + 1); + gdk_gc_set_foreground (gc, &mts->grid_color); + y += mts->row_height; + gdk_draw_line (drawable, gc, x, y, x + mts->day_width - 1, y); + y += mts->row_height; + gdk_draw_line (drawable, gc, x, y, x + mts->day_width - 1, y); + + + /* 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) { + gdk_draw_line (drawable, gc, + x + grid_x, mts->row_height * 2 - 4, + x + grid_x, height); + } + grid_x = mts->day_width - 2; + gdk_draw_line (drawable, gc, x + grid_x, 0, x + grid_x, height); + grid_x++; + gdk_draw_line (drawable, gc, x + grid_x, 0, x + grid_x, height); + + /* Draw the date. Set a clipping rectangle so we don't draw over the + next day. */ + font = GTK_WIDGET (mts)->style->font; + if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_FULL) + g_date_strftime (buffer, 128, "%A, %B %d, %Y", date); + else if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY) + g_date_strftime (buffer, 128, "%a %x", date); + else + g_date_strftime (buffer, 128, "%x", date); + clip_rect.x = x; + clip_rect.y = -scroll_y; + clip_rect.width = mts->day_width - 2; + clip_rect.height = mts->row_height - 2; + gdk_gc_set_clip_rectangle (gc, &clip_rect); + gdk_draw_string (drawable, font, gc, + x + 4, 4 + font->ascent - scroll_y, buffer); + gdk_gc_set_clip_rectangle (gc, NULL); + + /* Draw the hours. */ + hour = mts->first_hour_shown + (mts->zoomed_out ? 3 : 1); + hour_x = x + mts->col_width; + hour_y = mts->row_height + 4 + font->ascent - scroll_y; + while (hour < mts->last_hour_shown) { + gdk_draw_string (drawable, font, gc, + hour_x - (mts->hour_widths[hour] / 2), + hour_y, EMeetingTimeSelectorHours[hour]); + + hour += mts->zoomed_out ? 3 : 1; + hour_x += mts->col_width; + } +} + + +/* 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, GdkDrawable *drawable, GDate *date, int x, int scroll_y, int width, int height) +{ + EMeetingTimeSelector *mts; + EMeetingTimeSelectorBusyType busy_type; + gint row, y; + GdkGC *gc; + gint *first_periods; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + /* 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, mts->attendees->len); + for (row = 0; row < mts->attendees->len; 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_TIME_SELECTOR_BUSY_LAST; + busy_type++) { + gdk_gc_set_foreground (gc, &mts->busy_colors[busy_type]); + for (row = 0; row < mts->attendees->len; row++) { + if (first_periods[row] == -1) + continue; + e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, drawable, 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, + GdkDrawable *drawable, GDate *date, + int x, int scroll_y, + int width, int height) +{ + EMeetingTimeSelector *mts; + GdkGC *gc; + gint grid_x, grid_y, attendee_index, unused_y; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + /* 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 < mts->attendees->len) { + gdk_gc_set_foreground (gc, &mts->grid_color); + gdk_draw_line (drawable, gc, 0, grid_y, + width, grid_y); + } else { + gdk_gc_set_foreground (gc, &mts->grid_unused_color); + gdk_draw_line (drawable, gc, 0, grid_y, + width, grid_y); + } + attendee_index++; + } + + /* Draw the vertical grid lines. */ + unused_y = (mts->attendees->len * mts->row_height) - scroll_y; + if (unused_y >= 0) { + gdk_gc_set_foreground (gc, &mts->grid_color); + for (grid_x = mts->col_width - 1; + grid_x < mts->day_width - mts->col_width; + grid_x += mts->col_width) + { + gdk_draw_line (drawable, gc, + x + grid_x, 0, + x + grid_x, unused_y - 1); + } + gdk_draw_rectangle (drawable, gc, TRUE, + x + mts->day_width - 2, 0, + 2, unused_y); + } + + if (unused_y < height) { + gdk_gc_set_foreground (gc, &mts->grid_unused_color); + for (grid_x = mts->col_width - 1; + grid_x < mts->day_width - mts->col_width; + grid_x += mts->col_width) + { + gdk_draw_line (drawable, gc, + x + grid_x, unused_y, + x + grid_x, height); + } + gdk_draw_rectangle (drawable, gc, TRUE, + x + mts->day_width - 2, unused_y, + 2, height - unused_y); + } + + +} + + +/* 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, GdkDrawable *drawable, GDate *date, int x, int scroll_y, int width, int height) +{ + EMeetingTimeSelector *mts; + EMeetingTimeSelectorBusyType busy_type; + gint row, y, first_period; + GdkGC *gc; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + /* 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 < mts->attendees->len) { + + /* 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_TIME_SELECTOR_BUSY_LAST; + busy_type++) { + gdk_gc_set_foreground (gc, &mts->busy_colors[busy_type]); + e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, drawable, 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; + EMeetingTimeSelectorAttendee *attendee; + EMeetingTimeSelectorPeriod *period; + gint period_num; + + mts = mts_item->mts; + + attendee = &g_array_index (mts->attendees, + EMeetingTimeSelectorAttendee, row); + + period_num = e_meeting_time_selector_find_first_busy_period (mts, attendee, date); + if (period_num == -1) + return -1; + + /* Check if the period starts after the end of the current canvas + scroll area. */ + period = &g_array_index (attendee->busy_periods, + EMeetingTimeSelectorPeriod, 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, GdkDrawable *drawable, int x, int y, int width, int row, int first_period, EMeetingTimeSelectorBusyType busy_type) +{ + EMeetingTimeSelector *mts; + EMeetingTimeSelectorAttendee *attendee; + EMeetingTimeSelectorPeriod *period; + GdkGC *gc; + gint period_num, x1, x2, x2_within_day, x2_within_col; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + attendee = &g_array_index (mts->attendees, + EMeetingTimeSelectorAttendee, row); + + for (period_num = first_period; + period_num < attendee->busy_periods->len; + period_num++) { + period = &g_array_index (attendee->busy_periods, + EMeetingTimeSelectorPeriod, 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 + gdk_draw_rectangle (drawable, gc, TRUE, + x1 - x, y + 2, + x2 - x1, mts->row_height - 5); +#else + gdk_draw_rectangle (drawable, gc, TRUE, + x1 - x, y + 3, + x2 - x1, mts->row_height - 5); +#endif + } + } +} + + +/* + * CANVAS ITEM ROUTINES - functions to be a GnomeCanvasItem. + */ + +/* 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 double +e_meeting_time_selector_item_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, + GnomeCanvasItem **actual_item) +{ + *actual_item = item; + return 0.0; +} + + +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; + EMeetingTimeSelectorTime 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) { + if (gnome_canvas_item_grab (GNOME_CANVAS_ITEM (mts_item), + GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK, + mts_item->resize_cursor, + event->button.time) == 0 /*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->zoomed_out) { + start_time.minute = 0; + end_time = start_time; + end_time.hour += 1; + } else { + start_time.minute -= start_time.minute % 30; + end_time = start_time; + end_time.minute += 30; + } + + /* 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_year (start_date), + g_date_month (start_date), + g_date_day (start_date), + start_time.hour, + start_time.minute, + g_date_year (end_date), + g_date_month (end_date), + g_date_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->normal_cursor; + else + cursor = mts_item->resize_cursor; + + /* Only set the cursor if it is different to the last one we set. */ + if (mts_item->last_cursor_set != cursor) { + mts_item->last_cursor_set = cursor; + gdk_window_set_cursor (GTK_WIDGET (GNOME_CANVAS_ITEM (mts_item)->canvas)->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; +} + -- cgit v1.2.3