diff options
author | Damon Chaplin <damon@src.gnome.org> | 2000-03-14 07:23:40 +0800 |
---|---|---|
committer | Damon Chaplin <damon@src.gnome.org> | 2000-03-14 07:23:40 +0800 |
commit | db839e2f1fb423acd8758247606bca5c652a3643 (patch) | |
tree | c7f46b96e8a990cd7418de00b1cd5640155e487b | |
parent | f59d7438e2498b9a7dcaf3d95e403069338ca0e2 (diff) | |
download | gsoc2013-evolution-db839e2f1fb423acd8758247606bca5c652a3643.tar.gz gsoc2013-evolution-db839e2f1fb423acd8758247606bca5c652a3643.tar.zst gsoc2013-evolution-db839e2f1fb423acd8758247606bca5c652a3643.zip |
New widget for Day/Work-Week views. Unfinished.
svn path=/trunk/; revision=2115
-rw-r--r-- | calendar/ChangeLog | 4 | ||||
-rw-r--r-- | calendar/gui/e-day-view-main-item.c | 631 | ||||
-rw-r--r-- | calendar/gui/e-day-view-main-item.h | 65 | ||||
-rw-r--r-- | calendar/gui/e-day-view-time-item.c | 312 | ||||
-rw-r--r-- | calendar/gui/e-day-view-time-item.h | 71 | ||||
-rw-r--r-- | calendar/gui/e-day-view-top-item.c | 553 | ||||
-rw-r--r-- | calendar/gui/e-day-view-top-item.h | 65 | ||||
-rw-r--r-- | calendar/gui/e-day-view.c | 4585 | ||||
-rw-r--r-- | calendar/gui/e-day-view.h | 463 |
9 files changed, 6749 insertions, 0 deletions
diff --git a/calendar/ChangeLog b/calendar/ChangeLog index 01c67ce777..9f73875d9b 100644 --- a/calendar/ChangeLog +++ b/calendar/ChangeLog @@ -1,3 +1,7 @@ +2000-03-13 Damon Chaplin <damon@helixcode.com> + + * gui/e-day-view*.[hc]: new files for the Day/Work-Week views. + 2000-03-12 Federico Mena Quintero <federico@helixcode.com> * gui/main.c (gnome_calendar_locate): Removed function now that it diff --git a/calendar/gui/e-day-view-main-item.c b/calendar/gui/e-day-view-main-item.c new file mode 100644 index 0000000000..5d002b5945 --- /dev/null +++ b/calendar/gui/e-day-view-main-item.c @@ -0,0 +1,631 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * 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 + */ + +/* + * EDayViewMainItem - canvas item which displays most of the appointment + * data in the main Day/Work Week display. + */ + +#include "e-day-view-main-item.h" + +static void e_day_view_main_item_class_init (EDayViewMainItemClass *class); +static void e_day_view_main_item_init (EDayViewMainItem *dvtitem); + +static void e_day_view_main_item_set_arg (GtkObject *o, GtkArg *arg, + guint arg_id); +static void e_day_view_main_item_update (GnomeCanvasItem *item, + double *affine, + ArtSVP *clip_path, int flags); +static void e_day_view_main_item_draw (GnomeCanvasItem *item, + GdkDrawable *drawable, + int x, int y, + int width, int height); +static double e_day_view_main_item_point (GnomeCanvasItem *item, + double x, double y, + int cx, int cy, + GnomeCanvasItem **actual_item); +static gint e_day_view_main_item_event (GnomeCanvasItem *item, + GdkEvent *event); + +static void e_day_view_main_item_draw_long_events_in_vbars (EDayViewMainItem *dvmitem, + GdkDrawable *drawable, + int x, + int y, + int width, + int height); +static void e_day_view_main_item_draw_events_in_vbars (EDayViewMainItem *dvmitem, + GdkDrawable *drawable, + int x, int y, + int width, int height, + gint day); +static void e_day_view_main_item_draw_day_events (EDayViewMainItem *dvmitem, + GdkDrawable *drawable, + int x, int y, + int width, int height, + gint day); +static void e_day_view_main_item_draw_day_event (EDayViewMainItem *dvmitem, + GdkDrawable *drawable, + int x, int y, + int width, int height, + gint day, gint event_num); + +static GnomeCanvasItemClass *parent_class; + +/* The arguments we take */ +enum { + ARG_0, + ARG_DAY_VIEW +}; + + +GtkType +e_day_view_main_item_get_type (void) +{ + static GtkType e_day_view_main_item_type = 0; + + if (!e_day_view_main_item_type) { + GtkTypeInfo e_day_view_main_item_info = { + "EDayViewMainItem", + sizeof (EDayViewMainItem), + sizeof (EDayViewMainItemClass), + (GtkClassInitFunc) e_day_view_main_item_class_init, + (GtkObjectInitFunc) e_day_view_main_item_init, + NULL, /* reserved_1 */ + NULL, /* reserved_2 */ + (GtkClassInitFunc) NULL + }; + + e_day_view_main_item_type = gtk_type_unique (gnome_canvas_item_get_type (), &e_day_view_main_item_info); + } + + return e_day_view_main_item_type; +} + + +static void +e_day_view_main_item_class_init (EDayViewMainItemClass *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 ("EDayViewMainItem::day_view", + GTK_TYPE_POINTER, GTK_ARG_WRITABLE, + ARG_DAY_VIEW); + + object_class->set_arg = e_day_view_main_item_set_arg; + + /* GnomeCanvasItem method overrides */ + item_class->update = e_day_view_main_item_update; + item_class->draw = e_day_view_main_item_draw; + item_class->point = e_day_view_main_item_point; + item_class->event = e_day_view_main_item_event; +} + + +static void +e_day_view_main_item_init (EDayViewMainItem *dvtitem) +{ + dvtitem->day_view = NULL; +} + + +static void +e_day_view_main_item_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) +{ + GnomeCanvasItem *item; + EDayViewMainItem *dvmitem; + + item = GNOME_CANVAS_ITEM (o); + dvmitem = E_DAY_VIEW_MAIN_ITEM (o); + + switch (arg_id){ + case ARG_DAY_VIEW: + dvmitem->day_view = GTK_VALUE_POINTER (*arg); + break; + } +} + + +static void +e_day_view_main_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; +} + + +/* + * DRAWING ROUTINES - functions to paint the canvas item. + */ + +static void +e_day_view_main_item_draw (GnomeCanvasItem *canvas_item, GdkDrawable *drawable, + int x, int y, int width, int height) +{ + EDayViewMainItem *dvmitem; + EDayView *day_view; + GtkStyle *style; + GdkGC *fg_gc, *bg_gc, *light_gc, *dark_gc, *gc; + GdkFont *font; + gint row, row_y, grid_x1, grid_x2; + gint day, grid_y1, grid_y2; + gint work_day_start_row, work_day_end_row; + gint work_day_start_y, work_day_end_y; + gint work_day_x, work_day_w; + gint start_row, end_row, rect_x, rect_y, rect_width, rect_height; + +#if 0 + g_print ("In e_day_view_main_item_draw %i,%i %ix%i\n", + x, y, width, height); +#endif + dvmitem = E_DAY_VIEW_MAIN_ITEM (canvas_item); + day_view = dvmitem->day_view; + g_return_if_fail (day_view != NULL); + + style = GTK_WIDGET (day_view)->style; + font = style->font; + fg_gc = style->fg_gc[GTK_STATE_NORMAL]; + bg_gc = style->bg_gc[GTK_STATE_NORMAL]; + light_gc = style->light_gc[GTK_STATE_NORMAL]; + dark_gc = style->dark_gc[GTK_STATE_NORMAL]; + + /* Paint the background colors. */ + gc = day_view->main_gc; + work_day_start_row = e_day_view_convert_time_to_row (day_view, day_view->work_day_start_hour, day_view->work_day_start_minute); + work_day_start_y = work_day_start_row * day_view->row_height - y; + work_day_end_row = e_day_view_convert_time_to_row (day_view, day_view->work_day_end_hour, day_view->work_day_end_minute); + work_day_end_y = work_day_end_row * day_view->row_height - y; + + work_day_x = day_view->day_offsets[0] - x; + work_day_w = width - work_day_x; + gdk_gc_set_foreground (gc, &day_view->colors[E_DAY_VIEW_COLOR_BG_NOT_WORKING]); + gdk_draw_rectangle (drawable, gc, TRUE, + work_day_x, 0 - y, + work_day_w, work_day_start_y - (0 - y)); + gdk_gc_set_foreground (gc, &day_view->colors[E_DAY_VIEW_COLOR_BG_WORKING]); + gdk_draw_rectangle (drawable, gc, TRUE, + work_day_x, work_day_start_y, + work_day_w, work_day_end_y - work_day_start_y); + gdk_gc_set_foreground (gc, &day_view->colors[E_DAY_VIEW_COLOR_BG_NOT_WORKING]); + gdk_draw_rectangle (drawable, gc, TRUE, + work_day_x, work_day_end_y, + work_day_w, height - work_day_end_y); + + /* Paint the selection background. */ + if (day_view->selection_start_col != -1 + && !day_view->selection_in_top_canvas) { + for (day = day_view->selection_start_col; + day <= day_view->selection_end_col; + day++) { + if (day == day_view->selection_start_col + && day_view->selection_start_row != -1) + start_row = day_view->selection_start_row; + else + start_row = 0; + if (day == day_view->selection_end_col + && day_view->selection_end_row != -1) + end_row = day_view->selection_end_row; + else + end_row = day_view->rows - 1; + + rect_x = day_view->day_offsets[day] - x; + rect_width = day_view->day_widths[day]; + rect_y = start_row * day_view->row_height - y; + rect_height = (end_row - start_row + 1) * day_view->row_height; + + gc = style->bg_gc[GTK_STATE_SELECTED]; + gdk_draw_rectangle (drawable, gc, TRUE, + rect_x, rect_y, + rect_width, rect_height); + } + } + + /* Drawing the horizontal grid lines. */ + grid_x1 = day_view->day_offsets[0] - x; + grid_x2 = day_view->day_offsets[day_view->days_shown] - x; + + for (row = 0, row_y = 0 - y; + row < day_view->rows && row_y < height; + row++, row_y += day_view->row_height) { + if (row_y >= 0 && row_y < height) + gdk_draw_line (drawable, dark_gc, + grid_x1, row_y, grid_x2, row_y); + } + + /* Draw the vertical bars down the left of each column. */ + grid_y1 = 0; + grid_y2 = height; + for (day = 0; day < day_view->days_shown; day++) { + grid_x1 = day_view->day_offsets[day] - x; + + /* Skip if it isn't visible. */ + if (grid_x1 >= width || grid_x1 + E_DAY_VIEW_BAR_WIDTH <= 0) + continue; + + gdk_draw_line (drawable, fg_gc, + grid_x1, grid_y1, + grid_x1, grid_y2); + gdk_draw_line (drawable, fg_gc, + grid_x1 + E_DAY_VIEW_BAR_WIDTH - 1, grid_y1, + grid_x1 + E_DAY_VIEW_BAR_WIDTH - 1, grid_y2); + gdk_draw_rectangle (drawable, style->white_gc, TRUE, + grid_x1 + 1, grid_y1, + E_DAY_VIEW_BAR_WIDTH - 2, grid_y2 - grid_y1); + + /* Fill in the bars when the user is busy. */ + e_day_view_main_item_draw_events_in_vbars (dvmitem, drawable, + x, y, + width, height, + day); + } + + /* Fill in the vertical bars corresponding to the busy times from the + long events. */ + e_day_view_main_item_draw_long_events_in_vbars (dvmitem, drawable, + x, y, width, height); + + /* Draw the event borders and backgrounds, and the vertical bars + down the left edges. */ + for (day = 0; day < day_view->days_shown; day++) { + e_day_view_main_item_draw_day_events (dvmitem, drawable, + x, y, width, height, + day); + } +} + + +static void +e_day_view_main_item_draw_events_in_vbars (EDayViewMainItem *dvmitem, + GdkDrawable *drawable, + int x, int y, + int width, int height, + gint day) +{ + EDayView *day_view; + EDayViewEvent *event; + GdkGC *gc; + gint grid_x, event_num, bar_y, bar_h; + + day_view = dvmitem->day_view; + + gc = day_view->main_gc; + gdk_gc_set_foreground (gc, &day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR]); + + grid_x = day_view->day_offsets[day] + 1 - x; + + /* Draw the busy times corresponding to the events in the day. */ + for (event_num = 0; event_num < day_view->events[day]->len; + event_num++) { + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + /* We can skip the events in the first column since they will + draw over this anyway. */ + if (event->num_columns > 0 && event->start_row_or_col == 0) + continue; + + bar_y = event->start_minute * day_view->row_height / day_view->mins_per_row; + bar_h = event->end_minute * day_view->row_height / day_view->mins_per_row - bar_y; + bar_y -= y; + + /* Skip it if it isn't visible. */ + if (bar_y >= height || bar_y + bar_h <= 0) + continue; + + gdk_draw_rectangle (drawable, gc, TRUE, + grid_x, bar_y, + E_DAY_VIEW_BAR_WIDTH - 2, bar_h); + } +} + + +static void +e_day_view_main_item_draw_long_events_in_vbars (EDayViewMainItem *dvmitem, + GdkDrawable *drawable, + int x, int y, + int width, int height) +{ + EDayView *day_view; + EDayViewEvent *event; + gint event_num, start_day, end_day, day, bar_y1, bar_y2, grid_x; + GdkGC *gc; + + day_view = dvmitem->day_view; + + gc = day_view->main_gc; + gdk_gc_set_foreground (gc, &day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR]); + + for (event_num = 0; event_num < day_view->long_events->len; + event_num++) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + if (!e_day_view_find_long_event_days (day_view, event, + &start_day, &end_day)) + continue; + + for (day = start_day; day <= end_day; day++) { + grid_x = day_view->day_offsets[day] + 1 - x; + + /* Skip if it isn't visible. */ + if (grid_x >= width + || grid_x + E_DAY_VIEW_BAR_WIDTH <= 0) + continue; + + if (event->start <= day_view->day_starts[day]) { + bar_y1 = 0; + } else { + bar_y1 = event->start_minute * day_view->row_height / day_view->mins_per_row - y; + } + + if (event->end >= day_view->day_starts[day + 1]) { + bar_y2 = height; + } else { + bar_y2 = event->end_minute * day_view->row_height / day_view->mins_per_row - y; + } + + if (bar_y1 < height && bar_y2 > 0 && bar_y2 > bar_y1) { + gdk_draw_rectangle (drawable, gc, TRUE, + grid_x, bar_y1, + E_DAY_VIEW_BAR_WIDTH - 2, + bar_y2 - bar_y1); + } + } + + + } +} + + +static void +e_day_view_main_item_draw_day_events (EDayViewMainItem *dvmitem, + GdkDrawable *drawable, + int x, int y, int width, int height, + gint day) +{ + EDayView *day_view; + gint event_num; + + day_view = dvmitem->day_view; + + for (event_num = 0; event_num < day_view->events[day]->len; + event_num++) { + e_day_view_main_item_draw_day_event (dvmitem, drawable, + x, y, width, height, + day, event_num); + } +} + + +static void +e_day_view_main_item_draw_day_event (EDayViewMainItem *dvmitem, + GdkDrawable *drawable, + int x, int y, int width, int height, + gint day, gint event_num) +{ + EDayView *day_view; + EDayViewEvent *event; + gint item_x, item_y, item_w, item_h, bar_y1, bar_y2; + GtkStyle *style; + GdkGC *gc; + iCalObject *ico; + gint num_icons, icon_x, icon_y, icon_x_inc, icon_y_inc; + gboolean draw_reminder_icon, draw_recurrence_icon; + + day_view = dvmitem->day_view; + + /* If the event is currently being dragged, don't draw it. It will + be drawn in the special drag items. */ + if (day_view->drag_event_day == day + && day_view->drag_event_num == event_num) + return; + + style = GTK_WIDGET (day_view)->style; + + gc = day_view->main_gc; + gdk_gc_set_foreground (gc, &day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR]); + + /* Get the position of the event. If it is not shown skip it.*/ + if (!e_day_view_get_event_position (day_view, day, event_num, + &item_x, &item_y, + &item_w, &item_h)) + return; + + item_x -= x; + item_y -= y; + + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + /* Fill in the white background. Note that for events in the first + column of the day, we might not want topaint over the vertical bar, + since that is used for multiple events. But then you can't see + where the event in the first column finishes. */ +#if 0 + if (event->start_row_or_col == 0) + gdk_draw_rectangle (drawable, style->white_gc, TRUE, + item_x + E_DAY_VIEW_BAR_WIDTH, item_y + 1, + item_w - E_DAY_VIEW_BAR_WIDTH - 1, + item_h - 2); + else +#endif + gdk_draw_rectangle (drawable, style->white_gc, TRUE, + item_x + 1, item_y + 1, + item_w - 2, item_h - 2); + + /* Draw the right edge of the vertical bar. */ + gdk_draw_line (drawable, style->black_gc, + item_x + E_DAY_VIEW_BAR_WIDTH - 1, + item_y + 1, + item_x + E_DAY_VIEW_BAR_WIDTH - 1, + item_y + item_h - 2); + + /* Draw the vertical colored bar showing when the appointment + begins & ends. */ + bar_y1 = event->start_minute * day_view->row_height / day_view->mins_per_row - y; + bar_y2 = event->end_minute * day_view->row_height / day_view->mins_per_row - y; + + /* When an item is being resized, we fill the bar up to the new row. */ + if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE + && day_view->resize_event_day == day + && day_view->resize_event_num == event_num) { + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_TOP_EDGE) + bar_y1 = item_y + 1; + else if (day_view->resize_drag_pos == E_DAY_VIEW_POS_BOTTOM_EDGE) + bar_y2 = item_y + item_h - 1; + } + + gdk_draw_rectangle (drawable, gc, TRUE, + item_x + 1, bar_y1, + E_DAY_VIEW_BAR_WIDTH - 2, bar_y2 - bar_y1); + + /* Draw the box around the entire event. Do this after drawing + the colored bar so we don't have to worry about being 1 + pixel out. */ + gdk_draw_rectangle (drawable, style->black_gc, FALSE, + item_x, item_y, item_w - 1, item_h - 1); + +#if 0 + /* Draw the horizontal bars above and beneath the event if it + is currently being edited. */ + if (day_view->editing_event_day == day + && day_view->editing_event_num == event_num) { + gdk_draw_rectangle (drawable, gc, TRUE, + item_x, + item_y - E_DAY_VIEW_BAR_HEIGHT, + item_w, + E_DAY_VIEW_BAR_HEIGHT); + gdk_draw_rectangle (drawable, gc, TRUE, + item_x, item_y + item_h, + item_w, E_DAY_VIEW_BAR_HEIGHT); + } +#endif + + /* Draw the reminder & recurrence icons, if needed. */ + num_icons = 0; + draw_reminder_icon = FALSE; + draw_recurrence_icon = FALSE; + icon_x = item_x + E_DAY_VIEW_BAR_WIDTH + E_DAY_VIEW_ICON_X_PAD; + icon_y = item_y + E_DAY_VIEW_EVENT_BORDER_HEIGHT + + E_DAY_VIEW_ICON_Y_PAD; + ico = event->ico; + + if (ico->dalarm.enabled || ico->malarm.enabled + || ico->palarm.enabled || ico->aalarm.enabled) { + draw_reminder_icon = TRUE; + num_icons++; + } + + if (ico->recur) { + draw_recurrence_icon = TRUE; + num_icons++; + } + + if (num_icons != 0) { + if (item_h >= (E_DAY_VIEW_ICON_HEIGHT + E_DAY_VIEW_ICON_Y_PAD) + * num_icons) { + icon_x_inc = 0; + icon_y_inc = E_DAY_VIEW_ICON_HEIGHT + + E_DAY_VIEW_ICON_Y_PAD; + } else { + icon_x_inc = E_DAY_VIEW_ICON_WIDTH + + E_DAY_VIEW_ICON_X_PAD; + icon_y_inc = 0; + } + + if (draw_reminder_icon) { + gdk_gc_set_clip_origin (gc, icon_x, icon_y); + gdk_gc_set_clip_mask (gc, day_view->reminder_mask); + gdk_draw_pixmap (drawable, gc, + day_view->reminder_icon, + 0, 0, icon_x, icon_y, + E_DAY_VIEW_ICON_WIDTH, + E_DAY_VIEW_ICON_HEIGHT); + icon_x += icon_x_inc; + icon_y += icon_y_inc; + } + + if (draw_recurrence_icon) { + gdk_gc_set_clip_origin (gc, icon_x, icon_y); + gdk_gc_set_clip_mask (gc, day_view->recurrence_mask); + gdk_draw_pixmap (drawable, gc, + day_view->recurrence_icon, + 0, 0, icon_x, icon_y, + E_DAY_VIEW_ICON_WIDTH, + E_DAY_VIEW_ICON_HEIGHT); + } + gdk_gc_set_clip_mask (gc, NULL); + } +} + + +/* 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_day_view_main_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_main_item_event (GnomeCanvasItem *item, GdkEvent *event) +{ + EDayViewMainItem *dvtitem; + + dvtitem = E_DAY_VIEW_MAIN_ITEM (item); + + switch (event->type) { + case GDK_BUTTON_PRESS: + + case GDK_BUTTON_RELEASE: + + case GDK_MOTION_NOTIFY: + + default: + break; + } + + return FALSE; +} + + diff --git a/calendar/gui/e-day-view-main-item.h b/calendar/gui/e-day-view-main-item.h new file mode 100644 index 0000000000..d8305e594d --- /dev/null +++ b/calendar/gui/e-day-view-main-item.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * 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 + */ +#ifndef _E_DAY_VIEW_MAIN_ITEM_H_ +#define _E_DAY_VIEW_MAIN_ITEM_H_ + +#include "e-day-view.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * EDayViewMainItem - canvas item which displays most of the appointment + * data in the main Day/Work Week display. + */ + +#define E_DAY_VIEW_MAIN_ITEM(obj) (GTK_CHECK_CAST((obj), \ + e_day_view_main_item_get_type (), EDayViewMainItem)) +#define E_DAY_VIEW_MAIN_ITEM_CLASS(k) (GTK_CHECK_CLASS_CAST ((k),\ + e_day_view_main_item_get_type ())) +#define E_IS_DAY_VIEW_MAIN_ITEM(o) (GTK_CHECK_TYPE((o), \ + e_day_view_main_item_get_type ())) + +typedef struct { + GnomeCanvasItem canvas_item; + + /* The parent EDayView widget. */ + EDayView *day_view; +} EDayViewMainItem; + +typedef struct { + GnomeCanvasItemClass parent_class; + +} EDayViewMainItemClass; + + +GtkType e_day_view_main_item_get_type (void); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _E_DAY_VIEW_MAIN_ITEM_H_ */ diff --git a/calendar/gui/e-day-view-time-item.c b/calendar/gui/e-day-view-time-item.c new file mode 100644 index 0000000000..aa86cc2ca8 --- /dev/null +++ b/calendar/gui/e-day-view-time-item.c @@ -0,0 +1,312 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * 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 + */ + +/* + * EDayViewTimeItem - canvas item which displays the times down the left of + * the EDayView. + */ + +#include "e-day-view-time-item.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). */ +#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 + + +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 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; +} + + +static void +e_day_view_time_item_init (EDayViewTimeItem *dvtmitm) +{ + +} + + +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; + + day_view = dvtmitem->day_view; + g_return_val_if_fail (day_view != NULL, 0); + + /* Calculate the width of each time column. */ + if (day_view->mins_per_row == 60) { + dvtmitem->column_width = day_view->max_small_hour_width + + day_view->colon_width + + day_view->max_minute_width + + E_DVTMI_60_MIN_X_PAD * 2 + + E_DVTMI_TIME_GRID_X_PAD * 2; + } else { + dvtmitem->column_width = day_view->max_large_hour_width + + day_view->max_minute_width + + E_DVTMI_MIN_X_PAD * 2 + + E_DVTMI_HOUR_L_PAD + + E_DVTMI_HOUR_R_PAD + + E_DVTMI_TIME_GRID_X_PAD * 2; + } + + 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; + gint time_hour_x1, time_hour_x2, time_min_x1; + gint hour, minute, hour_y, min_y, hour_r, min_r, start_y; + gint row, row_y, min_width, hour_width; + GtkStyle *style; + GdkFont *small_font, *large_font; + GdkGC *fg_gc, *dark_gc; + gchar buffer[16]; + + 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]; + + /* Step through each row, drawing the horizontal grid lines for each + day column and the times. */ + time_hour_x1 = E_DVTMI_TIME_GRID_X_PAD - x; + time_hour_x2 = dvtmitem->column_width - E_DVTMI_TIME_GRID_X_PAD - x; + if (day_view->mins_per_row == 60) { + min_r = time_hour_x2 - E_DVTMI_60_MIN_X_PAD; + } else { + time_min_x1 = time_hour_x2 - E_DVTMI_MIN_X_PAD * 2 + - day_view->max_minute_width; + hour_r = time_min_x1 - E_DVTMI_HOUR_R_PAD; + min_r = time_hour_x2 - E_DVTMI_MIN_X_PAD; + } + + hour = day_view->first_hour_shown; + hour_y = large_font->ascent + 2; /* FIXME */ + minute = day_view->first_minute_shown; + min_y = small_font->ascent + 2; /* FIXME */ + start_y = 0 - MAX (day_view->row_height, hour_y + large_font->descent); + for (row = 0, row_y = 0 - y; + row < day_view->rows && row_y < height; + row++, row_y += day_view->row_height) { + if (row_y > start_y) { + /* Draw the times down the left if needed. */ + if (min_r <= 0) + continue; + + if (day_view->mins_per_row == 60) { + gdk_draw_line (drawable, dark_gc, + time_hour_x1, row_y, + time_hour_x2, row_y); + sprintf (buffer, "%02i:%02i", hour, minute); + min_width = day_view->small_hour_widths[hour] + day_view->minute_widths[minute / 5] + day_view->colon_width; + gdk_draw_string (drawable, small_font, fg_gc, + min_r - min_width, + row_y + min_y, buffer); + } else { + if (minute == 0) { + gdk_draw_line (drawable, dark_gc, + time_hour_x1, row_y, + time_hour_x2, row_y); + sprintf (buffer, "%02i", hour); + hour_width = day_view->large_hour_widths[hour]; + gdk_draw_string (drawable, large_font, + fg_gc, + hour_r - hour_width, + row_y + hour_y, + buffer); + } else { + gdk_draw_line (drawable, dark_gc, + time_min_x1, row_y, + time_hour_x2, row_y); + } + + if (day_view->mins_per_row != 30 + || minute != 30) { + sprintf (buffer, "%02i", minute); + min_width = day_view->minute_widths[minute / 5]; + gdk_draw_string (drawable, small_font, + fg_gc, + min_r - min_width, + row_y + min_y, + buffer); + } + } + } + + minute += day_view->mins_per_row; + if (minute >= 60) { + hour++; + minute -= 60; + } + } +} + + +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; +} diff --git a/calendar/gui/e-day-view-time-item.h b/calendar/gui/e-day-view-time-item.h new file mode 100644 index 0000000000..9fec96d7dc --- /dev/null +++ b/calendar/gui/e-day-view-time-item.h @@ -0,0 +1,71 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * 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 + */ +#ifndef _E_DAY_VIEW_TIME_ITEM_H_ +#define _E_DAY_VIEW_TIME_ITEM_H_ + +#include "e-day-view.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * EDayViewTimeItem - canvas item which displays the times down the left of + * the EDayView. + */ + +#define E_DAY_VIEW_TIME_ITEM(obj) (GTK_CHECK_CAST((obj), \ + e_day_view_time_item_get_type (), EDayViewTimeItem)) +#define E_DAY_VIEW_TIME_ITEM_CLASS(k) (GTK_CHECK_CLASS_CAST ((k),\ + e_day_view_time_item_get_type ())) +#define E_IS_DAY_VIEW_TIME_ITEM(o) (GTK_CHECK_TYPE((o), \ + e_day_view_time_item_get_type ())) + +typedef struct { + GnomeCanvasItem canvas_item; + + /* The parent EDayView widget. */ + EDayView *day_view; + + /* The width of the time column. */ + gint column_width; +} EDayViewTimeItem; + +typedef struct { + GnomeCanvasItemClass parent_class; + +} EDayViewTimeItemClass; + + +GtkType e_day_view_time_item_get_type (void); + + +gint e_day_view_time_item_get_column_width (EDayViewTimeItem *dvtmitem); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _E_DAY_VIEW_TIME_ITEM_H_ */ diff --git a/calendar/gui/e-day-view-top-item.c b/calendar/gui/e-day-view-top-item.c new file mode 100644 index 0000000000..fe19c6b491 --- /dev/null +++ b/calendar/gui/e-day-view-top-item.c @@ -0,0 +1,553 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * 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 + */ + +/* + * EDayViewTopItem - displays the top part of the Day/Work Week calendar view. + */ + +#include "e-day-view-top-item.h" + +static void e_day_view_top_item_class_init (EDayViewTopItemClass *class); +static void e_day_view_top_item_init (EDayViewTopItem *dvtitem); + +static void e_day_view_top_item_set_arg (GtkObject *o, + GtkArg *arg, + guint arg_id); +static void e_day_view_top_item_update (GnomeCanvasItem *item, + double *affine, + ArtSVP *clip_path, + int flags); +static void e_day_view_top_item_draw (GnomeCanvasItem *item, + GdkDrawable *drawable, + int x, + int y, + int width, + int height); +static void e_day_view_top_item_draw_long_event (EDayViewTopItem *dvtitem, + gint event_num, + GdkDrawable *drawable, + int x, + int y, + int width, + int height); +static void e_day_view_top_item_draw_triangle (EDayViewTopItem *dvtitem, + GdkDrawable *drawable, + gint x, + gint y, + gint w, + gint h); +static double e_day_view_top_item_point (GnomeCanvasItem *item, + double x, + double y, + int cx, + int cy, + GnomeCanvasItem **actual_item); +static gint e_day_view_top_item_event (GnomeCanvasItem *item, + GdkEvent *event); + + +static GnomeCanvasItemClass *parent_class; + +/* The arguments we take */ +enum { + ARG_0, + ARG_DAY_VIEW +}; + + +GtkType +e_day_view_top_item_get_type (void) +{ + static GtkType e_day_view_top_item_type = 0; + + if (!e_day_view_top_item_type) { + GtkTypeInfo e_day_view_top_item_info = { + "EDayViewTopItem", + sizeof (EDayViewTopItem), + sizeof (EDayViewTopItemClass), + (GtkClassInitFunc) e_day_view_top_item_class_init, + (GtkObjectInitFunc) e_day_view_top_item_init, + NULL, /* reserved_1 */ + NULL, /* reserved_2 */ + (GtkClassInitFunc) NULL + }; + + e_day_view_top_item_type = gtk_type_unique (gnome_canvas_item_get_type (), &e_day_view_top_item_info); + } + + return e_day_view_top_item_type; +} + + +static void +e_day_view_top_item_class_init (EDayViewTopItemClass *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 ("EDayViewTopItem::day_view", + GTK_TYPE_POINTER, GTK_ARG_WRITABLE, + ARG_DAY_VIEW); + + object_class->set_arg = e_day_view_top_item_set_arg; + + /* GnomeCanvasItem method overrides */ + item_class->update = e_day_view_top_item_update; + item_class->draw = e_day_view_top_item_draw; + item_class->point = e_day_view_top_item_point; + item_class->event = e_day_view_top_item_event; +} + + +static void +e_day_view_top_item_init (EDayViewTopItem *dvtitem) +{ + dvtitem->day_view = NULL; +} + + +static void +e_day_view_top_item_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) +{ + GnomeCanvasItem *item; + EDayViewTopItem *dvtitem; + + item = GNOME_CANVAS_ITEM (o); + dvtitem = E_DAY_VIEW_TOP_ITEM (o); + + switch (arg_id){ + case ARG_DAY_VIEW: + dvtitem->day_view = GTK_VALUE_POINTER (*arg); + break; + } +} + + +static void +e_day_view_top_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; +} + + +/* + * DRAWING ROUTINES - functions to paint the canvas item. + */ + +static void +e_day_view_top_item_draw (GnomeCanvasItem *canvas_item, + GdkDrawable *drawable, + int x, + int y, + int width, + int height) +{ + EDayViewTopItem *dvtitem; + EDayView *day_view; + GtkStyle *style; + GdkGC *fg_gc, *bg_gc, *light_gc, *dark_gc; + gchar buffer[128]; + GdkRectangle clip_rect; + GdkFont *font; + gint canvas_width, canvas_height, left_edge, day, date_width, date_x; + gint item_height, event_num; + struct tm *day_start; + +#if 0 + g_print ("In e_day_view_top_item_draw %i,%i %ix%i\n", + x, y, width, height); +#endif + dvtitem = E_DAY_VIEW_TOP_ITEM (canvas_item); + day_view = dvtitem->day_view; + g_return_if_fail (day_view != NULL); + + style = GTK_WIDGET (day_view)->style; + font = style->font; + fg_gc = style->fg_gc[GTK_STATE_NORMAL]; + bg_gc = style->bg_gc[GTK_STATE_NORMAL]; + light_gc = style->light_gc[GTK_STATE_NORMAL]; + dark_gc = style->dark_gc[GTK_STATE_NORMAL]; + canvas_width = GTK_WIDGET (canvas_item->canvas)->allocation.width; + canvas_height = GTK_WIDGET (canvas_item->canvas)->allocation.height; + left_edge = 0; + item_height = day_view->top_row_height - E_DAY_VIEW_TOP_CANVAS_Y_GAP; + + /* Clear the entire background. */ + gdk_draw_rectangle (drawable, dark_gc, TRUE, + left_edge - x, 0, + canvas_width - left_edge, height); + + /* Draw the shadow around the dates. */ + gdk_draw_line (drawable, light_gc, + left_edge + 1 - x, 1 - y, + canvas_width - 2 - x, 1 - y); + gdk_draw_line (drawable, light_gc, + left_edge + 1 - x, 2 - y, + left_edge + 1 - x, item_height - 1 - y); + + /* Draw the background for the dates. */ + gdk_draw_rectangle (drawable, bg_gc, TRUE, + left_edge + 2 - x, 2 - y, + canvas_width - left_edge - 3, + item_height - 3); + + /* Draw the selection background. */ + if (day_view->selection_start_col != -1) { + gint start_col, end_col, rect_x, rect_y, rect_w, rect_h; + + start_col = day_view->selection_start_col; + end_col = day_view->selection_end_col; + + if (end_col > start_col + || day_view->selection_start_row == -1 + || day_view->selection_end_row == -1) { + rect_x = day_view->day_offsets[start_col]; + rect_y = item_height; + rect_w = day_view->day_offsets[end_col + 1] - rect_x; + rect_h = canvas_height - 1 - rect_y; + + gdk_draw_rectangle (drawable, style->white_gc, TRUE, + rect_x - x, rect_y - y, + rect_w, rect_h); + } + } + + /* Draw the date. Set a clipping rectangle so we don't draw over the + next day. */ + for (day = 0; day < day_view->days_shown; day++) { + day_start = localtime (&day_view->day_starts[day]); + + if (day_view->date_format == E_DAY_VIEW_DATE_FULL) + strftime (buffer, 128, "%d %B", day_start); + else if (day_view->date_format == E_DAY_VIEW_DATE_ABBREVIATED) + strftime (buffer, 128, "%d %b", day_start); + else + strftime (buffer, 128, "%d", day_start); + + clip_rect.x = day_view->day_offsets[day] - x; + clip_rect.y = 2 - y; + clip_rect.width = day_view->day_widths[day]; + clip_rect.height = item_height - 2; + gdk_gc_set_clip_rectangle (fg_gc, &clip_rect); + + date_width = gdk_string_width (font, buffer); + date_x = day_view->day_offsets[day] + (day_view->day_widths[day] - date_width) / 2; + gdk_draw_string (drawable, font, fg_gc, + date_x - x, 3 + font->ascent - y, buffer); + + gdk_gc_set_clip_rectangle (fg_gc, NULL); + + /* Draw the lines down the left and right of the date cols. */ + if (day != 0) { + gdk_draw_line (drawable, light_gc, + day_view->day_offsets[day] - x, + 4 - y, + day_view->day_offsets[day] - x, + item_height - 4 - y); + + gdk_draw_line (drawable, dark_gc, + day_view->day_offsets[day] - 1 - x, + 4 - y, + day_view->day_offsets[day] - 1 - x, + item_height - 4 - y); + } + + /* Draw the lines between each column. */ + if (day != 0) { + gdk_draw_line (drawable, style->black_gc, + day_view->day_offsets[day] - x, + item_height - y, + day_view->day_offsets[day] - x, + canvas_height - y); + } + } + + /* Draw the long events. */ + for (event_num = 0; event_num < day_view->long_events->len; + event_num++) { + e_day_view_top_item_draw_long_event (dvtitem, event_num, + drawable, + x, y, width, height); + } +} + + +/* This draws one event in the top canvas. */ +static void +e_day_view_top_item_draw_long_event (EDayViewTopItem *dvtitem, + gint event_num, + GdkDrawable *drawable, + int x, + int y, + int width, + int height) +{ + EDayView *day_view; + EDayViewEvent *event; + GtkStyle *style; + GdkGC *gc, *fg_gc, *bg_gc; + GdkFont *font; + gint start_day, end_day; + gint item_x, item_y, item_w, item_h; + gint text_x, icon_x, icon_y, icon_x_inc; + iCalObject *ico; + gchar buffer[16]; + gint hour, minute, offset, time_width; + gboolean draw_start_triangle, draw_end_triangle; + + day_view = dvtitem->day_view; + + /* If the event is currently being dragged, don't draw it. It will + be drawn in the special drag items. */ + if (day_view->drag_event_day == E_DAY_VIEW_LONG_EVENT + && day_view->drag_event_num == event_num) + return; + + if (!e_day_view_get_long_event_position (day_view, event_num, + &start_day, &end_day, + &item_x, &item_y, + &item_w, &item_h)) + return; + + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + style = GTK_WIDGET (day_view)->style; + font = style->font; + gc = day_view->main_gc; + fg_gc = style->fg_gc[GTK_STATE_NORMAL]; + bg_gc = style->bg_gc[GTK_STATE_NORMAL]; + ico = event->ico; + + /* Draw the lines across the top & bottom of the entire event. */ + gdk_draw_line (drawable, fg_gc, + item_x - x, item_y - y, + item_x + item_w - 1 - x, item_y - y); + gdk_draw_line (drawable, fg_gc, + item_x - x, item_y + item_h - 1 - y, + item_x + item_w - 1 - x, item_y + item_h - 1 - y); + + /* Fill it in. */ + gdk_draw_rectangle (drawable, bg_gc, TRUE, + item_x - x, item_y + 1 - y, + item_w, item_h - 2); + + /* When resizing we don't draw the triangles.*/ + draw_start_triangle = TRUE; + draw_end_triangle = TRUE; + if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE + && day_view->resize_event_day == E_DAY_VIEW_LONG_EVENT + && day_view->resize_event_num == event_num) { + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_LEFT_EDGE) + draw_start_triangle = FALSE; + + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_RIGHT_EDGE) + draw_end_triangle = FALSE; + } + + /* If the event starts before the first day shown, draw a triangle, + else just draw a vertical line down the left. */ + if (draw_start_triangle + && event->start < day_view->day_starts[start_day]) { + e_day_view_top_item_draw_triangle (dvtitem, drawable, + item_x - x, item_y - y, + -E_DAY_VIEW_BAR_WIDTH, + item_h); + } else { + gdk_draw_line (drawable, fg_gc, + item_x - x, item_y - y, + item_x - x, item_y + item_h - 1 - y); + } + + /* Similar for the event end. */ + if (draw_end_triangle + && event->end > day_view->day_starts[end_day + 1]) { + e_day_view_top_item_draw_triangle (dvtitem, drawable, + item_x + item_w - 1 - x, + item_y - y, + E_DAY_VIEW_BAR_WIDTH, + item_h); + } else { + gdk_draw_line (drawable, fg_gc, + item_x + item_w - 1 - x, + item_y - y, + item_x + item_w - 1 - x, + item_y + item_h - 1 - y); + } + + /* If we are editing the event we don't show the icons or the start + & end times. */ + if (day_view->editing_event_day == E_DAY_VIEW_LONG_EVENT + && day_view->editing_event_num == event_num) + return; + + /* Determine the position of the label, so we know where to place the + icons. Note that since the top canvas never scrolls we don't need + to take the scroll offset into account. It will always be 0. */ + text_x = event->canvas_item->x1; + + /* Draw the icons. */ + icon_x_inc = E_DAY_VIEW_ICON_WIDTH + E_DAY_VIEW_ICON_X_PAD; + icon_x = text_x - icon_x_inc - x; + icon_y = item_y + 1 + E_DAY_VIEW_ICON_Y_PAD - y; + + if (ico->recur) { + gdk_gc_set_clip_origin (gc, icon_x, icon_y); + gdk_gc_set_clip_mask (gc, day_view->recurrence_mask); + gdk_draw_pixmap (drawable, gc, + day_view->recurrence_icon, + 0, 0, icon_x, icon_y, + E_DAY_VIEW_ICON_WIDTH, + E_DAY_VIEW_ICON_HEIGHT); + icon_x -= icon_x_inc; + } + + if (ico->dalarm.enabled || ico->malarm.enabled + || ico->palarm.enabled || ico->aalarm.enabled) { + gdk_gc_set_clip_origin (gc, icon_x, icon_y); + gdk_gc_set_clip_mask (gc, day_view->reminder_mask); + gdk_draw_pixmap (drawable, gc, + day_view->reminder_icon, + 0, 0, icon_x, icon_y, + E_DAY_VIEW_ICON_WIDTH, + E_DAY_VIEW_ICON_HEIGHT); + icon_x -= icon_x_inc; + } + gdk_gc_set_clip_mask (gc, NULL); + + /* Draw the start & end times, if necessary. + Note that GtkLabel adds 1 to the ascent so we must do that to be + level with it. */ + if (event->start > day_view->day_starts[start_day]) { + offset = day_view->first_hour_shown * 60 + + day_view->first_minute_shown + event->start_minute; + hour = offset / 60; + minute = offset % 60; + sprintf (buffer, "%02i:%02i", hour, minute); + gdk_draw_string (drawable, font, fg_gc, + item_x + + E_DAY_VIEW_LONG_EVENT_X_PAD - x, + item_y + E_DAY_VIEW_LONG_EVENT_Y_PAD + font->ascent + 1 - y, + buffer); + } + + if (event->end < day_view->day_starts[end_day + 1]) { + offset = day_view->first_hour_shown * 60 + + day_view->first_minute_shown + event->end_minute; + hour = offset / 60; + minute = offset % 60; + sprintf (buffer, "%02i:%02i", hour, minute); + time_width = day_view->small_hour_widths[hour] + + day_view->max_minute_width + day_view->colon_width; + gdk_draw_string (drawable, font, fg_gc, + item_x + item_w - E_DAY_VIEW_LONG_EVENT_X_PAD - time_width - E_DAY_VIEW_LONG_EVENT_TIME_X_PAD - x, + item_y + E_DAY_VIEW_LONG_EVENT_Y_PAD + font->ascent + 1 - y, + buffer); + } +} + + +/* This draws a little triangle to indicate that an event extends past + the days visible on screen. */ +static void +e_day_view_top_item_draw_triangle (EDayViewTopItem *dvtitem, + GdkDrawable *drawable, + gint x, + gint y, + gint w, + gint h) +{ + EDayView *day_view; + GtkStyle *style; + GdkGC *fg_gc, *bg_gc; + GdkPoint points[3]; + + day_view = dvtitem->day_view; + + style = GTK_WIDGET (day_view)->style; + fg_gc = style->fg_gc[GTK_STATE_NORMAL]; + bg_gc = style->bg_gc[GTK_STATE_NORMAL]; + + points[0].x = x; + points[0].y = y; + points[1].x = x + w; + points[1].y = y + (h / 2) - 1; + points[2].x = x; + points[2].y = y + h - 1; + + gdk_draw_polygon (drawable, bg_gc, TRUE, points, 3); + gdk_draw_line (drawable, fg_gc, x, y, x + w, y + (h / 2) - 1); + gdk_draw_line (drawable, fg_gc, x, y + h - 1, x + w, y + h - (h / 2)); +} + + +/* 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_day_view_top_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_top_item_event (GnomeCanvasItem *item, GdkEvent *event) +{ + EDayViewTopItem *dvtitem; + + dvtitem = E_DAY_VIEW_TOP_ITEM (item); + + switch (event->type) { + case GDK_BUTTON_PRESS: + + case GDK_BUTTON_RELEASE: + + case GDK_MOTION_NOTIFY: + + default: + break; + } + + return FALSE; +} + + diff --git a/calendar/gui/e-day-view-top-item.h b/calendar/gui/e-day-view-top-item.h new file mode 100644 index 0000000000..435ef12b58 --- /dev/null +++ b/calendar/gui/e-day-view-top-item.h @@ -0,0 +1,65 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * 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 + */ +#ifndef _E_DAY_VIEW_TOP_ITEM_H_ +#define _E_DAY_VIEW_TOP_ITEM_H_ + +#include "e-day-view.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * EDayViewTopItem - displays the top part of the Day/Work Week calendar view. + */ + +#define E_DAY_VIEW_TOP_ITEM(obj) (GTK_CHECK_CAST((obj), \ + e_day_view_top_item_get_type (), EDayViewTopItem)) +#define E_DAY_VIEW_TOP_ITEM_CLASS(k) (GTK_CHECK_CLASS_CAST ((k),\ + e_day_view_top_item_get_type ())) +#define E_IS_DAY_VIEW_TOP_ITEM(o) (GTK_CHECK_TYPE((o), \ + e_day_view_top_item_get_type ())) + +typedef struct { + GnomeCanvasItem canvas_item; + + /* The parent EDayView widget. */ + EDayView *day_view; +} EDayViewTopItem; + +typedef struct { + GnomeCanvasItemClass parent_class; + +} EDayViewTopItemClass; + + +GtkType e_day_view_top_item_get_type (void); + + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _E_DAY_VIEW_TOP_ITEM_H_ */ diff --git a/calendar/gui/e-day-view.c b/calendar/gui/e-day-view.c new file mode 100644 index 0000000000..8b8e272faf --- /dev/null +++ b/calendar/gui/e-day-view.c @@ -0,0 +1,4585 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * 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 + */ + +/* + * EDayView - displays the Day & Work-Week views of the calendar. + */ + +#include <math.h> +#include <time.h> +#include <gnome.h> +#include <gdk/gdkx.h> + +#include "e-day-view.h" +#include "e-day-view-time-item.h" +#include "e-day-view-top-item.h" +#include "e-day-view-main-item.h" +#include "main.h" +#include "timeutil.h" +#include "popup-menu.h" +#include "eventedit.h" +#include "../e-util/e-canvas.h" +#include "../widgets/e-text/e-text.h" + +/* Images */ +#include "bell.xpm" +#include "recur.xpm" + +/* The minimum amount of space wanted on each side of the date string. */ +#define E_DAY_VIEW_DATE_X_PAD 4 + +#define E_DAY_VIEW_LARGE_FONT \ + "-adobe-utopia-regular-r-normal-*-*-240-*-*-p-*-iso8859-*" +#define E_DAY_VIEW_LARGE_FONT_FALLBACK \ + "-adobe-helvetica-bold-r-normal-*-*-240-*-*-p-*-iso8859-*" + +/* The offset from the top/bottom of the canvas before auto-scrolling starts.*/ +#define E_DAY_VIEW_AUTO_SCROLL_OFFSET 16 + +/* The time between each auto-scroll, in milliseconds. */ +#define E_DAY_VIEW_AUTO_SCROLL_TIMEOUT 50 + +/* The number of timeouts we skip before we start scrolling. */ +#define E_DAY_VIEW_AUTO_SCROLL_DELAY 5 + +/* The number of pixels the mouse has to be moved with the button down before + we start a drag. */ +#define E_DAY_VIEW_DRAG_START_OFFSET 4 + +/* Drag and Drop stuff. */ +enum { + TARGET_CALENDAR_EVENT +}; +static GtkTargetEntry target_table[] = { + { "application/x-e-calendar-event", 0, TARGET_CALENDAR_EVENT } +}; +static guint n_targets = sizeof(target_table) / sizeof(target_table[0]); + +static void e_day_view_class_init (EDayViewClass *class); +static void e_day_view_init (EDayView *day_view); +static void e_day_view_destroy (GtkObject *object); +static void e_day_view_realize (GtkWidget *widget); +static void e_day_view_unrealize (GtkWidget *widget); +static void e_day_view_style_set (GtkWidget *widget, + GtkStyle *previous_style); +static void e_day_view_size_allocate (GtkWidget *widget, + GtkAllocation *allocation); +static gint e_day_view_focus_in (GtkWidget *widget, + GdkEventFocus *event); +static gint e_day_view_focus_out (GtkWidget *widget, + GdkEventFocus *event); +static gint e_day_view_key_press (GtkWidget *widget, + GdkEventKey *event); + +static void e_day_view_on_canvas_realized (GtkWidget *widget, + EDayView *day_view); + +static gboolean e_day_view_on_top_canvas_button_press (GtkWidget *widget, + GdkEventButton *event, + EDayView *day_view); +static gboolean e_day_view_on_top_canvas_button_release (GtkWidget *widget, + GdkEventButton *event, + EDayView *day_view); +static gboolean e_day_view_on_top_canvas_motion (GtkWidget *widget, + GdkEventMotion *event, + EDayView *day_view); + +static gboolean e_day_view_on_main_canvas_button_press (GtkWidget *widget, + GdkEventButton *event, + EDayView *day_view); +static gboolean e_day_view_on_main_canvas_button_release (GtkWidget *widget, + GdkEventButton *event, + EDayView *day_view); +static gboolean e_day_view_on_main_canvas_motion (GtkWidget *widget, + GdkEventMotion *event, + EDayView *day_view); +static gboolean e_day_view_convert_event_coords (EDayView *day_view, + GdkEvent *event, + GdkWindow *window, + gint *x_return, + gint *y_return); +static void e_day_view_update_selection (EDayView *day_view, + gint row, + gint col); +static void e_day_view_update_long_event_resize (EDayView *day_view, + gint day); +static void e_day_view_update_resize (EDayView *day_view, + gint row); +static void e_day_view_finish_long_event_resize (EDayView *day_view); +static void e_day_view_finish_resize (EDayView *day_view); +static void e_day_view_abort_resize (EDayView *day_view, + guint32 time); + + +static gboolean e_day_view_on_long_event_button_press (EDayView *day_view, + gint event_num, + GdkEventButton *event, + EDayViewPosition pos, + gint event_x, + gint event_y); +static gboolean e_day_view_on_event_button_press (EDayView *day_view, + gint day, + gint event_num, + GdkEventButton *event, + EDayViewPosition pos, + gint event_x, + gint event_y); +static void e_day_view_on_long_event_click (EDayView *day_view, + gint event_num, + GdkEventButton *bevent, + EDayViewPosition pos, + gint event_x, + gint event_y); +static void e_day_view_on_event_click (EDayView *day_view, + gint day, + gint event_num, + GdkEventButton *event, + EDayViewPosition pos, + gint event_x, + gint event_y); +static void e_day_view_on_event_double_click (EDayView *day_view, + gint day, + gint event_num); +static void e_day_view_on_event_right_click (EDayView *day_view, + GdkEventButton *bevent, + gint day, + gint event_num); + +static void e_day_view_recalc_num_rows (EDayView *day_view); + +static EDayViewPosition e_day_view_convert_position_in_top_canvas (EDayView *day_view, + gint x, + gint y, + gint *day_return, + gint *event_num_return); +static EDayViewPosition e_day_view_convert_position_in_main_canvas (EDayView *day_view, + gint x, + gint y, + gint *day_return, + gint *row_return, + gint *event_num_return); +static gboolean e_day_view_find_event_from_item (EDayView *day_view, + GnomeCanvasItem *item, + gint *day_return, + gint *event_num_return); +static gboolean e_day_view_find_event_from_ico (EDayView *day_view, + iCalObject *ico, + gint *day_return, + gint *event_num_return); + +static void e_day_view_reload_events (EDayView *day_view); +static void e_day_view_free_events (EDayView *day_view); +static void e_day_view_free_event_array (EDayView *day_view, + GArray *array); +static int e_day_view_add_event (iCalObject *ico, + time_t start, + time_t end, + gpointer data); +static void e_day_view_update_event_label (EDayView *day_view, + gint day, + gint event_num); +static void e_day_view_update_long_event_label (EDayView *day_view, + gint event_num); + +static void e_day_view_layout_long_events (EDayView *day_view); +static void e_day_view_layout_long_event (EDayView *day_view, + EDayViewEvent *event, + guint8 *grid); +static void e_day_view_reshape_long_events (EDayView *day_view); +static void e_day_view_reshape_long_event (EDayView *day_view, + gint event_num); +static void e_day_view_layout_day_events (EDayView *day_view, + gint day); +static void e_day_view_layout_day_event (EDayView *day_view, + gint day, + EDayViewEvent *event, + guint8 *grid, + guint16 *group_starts); +static void e_day_view_expand_day_event (EDayView *day_view, + gint day, + EDayViewEvent *event, + guint8 *grid); +static void e_day_view_recalc_cols_per_row (EDayView *day_view, + gint day, + guint16 *group_starts); +static void e_day_view_reshape_day_events (EDayView *day_view, + gint day); +static void e_day_view_reshape_day_event (EDayView *day_view, + gint day, + gint event_num); +static void e_day_view_reshape_main_canvas_resize_bars (EDayView *day_view); +static void e_day_view_reshape_resize_long_event_rect_item (EDayView *day_view); +static void e_day_view_reshape_resize_rect_item (EDayView *day_view); + +static void e_day_view_ensure_events_sorted (EDayView *day_view); +static gint e_day_view_event_sort_func (const void *arg1, + const void *arg2); + +static void e_day_view_start_editing_event (EDayView *day_view, + gint day, + gint event_num, + gchar *initial_text); +static void e_day_view_stop_editing_event (EDayView *day_view); +static gboolean e_day_view_on_text_item_event (GnomeCanvasItem *item, + GdkEvent *event, + EDayView *day_view); +static void e_day_view_on_editing_started (EDayView *day_view, + GnomeCanvasItem *item); +static void e_day_view_on_editing_stopped (EDayView *day_view, + GnomeCanvasItem *item); + +static void e_day_view_get_selection_range (EDayView *day_view, + time_t *start, + time_t *end); +static time_t e_day_view_convert_grid_position_to_time (EDayView *day_view, + gint col, + gint row); + +static void e_day_view_check_auto_scroll (EDayView *day_view, + gint event_y); +static void e_day_view_start_auto_scroll (EDayView *day_view, + gboolean scroll_up); +static void e_day_view_stop_auto_scroll (EDayView *day_view); +static gboolean e_day_view_auto_scroll_handler (gpointer data); + +static void e_day_view_on_new_appointment (GtkWidget *widget, + gpointer data); +static void e_day_view_on_edit_appointment (GtkWidget *widget, + gpointer data); +static void e_day_view_on_delete_occurance (GtkWidget *widget, + gpointer data); +static void e_day_view_on_delete_appointment (GtkWidget *widget, + gpointer data); +static void e_day_view_on_unrecur_appointment (GtkWidget *widget, + gpointer data); +static EDayViewEvent* e_day_view_get_popup_menu_event (EDayView *day_view); + +static gint e_day_view_on_top_canvas_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + EDayView *day_view); +static void e_day_view_update_top_canvas_drag (EDayView *day_view, + gint day); +static void e_day_view_reshape_top_canvas_drag_item (EDayView *day_view); +static gint e_day_view_on_main_canvas_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + EDayView *day_view); +static void e_day_view_reshape_main_canvas_drag_item (EDayView *day_view); +static void e_day_view_update_main_canvas_drag (EDayView *day_view, + gint row, + gint day); +static void e_day_view_on_top_canvas_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + EDayView *day_view); +static void e_day_view_on_main_canvas_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + EDayView *day_view); +static void e_day_view_on_drag_begin (GtkWidget *widget, + GdkDragContext *context, + EDayView *day_view); +static void e_day_view_on_drag_end (GtkWidget *widget, + GdkDragContext *context, + EDayView *day_view); +static void e_day_view_on_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time, + EDayView *day_view); +static void e_day_view_on_top_canvas_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + EDayView *day_view); +static void e_day_view_on_main_canvas_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + EDayView *day_view); + + +static GtkTableClass *parent_class; + + +GtkType +e_day_view_get_type (void) +{ + static GtkType e_day_view_type = 0; + + if (!e_day_view_type){ + GtkTypeInfo e_day_view_info = { + "EDayView", + sizeof (EDayView), + sizeof (EDayViewClass), + (GtkClassInitFunc) e_day_view_class_init, + (GtkObjectInitFunc) e_day_view_init, + NULL, /* reserved 1 */ + NULL, /* reserved 2 */ + (GtkClassInitFunc) NULL + }; + + parent_class = gtk_type_class (GTK_TYPE_TABLE); + e_day_view_type = gtk_type_unique (GTK_TYPE_TABLE, + &e_day_view_info); + } + + return e_day_view_type; +} + + +static void +e_day_view_class_init (EDayViewClass *class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = (GtkObjectClass *) class; + widget_class = (GtkWidgetClass *) class; + + /* Method override */ + object_class->destroy = e_day_view_destroy; + + widget_class->realize = e_day_view_realize; + widget_class->unrealize = e_day_view_unrealize; + widget_class->style_set = e_day_view_style_set; + widget_class->size_allocate = e_day_view_size_allocate; + widget_class->focus_in_event = e_day_view_focus_in; + widget_class->focus_out_event = e_day_view_focus_out; + widget_class->key_press_event = e_day_view_key_press; +} + + +static void +e_day_view_init (EDayView *day_view) +{ + GdkColormap *colormap; + gboolean success[E_DAY_VIEW_COLOR_LAST]; + gint day, nfailed; + GnomeCanvasGroup *canvas_group; + + GTK_WIDGET_SET_FLAGS (day_view, GTK_CAN_FOCUS); + + colormap = gtk_widget_get_colormap (GTK_WIDGET (day_view)); + + day_view->calendar = NULL; + + day_view->long_events = g_array_new (FALSE, FALSE, + sizeof (EDayViewEvent)); + day_view->long_events_sorted = TRUE; + day_view->long_events_need_layout = FALSE; + day_view->long_events_need_reshape = FALSE; + + for (day = 0; day < E_DAY_VIEW_MAX_DAYS; day++) { + day_view->events[day] = g_array_new (FALSE, FALSE, + sizeof (EDayViewEvent)); + day_view->events_sorted[day] = TRUE; + day_view->need_layout[day] = FALSE; + day_view->need_reshape[day] = FALSE; + } + + /* FIXME: Initialize lower, upper, day_starts. */ + day_view->days_shown = 1; + + day_view->mins_per_row = 30; + day_view->date_format = E_DAY_VIEW_DATE_FULL; + day_view->rows_in_top_display = 0; + day_view->first_hour_shown = 0; + day_view->first_minute_shown = 0; + day_view->last_hour_shown = 24; + day_view->last_minute_shown = 0; + day_view->main_gc = NULL; + e_day_view_recalc_num_rows (day_view); + + day_view->work_day_start_hour = 9; + day_view->work_day_start_minute = 0; + day_view->work_day_end_hour = 17; + day_view->work_day_end_minute = 0; + day_view->scroll_to_work_day = TRUE; + + day_view->editing_event_day = -1; + day_view->editing_event_num = -1; + + day_view->resize_bars_event_day = -1; + day_view->resize_bars_event_num = -1; + + day_view->selection_start_row = -1; + day_view->selection_start_col = -1; + day_view->selection_end_row = -1; + day_view->selection_end_col = -1; + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_NONE; + day_view->selection_in_top_canvas = FALSE; + + day_view->resize_drag_pos = E_DAY_VIEW_POS_NONE; + + day_view->pressed_event_day = -1; + + day_view->drag_event_day = -1; + day_view->drag_last_day = -1; + + day_view->auto_scroll_timeout_id = 0; + + /* Create the large font. */ + day_view->large_font = gdk_font_load (E_DAY_VIEW_LARGE_FONT); + if (!day_view->large_font) + day_view->large_font = gdk_font_load (E_DAY_VIEW_LARGE_FONT_FALLBACK); + if (!day_view->large_font) + g_warning ("Couldn't load font"); + + + /* Allocate the colors. */ +#if 1 + day_view->colors[E_DAY_VIEW_COLOR_BG_WORKING].red = 255 * 257; + day_view->colors[E_DAY_VIEW_COLOR_BG_WORKING].green = 255 * 257; + day_view->colors[E_DAY_VIEW_COLOR_BG_WORKING].blue = 131 * 257; + + day_view->colors[E_DAY_VIEW_COLOR_BG_NOT_WORKING].red = 211 * 257; + day_view->colors[E_DAY_VIEW_COLOR_BG_NOT_WORKING].green = 208 * 257; + day_view->colors[E_DAY_VIEW_COLOR_BG_NOT_WORKING].blue = 6 * 257; +#else + + /* FG: MistyRose1, LightPink3 | RosyBrown | MistyRose3. */ + + day_view->colors[E_DAY_VIEW_COLOR_BG_WORKING].red = 255 * 257; + day_view->colors[E_DAY_VIEW_COLOR_BG_WORKING].green = 228 * 257; + day_view->colors[E_DAY_VIEW_COLOR_BG_WORKING].blue = 225 * 257; + + day_view->colors[E_DAY_VIEW_COLOR_BG_NOT_WORKING].red = 238 * 257; + day_view->colors[E_DAY_VIEW_COLOR_BG_NOT_WORKING].green = 162 * 257; + day_view->colors[E_DAY_VIEW_COLOR_BG_NOT_WORKING].blue = 173 * 257; +#endif + + day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR].red = 0; + day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR].green = 0; + day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR].blue = 65535; + + day_view->colors[E_DAY_VIEW_COLOR_EVENT_BACKGROUND].red = 65535; + day_view->colors[E_DAY_VIEW_COLOR_EVENT_BACKGROUND].green = 65535; + day_view->colors[E_DAY_VIEW_COLOR_EVENT_BACKGROUND].blue = 65535; + + day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER].red = 0; + day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER].green = 0; + day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER].blue = 0; + + nfailed = gdk_colormap_alloc_colors (colormap, day_view->colors, + E_DAY_VIEW_COLOR_LAST, FALSE, + TRUE, success); + if (nfailed) + g_warning ("Failed to allocate all colors"); + + + + /* + * Top Canvas + */ + day_view->top_canvas = e_canvas_new (); + gtk_table_attach (GTK_TABLE (day_view), day_view->top_canvas, + 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (day_view->top_canvas); + gtk_signal_connect_after (GTK_OBJECT (day_view->top_canvas), "button_press_event", + GTK_SIGNAL_FUNC (e_day_view_on_top_canvas_button_press), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->top_canvas), "button_release_event", + GTK_SIGNAL_FUNC (e_day_view_on_top_canvas_button_release), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->top_canvas), "motion_notify_event", + GTK_SIGNAL_FUNC (e_day_view_on_top_canvas_motion), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->top_canvas), + "drag_motion", + GTK_SIGNAL_FUNC (e_day_view_on_top_canvas_drag_motion), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->top_canvas), + "drag_leave", + GTK_SIGNAL_FUNC (e_day_view_on_top_canvas_drag_leave), + day_view); + gtk_signal_connect (GTK_OBJECT (day_view->top_canvas), + "drag_begin", + GTK_SIGNAL_FUNC (e_day_view_on_drag_begin), + day_view); + gtk_signal_connect (GTK_OBJECT (day_view->top_canvas), + "drag_end", + GTK_SIGNAL_FUNC (e_day_view_on_drag_end), + day_view); + gtk_signal_connect (GTK_OBJECT (day_view->top_canvas), + "drag_data_get", + GTK_SIGNAL_FUNC (e_day_view_on_drag_data_get), + day_view); + gtk_signal_connect (GTK_OBJECT (day_view->top_canvas), + "drag_data_received", + GTK_SIGNAL_FUNC (e_day_view_on_top_canvas_drag_data_received), + day_view); + + canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (day_view->top_canvas)->root); + + day_view->top_canvas_item = + gnome_canvas_item_new (canvas_group, + e_day_view_top_item_get_type (), + "EDayViewTopItem::day_view", day_view, + NULL); + + day_view->resize_long_event_rect_item = + gnome_canvas_item_new (canvas_group, + gnome_canvas_rect_get_type(), + "fill_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BACKGROUND], + "outline_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER], + NULL); + gnome_canvas_item_hide (day_view->resize_long_event_rect_item); + + day_view->drag_long_event_rect_item = + gnome_canvas_item_new (canvas_group, + gnome_canvas_rect_get_type (), + "width_pixels", 1, + "fill_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BACKGROUND], + "outline_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER], + NULL); + gnome_canvas_item_hide (day_view->drag_long_event_rect_item); + + day_view->drag_long_event_item = + gnome_canvas_item_new (canvas_group, + e_text_get_type (), + "anchor", GTK_ANCHOR_NW, + "line_wrap", TRUE, + "clip", TRUE, + "max_lines", 1, + "editable", TRUE, + NULL); + gnome_canvas_item_hide (day_view->drag_long_event_item); + + /* + * Main Canvas + */ + day_view->main_canvas = e_canvas_new (); + gtk_table_attach (GTK_TABLE (day_view), day_view->main_canvas, + 1, 2, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (day_view->main_canvas); + gtk_signal_connect (GTK_OBJECT (day_view->main_canvas), "realize", + GTK_SIGNAL_FUNC (e_day_view_on_canvas_realized), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->main_canvas), + "button_press_event", + GTK_SIGNAL_FUNC (e_day_view_on_main_canvas_button_press), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->main_canvas), + "button_release_event", + GTK_SIGNAL_FUNC (e_day_view_on_main_canvas_button_release), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->main_canvas), + "motion_notify_event", + GTK_SIGNAL_FUNC (e_day_view_on_main_canvas_motion), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->main_canvas), + "drag_motion", + GTK_SIGNAL_FUNC (e_day_view_on_main_canvas_drag_motion), + day_view); + gtk_signal_connect_after (GTK_OBJECT (day_view->main_canvas), + "drag_leave", + GTK_SIGNAL_FUNC (e_day_view_on_main_canvas_drag_leave), + day_view); + gtk_signal_connect (GTK_OBJECT (day_view->main_canvas), + "drag_begin", + GTK_SIGNAL_FUNC (e_day_view_on_drag_begin), + day_view); + gtk_signal_connect (GTK_OBJECT (day_view->main_canvas), + "drag_end", + GTK_SIGNAL_FUNC (e_day_view_on_drag_end), + day_view); + gtk_signal_connect (GTK_OBJECT (day_view->main_canvas), + "drag_data_get", + GTK_SIGNAL_FUNC (e_day_view_on_drag_data_get), + day_view); + gtk_signal_connect (GTK_OBJECT (day_view->main_canvas), + "drag_data_received", + GTK_SIGNAL_FUNC (e_day_view_on_main_canvas_drag_data_received), + day_view); + + canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (day_view->main_canvas)->root); + + day_view->main_canvas_item = + gnome_canvas_item_new (canvas_group, + e_day_view_main_item_get_type (), + "EDayViewMainItem::day_view", day_view, + NULL); + + day_view->resize_rect_item = + gnome_canvas_item_new (canvas_group, + gnome_canvas_rect_get_type(), + "fill_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BACKGROUND], + "outline_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER], + NULL); + gnome_canvas_item_hide (day_view->resize_rect_item); + + day_view->resize_bar_item = + gnome_canvas_item_new (canvas_group, + gnome_canvas_rect_get_type(), + "fill_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR], + "outline_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER], + NULL); + gnome_canvas_item_hide (day_view->resize_bar_item); + + day_view->main_canvas_top_resize_bar_item = + gnome_canvas_item_new (canvas_group, + gnome_canvas_rect_get_type (), + "width_pixels", 1, + "fill_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR], + "outline_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER], + NULL); + gnome_canvas_item_hide (day_view->main_canvas_top_resize_bar_item); + + day_view->main_canvas_bottom_resize_bar_item = + gnome_canvas_item_new (canvas_group, + gnome_canvas_rect_get_type (), + "width_pixels", 1, + "fill_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR], + "outline_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER], + NULL); + gnome_canvas_item_hide (day_view->main_canvas_bottom_resize_bar_item); + + + day_view->drag_rect_item = + gnome_canvas_item_new (canvas_group, + gnome_canvas_rect_get_type (), + "width_pixels", 1, + "fill_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BACKGROUND], + "outline_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER], + NULL); + gnome_canvas_item_hide (day_view->drag_rect_item); + + day_view->drag_bar_item = + gnome_canvas_item_new (canvas_group, + gnome_canvas_rect_get_type (), + "width_pixels", 1, + "fill_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_VBAR], + "outline_color_gdk", &day_view->colors[E_DAY_VIEW_COLOR_EVENT_BORDER], + NULL); + gnome_canvas_item_hide (day_view->drag_bar_item); + + day_view->drag_item = + gnome_canvas_item_new (canvas_group, + e_text_get_type (), + "anchor", GTK_ANCHOR_NW, + "line_wrap", TRUE, + "clip", TRUE, + "editable", TRUE, + NULL); + gnome_canvas_item_hide (day_view->drag_item); + + + /* + * Times Canvas + */ + day_view->time_canvas = e_canvas_new (); + gtk_layout_set_vadjustment (GTK_LAYOUT (day_view->time_canvas), + GTK_LAYOUT (day_view->main_canvas)->vadjustment); + gtk_table_attach (GTK_TABLE (day_view), day_view->time_canvas, + 0, 1, 1, 2, + GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (day_view->time_canvas); + + canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (day_view->time_canvas)->root); + + day_view->time_canvas_item = + gnome_canvas_item_new (canvas_group, + e_day_view_time_item_get_type (), + "EDayViewTimeItem::day_view", day_view, + NULL); + + + /* + * Scrollbar. + */ + day_view->vscrollbar = gtk_vscrollbar_new (GTK_LAYOUT (day_view->main_canvas)->vadjustment); + gtk_table_attach (GTK_TABLE (day_view), day_view->vscrollbar, + 2, 3, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (day_view->vscrollbar); + + + /* Create the pixmaps. */ + day_view->reminder_icon = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &day_view->reminder_mask, NULL, bell_xpm); + day_view->recurrence_icon = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &day_view->recurrence_mask, NULL, recur_xpm); + + + /* Create the cursors. */ + day_view->normal_cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW); + day_view->move_cursor = gdk_cursor_new (GDK_FLEUR); + day_view->resize_width_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); + day_view->resize_height_cursor = gdk_cursor_new (GDK_SB_V_DOUBLE_ARROW); + day_view->last_cursor_set_in_top_canvas = NULL; + day_view->last_cursor_set_in_main_canvas = NULL; + + /* Set up the drop sites. */ + gtk_drag_dest_set (GTK_WIDGET (day_view->top_canvas), + GTK_DEST_DEFAULT_ALL, + target_table, n_targets, + GDK_ACTION_COPY | GDK_ACTION_MOVE); + gtk_drag_dest_set (GTK_WIDGET (day_view->main_canvas), + GTK_DEST_DEFAULT_ALL, + target_table, n_targets, + GDK_ACTION_COPY | GDK_ACTION_MOVE); +} + + +/* Turn off the background of the canvas windows. This reduces flicker + considerably when scrolling. (Why isn't it in GnomeCanvas?). */ +static void +e_day_view_on_canvas_realized (GtkWidget *widget, + EDayView *day_view) +{ + gdk_window_set_back_pixmap (GTK_LAYOUT (widget)->bin_window, + NULL, FALSE); +} + + +/** + * e_day_view_new: + * @Returns: a new #EDayView. + * + * Creates a new #EDayView. + **/ +GtkWidget * +e_day_view_new (void) +{ + GtkWidget *day_view; + + day_view = GTK_WIDGET (gtk_type_new (e_day_view_get_type ())); + + return day_view; +} + + +static void +e_day_view_destroy (GtkObject *object) +{ + EDayView *day_view; + gint day; + + day_view = E_DAY_VIEW (object); + + e_day_view_stop_auto_scroll (day_view); + + gdk_cursor_destroy (day_view->normal_cursor); + gdk_cursor_destroy (day_view->move_cursor); + gdk_cursor_destroy (day_view->resize_width_cursor); + gdk_cursor_destroy (day_view->resize_height_cursor); + + e_day_view_free_events (day_view); + g_array_free (day_view->long_events, TRUE); + for (day = 0; day < E_DAY_VIEW_MAX_DAYS; day++) + g_array_free (day_view->events[day], TRUE); + + GTK_OBJECT_CLASS (parent_class)->destroy (object); +} + + +static void +e_day_view_realize (GtkWidget *widget) +{ + EDayView *day_view; + + if (GTK_WIDGET_CLASS (parent_class)->realize) + (*GTK_WIDGET_CLASS (parent_class)->realize)(widget); + + day_view = E_DAY_VIEW (widget); + day_view->main_gc = gdk_gc_new (widget->window); +} + + +static void +e_day_view_unrealize (GtkWidget *widget) +{ + EDayView *day_view; + + day_view = E_DAY_VIEW (widget); + + gdk_gc_unref (day_view->main_gc); + day_view->main_gc = NULL; + + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + (*GTK_WIDGET_CLASS (parent_class)->unrealize)(widget); +} + + +static void +e_day_view_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + EDayView *day_view; + GdkFont *font; + gint top_rows, top_canvas_height; + gint month, max_month_width, max_abbr_month_width, number_width; + gint hour, max_large_hour_width; + gint minute, max_minute_width, i; + GDate date; + gchar buffer[128]; + gint times_width; + + if (GTK_WIDGET_CLASS (parent_class)->style_set) + (*GTK_WIDGET_CLASS (parent_class)->style_set)(widget, previous_style); + + day_view = E_DAY_VIEW (widget); + font = widget->style->font; + + /* Recalculate the height of each row based on the font size. */ + day_view->row_height = font->ascent + font->descent + E_DAY_VIEW_EVENT_BORDER_HEIGHT * 2 + E_DAY_VIEW_EVENT_Y_PAD * 2 + 2 /* FIXME */; + day_view->row_height = MAX (day_view->row_height, E_DAY_VIEW_ICON_HEIGHT + E_DAY_VIEW_ICON_Y_PAD + 2); + GTK_LAYOUT (day_view->main_canvas)->vadjustment->step_increment = day_view->row_height; + + day_view->top_row_height = font->ascent + font->descent + E_DAY_VIEW_LONG_EVENT_BORDER_HEIGHT * 2 + E_DAY_VIEW_LONG_EVENT_Y_PAD * 2 + E_DAY_VIEW_TOP_CANVAS_Y_GAP; + day_view->top_row_height = MAX (day_view->top_row_height, E_DAY_VIEW_ICON_HEIGHT + E_DAY_VIEW_ICON_Y_PAD + 2 + E_DAY_VIEW_TOP_CANVAS_Y_GAP); + + /* Set the height of the top canvas based on the row height and the + number of rows needed (min 1 + 1 for the dates + 1 space for DnD).*/ + top_rows = MAX (1, day_view->rows_in_top_display); + top_canvas_height = (top_rows + 2) * day_view->top_row_height; + gtk_widget_set_usize (day_view->top_canvas, -1, top_canvas_height); + + /* Find the biggest full month name. */ + g_date_clear (&date, 1); + g_date_set_dmy (&date, 20, 1, 2000); + max_month_width = 0; + for (month = 1; month <= 12; month++) { + g_date_set_month (&date, month); + + g_date_strftime (buffer, 128, "%B", &date); + max_month_width = MAX (max_month_width, + gdk_string_width (font, buffer)); + + g_date_strftime (buffer, 128, "%b", &date); + max_abbr_month_width = MAX (max_abbr_month_width, + gdk_string_width (font, buffer)); + } + number_width = gdk_string_width (font, "31 "); + day_view->long_format_width = number_width + max_month_width + + E_DAY_VIEW_DATE_X_PAD; + day_view->abbreviated_format_width = number_width + + max_abbr_month_width + E_DAY_VIEW_DATE_X_PAD; + + /* Calculate the widths of all the time strings necessary. */ + for (hour = 0; hour < 24; hour++) { + sprintf (buffer, "%02i", hour); + day_view->small_hour_widths[hour] = gdk_string_width (font, buffer); + day_view->large_hour_widths[hour] = gdk_string_width (day_view->large_font, buffer); + day_view->max_small_hour_width = MAX (day_view->max_small_hour_width, day_view->small_hour_widths[hour]); + max_large_hour_width = MAX (max_large_hour_width, day_view->large_hour_widths[hour]); + } + day_view->max_large_hour_width = max_large_hour_width; + + for (minute = 0, i = 0; minute < 60; minute += 5, i++) { + sprintf (buffer, "%02i", minute); + day_view->minute_widths[i] = gdk_string_width (font, buffer); + max_minute_width = MAX (max_minute_width, day_view->minute_widths[i]); + } + day_view->max_minute_width = max_minute_width; + day_view->colon_width = gdk_string_width (font, ":"); + + /* Calculate the width of the time column. */ + times_width = e_day_view_time_item_get_column_width (E_DAY_VIEW_TIME_ITEM (day_view->time_canvas_item)); + gtk_widget_set_usize (day_view->time_canvas, times_width, -1); +} + + +/* This recalculates the sizes of each column. */ +static void +e_day_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + EDayView *day_view; + gfloat width, offset; + gint col, day; + gdouble old_width, old_height, new_width, new_height; + gint scroll_y; + + g_print ("In e_day_view_size_allocate\n"); + + day_view = E_DAY_VIEW (widget); + + (*GTK_WIDGET_CLASS (parent_class)->size_allocate) (widget, allocation); + + /* Calculate the column sizes, using floating point so that pixels + get divided evenly. Note that we use one more element than the + number of columns, to make it easy to get the column widths. */ + width = day_view->main_canvas->allocation.width; + width /= day_view->days_shown; + offset = 0; + for (col = 0; col <= day_view->days_shown; col++) { + day_view->day_offsets[col] = floor (offset + 0.5); + offset += width; + } + + /* Calculate the days widths based on the offsets. */ + for (col = 0; col < day_view->days_shown; col++) { + day_view->day_widths[col] = day_view->day_offsets[col + 1] - day_view->day_offsets[col]; + } + + /* Determine which date format to use, based on the column widths. */ + if (day_view->day_widths[0] > day_view->long_format_width) + day_view->date_format = E_DAY_VIEW_DATE_FULL; + else if (day_view->day_widths[0] > day_view->abbreviated_format_width) + day_view->date_format = E_DAY_VIEW_DATE_ABBREVIATED; + else + day_view->date_format = E_DAY_VIEW_DATE_SHORT; + + /* Set the scroll region of the top canvas to its allocated size. */ + gnome_canvas_get_scroll_region (GNOME_CANVAS (day_view->top_canvas), + NULL, NULL, &old_width, &old_height); + new_width = day_view->top_canvas->allocation.width; + new_height = day_view->top_canvas->allocation.height; + if (old_width != new_width || old_height != new_height) + gnome_canvas_set_scroll_region (GNOME_CANVAS (day_view->top_canvas), + 0, 0, new_width, new_height); + + /* Set the scroll region of the time canvas to its allocated width, + but with the height the same as the main canvas. */ + gnome_canvas_get_scroll_region (GNOME_CANVAS (day_view->time_canvas), + NULL, NULL, &old_width, &old_height); + new_width = day_view->time_canvas->allocation.width; + new_height = MAX (day_view->rows * day_view->row_height, day_view->main_canvas->allocation.height); + if (old_width != new_width || old_height != new_height) + gnome_canvas_set_scroll_region (GNOME_CANVAS (day_view->time_canvas), + 0, 0, new_width, new_height); + + /* Set the scroll region of the main canvas to its allocated width, + but with the height depending on the number of rows needed. */ + gnome_canvas_get_scroll_region (GNOME_CANVAS (day_view->main_canvas), + NULL, NULL, &old_width, &old_height); + new_width = day_view->main_canvas->allocation.width; + if (old_width != new_width || old_height != new_height) + gnome_canvas_set_scroll_region (GNOME_CANVAS (day_view->main_canvas), + 0, 0, new_width, new_height); + + /* Scroll to the start of the working day, if this is the initial + allocation. */ + if (day_view->scroll_to_work_day) { + scroll_y = e_day_view_convert_time_to_position (day_view, day_view->work_day_start_hour, day_view->work_day_start_minute); + gnome_canvas_scroll_to (GNOME_CANVAS (day_view->main_canvas), + 0, scroll_y); + day_view->scroll_to_work_day = FALSE; + } + + /* Flag that we need to reshape the events. Note that changes in height + don't matter, since the rows are always the same height. */ + if (old_width != new_width) { + g_print ("Need reshape\n"); + + day_view->long_events_need_reshape = TRUE; + for (day = 0; day < E_DAY_VIEW_MAX_DAYS; day++) + day_view->need_reshape[day] = TRUE; + + e_day_view_check_layout (day_view); + } +} + + +static gint +e_day_view_focus_in (GtkWidget *widget, GdkEventFocus *event) +{ + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (E_IS_DAY_VIEW (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + g_print ("In e_day_view_focus_in\n"); + + GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS); + gtk_widget_draw_focus (widget); + + return FALSE; +} + + +static gint +e_day_view_focus_out (GtkWidget *widget, GdkEventFocus *event) +{ + EDayView *day_view; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (E_IS_DAY_VIEW (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + g_print ("In e_day_view_focus_out\n"); + + day_view = E_DAY_VIEW (widget); + + GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS); + + /* Get rid of selection. */ + day_view->selection_start_col = -1; + + gtk_widget_queue_draw (day_view->top_canvas); + gtk_widget_queue_draw (day_view->main_canvas); + + return FALSE; +} + + +void +e_day_view_set_calendar (EDayView *day_view, + GnomeCalendar *calendar) +{ + day_view->calendar = calendar; + + /* FIXME: free current events? */ +} + + +void +e_day_view_update_event (EDayView *day_view, + iCalObject *ico, + int flags) +{ + gint day, event_num; + + g_return_if_fail (E_IS_DAY_VIEW (day_view)); + + g_print ("In e_day_view_update_event\n"); + + /* We only care about events. */ + if (ico && ico->type != ICAL_EVENT) + return; + + /* If one non-recurring event was added, we can just add it. */ + if (flags == CHANGE_NEW && !ico->recur) { + if (ico->dtstart < day_view->upper + && ico->dtend > day_view->lower) { + e_day_view_add_event (ico, ico->dtstart, ico->dtend, + day_view); + e_day_view_check_layout (day_view); + + gtk_widget_queue_draw (day_view->top_canvas); + gtk_widget_queue_draw (day_view->main_canvas); + } + return; + + /* If only the summary changed, we can update that easily. */ + } else if (!(flags & ~CHANGE_SUMMARY)) { + if (e_day_view_find_event_from_ico (day_view, ico, + &day, &event_num)) { + if (day == E_DAY_VIEW_LONG_EVENT) { + e_day_view_update_long_event_label (day_view, + event_num); + /* For long events we also have to reshape it + as the text is centered. */ + e_day_view_reshape_long_event (day_view, + event_num); + } else { + e_day_view_update_event_label (day_view, day, + event_num); + } + + return; + } + } + + e_day_view_reload_events (day_view); +} + + +/* This updates the text shown for an event. If the event start or end do not + lie on a row boundary, the time is displayed before the summary. */ +static void +e_day_view_update_event_label (EDayView *day_view, + gint day, + gint event_num) +{ + EDayViewEvent *event; + gchar *text; + gboolean free_text; + gint offset, start_minute, end_minute; + + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + if (event->start_minute % day_view->mins_per_row != 0 + || event->end_minute % day_view->mins_per_row != 0) { + offset = day_view->first_hour_shown * 60 + + day_view->first_minute_shown; + start_minute = offset + event->start_minute; + end_minute = offset + event->end_minute; + text = g_strdup_printf ("%02i:%02i-%02i:%02i %s", + start_minute / 60, + start_minute % 60, + end_minute / 60, + end_minute % 60, + event->ico->summary); + free_text = TRUE; + } else { + text = event->ico->summary; + free_text = FALSE; + } + + gnome_canvas_item_set (event->canvas_item, + "text", event->ico->summary ? event->ico->summary : "", + NULL); + + if (free_text) + g_free (text); +} + + +static void +e_day_view_update_long_event_label (EDayView *day_view, + gint event_num) +{ + EDayViewEvent *event; + + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + gnome_canvas_item_set (event->canvas_item, + "text", event->ico->summary ? event->ico->summary : "", + NULL); +} + + +/* Finds the day and index of the event with the given canvas item. + If is is a long event, -1 is returned as the day. + Returns TRUE if the event was found. */ +static gboolean +e_day_view_find_event_from_item (EDayView *day_view, + GnomeCanvasItem *item, + gint *day_return, + gint *event_num_return) +{ + EDayViewEvent *event; + gint day, event_num; + + for (day = 0; day < day_view->days_shown; day++) { + for (event_num = 0; event_num < day_view->events[day]->len; + event_num++) { + event = &g_array_index (day_view->events[day], + EDayViewEvent, event_num); + if (event->canvas_item == item) { + *day_return = day; + *event_num_return = event_num; + return TRUE; + } + } + } + + for (event_num = 0; event_num < day_view->long_events->len; + event_num++) { + event = &g_array_index (day_view->long_events, + EDayViewEvent, event_num); + if (event->canvas_item == item) { + *day_return = E_DAY_VIEW_LONG_EVENT; + *event_num_return = event_num; + return TRUE; + } + } + + return FALSE; +} + + +/* Finds the day and index of the event containing the iCalObject. + If is is a long event, E_DAY_VIEW_LONG_EVENT is returned as the day. + Returns TRUE if the event was found. */ +static gboolean +e_day_view_find_event_from_ico (EDayView *day_view, + iCalObject *ico, + gint *day_return, + gint *event_num_return) +{ + EDayViewEvent *event; + gint day, event_num; + + for (day = 0; day < day_view->days_shown; day++) { + for (event_num = 0; event_num < day_view->events[day]->len; + event_num++) { + event = &g_array_index (day_view->events[day], + EDayViewEvent, event_num); + if (event->ico == ico) { + *day_return = day; + *event_num_return = event_num; + return TRUE; + } + } + } + + for (event_num = 0; event_num < day_view->long_events->len; + event_num++) { + event = &g_array_index (day_view->long_events, + EDayViewEvent, event_num); + if (event->ico == ico) { + *day_return = E_DAY_VIEW_LONG_EVENT; + *event_num_return = event_num; + return TRUE; + } + } + + return FALSE; +} + + +/* Note that the times must be the start and end of days. */ +void +e_day_view_set_interval (EDayView *day_view, + time_t lower, + time_t upper) +{ + time_t tmp_lower, day_starts[E_DAY_VIEW_MAX_DAYS + 1]; + gint day; + + g_return_if_fail (E_IS_DAY_VIEW (day_view)); + + g_print ("In e_day_view_set_interval\n"); + + if (lower == day_view->lower && upper == day_view->upper) + return; + + /* Check that the first time is the start of a day. */ + tmp_lower = time_day_begin (lower); + g_return_if_fail (lower == tmp_lower); + + /* Calculate the start of each day shown, and check that upper is + valid. */ + day_starts[0] = lower; + for (day = 1; day <= E_DAY_VIEW_MAX_DAYS; day++) { + day_starts[day] = time_add_day (day_starts[day - 1], 1); + /* Check if we have reached the upper time. */ + if (day_starts[day] == upper) { + day_view->days_shown = day; + break; + } + + /* Check that we haven't gone past the upper time. */ + g_return_if_fail (day_starts[day] < upper); + } + + /* Now that we know that lower & upper are valid, update the fields + in the EDayView. */ + day_view->lower = lower; + day_view->upper = upper; + + for (day = 0; day <= day_view->days_shown; day++) { + day_view->day_starts[day] = day_starts[day]; + } + + e_day_view_reload_events (day_view); +} + + +gint +e_day_view_get_days_shown (EDayView *day_view) +{ + g_return_val_if_fail (E_IS_DAY_VIEW (day_view), -1); + + return day_view->days_shown; +} + + +void +e_day_view_set_days_shown (EDayView *day_view, + gint days_shown) +{ + g_return_if_fail (E_IS_DAY_VIEW (day_view)); + g_return_if_fail (days_shown >= 1); + g_return_if_fail (days_shown <= E_DAY_VIEW_MAX_DAYS); + + if (day_view->days_shown != days_shown) { + day_view->days_shown = days_shown; + + /* FIXME: Update everything. */ + } +} + + +gint +e_day_view_get_mins_per_row (EDayView *day_view) +{ + g_return_val_if_fail (E_IS_DAY_VIEW (day_view), -1); + + return day_view->mins_per_row; +} + + +void +e_day_view_set_mins_per_row (EDayView *day_view, + gint mins_per_row) +{ + g_return_if_fail (E_IS_DAY_VIEW (day_view)); + + if (mins_per_row != 5 && mins_per_row != 10 && mins_per_row != 15 + && mins_per_row != 30 && mins_per_row != 60) { + g_warning ("Invalid minutes per row setting"); + return; + } + + if (day_view->mins_per_row != mins_per_row) { + day_view->mins_per_row = mins_per_row; + + /* FIXME: Update positions & display. */ + } +} + + +/* This recalculates the number of rows to display, based on the time range + shown and the minutes per row. */ +static void +e_day_view_recalc_num_rows (EDayView *day_view) +{ + gint hours, minutes, total_minutes; + + hours = day_view->last_hour_shown - day_view->first_hour_shown; + /* This could be negative but it works out OK. */ + minutes = day_view->last_minute_shown - day_view->first_minute_shown; + total_minutes = hours * 60 + minutes; + day_view->rows = total_minutes / day_view->mins_per_row; +} + + +/* Converts an hour and minute to a row in the canvas. */ +gint +e_day_view_convert_time_to_row (EDayView *day_view, + gint hour, + gint minute) +{ + gint total_minutes, start_minute, offset; + + total_minutes = hour * 60 + minute; + start_minute = day_view->first_hour_shown * 60 + + day_view->first_minute_shown; + offset = total_minutes - start_minute; + + return offset / day_view->mins_per_row; +} + + +/* Converts an hour and minute to a y coordinate in the canvas. */ +gint +e_day_view_convert_time_to_position (EDayView *day_view, + gint hour, + gint minute) +{ + gint total_minutes, start_minute, offset; + + total_minutes = hour * 60 + minute; + start_minute = day_view->first_hour_shown * 60 + + day_view->first_minute_shown; + offset = total_minutes - start_minute; + + return offset * day_view->row_height / day_view->mins_per_row; +} + + +static gboolean +e_day_view_on_top_canvas_button_press (GtkWidget *widget, + GdkEventButton *event, + EDayView *day_view) +{ + gint event_x, event_y, scroll_x, scroll_y, day, event_num; + EDayViewPosition pos; + + g_print ("In e_day_view_on_top_canvas_button_press\n"); + + /* Convert the coords to the main canvas window, or return if the + window is not found. */ + if (!e_day_view_convert_event_coords (day_view, (GdkEvent*) event, + GTK_LAYOUT (widget)->bin_window, + &event_x, &event_y)) + return FALSE; + + /* The top canvas doesn't scroll, but just in case. */ + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), + &scroll_x, &scroll_y); + event_x += scroll_x; + event_y += scroll_y; + + pos = e_day_view_convert_position_in_top_canvas (day_view, + event_x, event_y, + &day, &event_num); + + if (pos == E_DAY_VIEW_POS_OUTSIDE) + return FALSE; + + if (pos != E_DAY_VIEW_POS_NONE) + return e_day_view_on_long_event_button_press (day_view, + event_num, + event, pos, + event_x, + event_y); + + e_day_view_stop_editing_event (day_view); + + if (event->button == 1) { + if (!GTK_WIDGET_HAS_FOCUS (day_view)) + gtk_widget_grab_focus (GTK_WIDGET (day_view)); + + if (gdk_pointer_grab (GTK_LAYOUT (widget)->bin_window, FALSE, + GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK, + FALSE, NULL, event->time) == 0) { + day_view->selection_start_row = -1; + day_view->selection_start_col = day; + day_view->selection_end_row = -1; + day_view->selection_end_col = day; + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_END; + day_view->selection_in_top_canvas = TRUE; + + /* FIXME: Optimise? */ + gtk_widget_queue_draw (day_view->top_canvas); + gtk_widget_queue_draw (day_view->main_canvas); + } + } else if (event->button == 3) { + e_day_view_on_event_right_click (day_view, event, -1, -1); + } + + return TRUE; +} + + +static gboolean +e_day_view_convert_event_coords (EDayView *day_view, + GdkEvent *event, + GdkWindow *window, + gint *x_return, + gint *y_return) +{ + gint event_x, event_y, win_x, win_y; + GdkWindow *event_window;; + + /* Get the event window, x & y from the appropriate event struct. */ + switch (event->type) { + case GDK_BUTTON_PRESS: + case GDK_2BUTTON_PRESS: + case GDK_3BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + event_x = event->button.x; + event_y = event->button.y; + event_window = event->button.window; + break; + case GDK_MOTION_NOTIFY: + event_x = event->motion.x; + event_y = event->motion.y; + event_window = event->motion.window; + break; + default: + /* Shouldn't get here. */ + g_assert_not_reached (); + return FALSE; + } + + while (event_window && event_window != window + && event_window != GDK_ROOT_PARENT()) { + gdk_window_get_position (event_window, &win_x, &win_y); + event_x += win_x; + event_y += win_y; + event_window = gdk_window_get_parent (event_window); + } + + *x_return = event_x; + *y_return = event_y; + + if (event_window != window) + g_print ("Couldn't find event window\n"); + + return (event_window == window) ? TRUE : FALSE; +} + + +static gboolean +e_day_view_on_main_canvas_button_press (GtkWidget *widget, + GdkEventButton *event, + EDayView *day_view) +{ + gint event_x, event_y, scroll_x, scroll_y, row, day, event_num; + EDayViewPosition pos; + + g_print ("In e_day_view_on_main_canvas_button_press\n"); + + /* Convert the coords to the main canvas window, or return if the + window is not found. */ + if (!e_day_view_convert_event_coords (day_view, (GdkEvent*) event, + GTK_LAYOUT (widget)->bin_window, + &event_x, &event_y)) + return FALSE; + + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), + &scroll_x, &scroll_y); + event_x += scroll_x; + event_y += scroll_y; + + /* Find out where the mouse is. */ + pos = e_day_view_convert_position_in_main_canvas (day_view, + event_x, event_y, + &day, &row, + &event_num); + + if (pos == E_DAY_VIEW_POS_OUTSIDE) + return FALSE; + + if (pos != E_DAY_VIEW_POS_NONE) + return e_day_view_on_event_button_press (day_view, day, + event_num, event, pos, + event_x, event_y); + + e_day_view_stop_editing_event (day_view); + + /* Start the selection drag. */ + if (event->button == 1) { + if (!GTK_WIDGET_HAS_FOCUS (day_view)) + gtk_widget_grab_focus (GTK_WIDGET (day_view)); + + if (gdk_pointer_grab (GTK_LAYOUT (widget)->bin_window, FALSE, + GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK, + FALSE, NULL, event->time) == 0) { + day_view->selection_start_row = row; + day_view->selection_start_col = day; + day_view->selection_end_row = row; + day_view->selection_end_col = day; + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_END; + day_view->selection_in_top_canvas = FALSE; + + /* FIXME: Optimise? */ + gtk_widget_queue_draw (day_view->top_canvas); + gtk_widget_queue_draw (day_view->main_canvas); + } + } else if (event->button == 3) { + e_day_view_on_event_right_click (day_view, event, -1, -1); + } + + return TRUE; +} + + +static gboolean +e_day_view_on_long_event_button_press (EDayView *day_view, + gint event_num, + GdkEventButton *event, + EDayViewPosition pos, + gint event_x, + gint event_y) +{ + if (event->button == 1) { + if (event->type == GDK_BUTTON_PRESS) { + e_day_view_on_long_event_click (day_view, event_num, + event, pos, + event_x, event_y); + return TRUE; + } else if (event->type == GDK_2BUTTON_PRESS) { + e_day_view_on_event_double_click (day_view, -1, + event_num); + return TRUE; + } + } else if (event->button == 3) { + e_day_view_on_event_right_click (day_view, event, + E_DAY_VIEW_LONG_EVENT, + event_num); + return TRUE; + } + return FALSE; +} + + +static gboolean +e_day_view_on_event_button_press (EDayView *day_view, + gint day, + gint event_num, + GdkEventButton *event, + EDayViewPosition pos, + gint event_x, + gint event_y) +{ + if (event->button == 1) { + if (event->type == GDK_BUTTON_PRESS) { + e_day_view_on_event_click (day_view, day, event_num, + event, pos, + event_x, event_y); + return TRUE; + } else if (event->type == GDK_2BUTTON_PRESS) { + e_day_view_on_event_double_click (day_view, day, + event_num); + return TRUE; + } + } else if (event->button == 3) { + e_day_view_on_event_right_click (day_view, event, + day, event_num); + return TRUE; + } + return FALSE; +} + + +static void +e_day_view_on_long_event_click (EDayView *day_view, + gint event_num, + GdkEventButton *bevent, + EDayViewPosition pos, + gint event_x, + gint event_y) +{ + EDayViewEvent *event; + gint start_day, end_day, day; + gint item_x, item_y, item_w, item_h; + + g_print ("In e_day_view_on_long_event_click\n"); + + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + /* Ignore clicks on the EText while editing. */ + if (pos == E_DAY_VIEW_POS_EVENT + && E_TEXT (event->canvas_item)->editing) + return; + + if (pos == E_DAY_VIEW_POS_LEFT_EDGE + || pos == E_DAY_VIEW_POS_RIGHT_EDGE) { + if (!e_day_view_find_long_event_days (day_view, event, + &start_day, &end_day)) + return; + + /* Grab the keyboard focus, so the event being edited is saved + and we can use the Escape key to abort the resize. */ + if (!GTK_WIDGET_HAS_FOCUS (day_view)) + gtk_widget_grab_focus (GTK_WIDGET (day_view)); + + if (gdk_pointer_grab (GTK_LAYOUT (day_view->top_canvas)->bin_window, FALSE, + GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK, + FALSE, NULL, bevent->time) == 0) { + + day_view->resize_event_day = E_DAY_VIEW_LONG_EVENT; + day_view->resize_event_num = event_num; + day_view->resize_drag_pos = pos; + day_view->resize_start_row = start_day; + day_view->resize_end_row = end_day; + + /* Create the edit rect if necessary. */ + e_day_view_reshape_resize_long_event_rect_item (day_view); + + /* Make sure the text item is on top. */ + gnome_canvas_item_raise_to_top (day_view->resize_long_event_rect_item); + + /* Raise the event's item, above the rect as well. */ + gnome_canvas_item_raise_to_top (event->canvas_item); + } + } else if (e_day_view_get_long_event_position (day_view, event_num, + &start_day, &end_day, + &item_x, &item_y, + &item_w, &item_h)) { + /* Remember the item clicked and the mouse position, + so we can start a drag if the mouse moves. */ + day_view->pressed_event_day = E_DAY_VIEW_LONG_EVENT; + day_view->pressed_event_num = event_num; + + day_view->drag_event_x = event_x; + day_view->drag_event_y = event_y; + + e_day_view_convert_position_in_top_canvas (day_view, + event_x, event_y, + &day, NULL); + day_view->drag_event_offset = day - start_day; + g_print ("Y offset: %i\n", day_view->drag_event_offset); + } +} + + +static void +e_day_view_on_event_click (EDayView *day_view, + gint day, + gint event_num, + GdkEventButton *bevent, + EDayViewPosition pos, + gint event_x, + gint event_y) +{ + EDayViewEvent *event; + gint tmp_day, row, start_row; + + g_print ("In e_day_view_on_event_click\n"); + + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + /* Ignore clicks on the EText while editing. */ + if (pos == E_DAY_VIEW_POS_EVENT + && E_TEXT (event->canvas_item)->editing) + return; + + if (pos == E_DAY_VIEW_POS_TOP_EDGE + || pos == E_DAY_VIEW_POS_BOTTOM_EDGE) { + /* Grab the keyboard focus, so the event being edited is saved + and we can use the Escape key to abort the resize. */ + if (!GTK_WIDGET_HAS_FOCUS (day_view)) + gtk_widget_grab_focus (GTK_WIDGET (day_view)); + + if (gdk_pointer_grab (GTK_LAYOUT (day_view->main_canvas)->bin_window, FALSE, + GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK, + FALSE, NULL, bevent->time) == 0) { + + day_view->resize_event_day = day; + day_view->resize_event_num = event_num; + day_view->resize_drag_pos = pos; + day_view->resize_start_row = event->start_minute / day_view->mins_per_row; + day_view->resize_end_row = (event->end_minute - 1) / day_view->mins_per_row; + + day_view->resize_bars_event_day = day; + day_view->resize_bars_event_num = event_num; + + /* Create the edit rect if necessary. */ + e_day_view_reshape_resize_rect_item (day_view); + + e_day_view_reshape_main_canvas_resize_bars (day_view); + + /* Make sure the text item is on top. */ + gnome_canvas_item_raise_to_top (day_view->resize_rect_item); + gnome_canvas_item_raise_to_top (day_view->resize_bar_item); + + /* Raise the event's item, above the rect as well. */ + gnome_canvas_item_raise_to_top (event->canvas_item); + } + + } else { + /* Remember the item clicked and the mouse position, + so we can start a drag if the mouse moves. */ + day_view->pressed_event_day = day; + day_view->pressed_event_num = event_num; + + day_view->drag_event_x = event_x; + day_view->drag_event_y = event_y; + + e_day_view_convert_position_in_main_canvas (day_view, + event_x, event_y, + &tmp_day, &row, + NULL); + start_row = event->start_minute / day_view->mins_per_row; + day_view->drag_event_offset = row - start_row; + g_print ("Y offset: %i Row: %i Start: %i\n", + day_view->drag_event_offset, row, start_row); + } +} + + +static void +e_day_view_reshape_resize_long_event_rect_item (EDayView *day_view) +{ + gint day, event_num, start_day, end_day; + gint item_x, item_y, item_w, item_h; + gdouble x1, y1, x2, y2; + + day = day_view->resize_event_day; + event_num = day_view->resize_event_num; + + /* If we're not resizing an event, or the event is not shown, + hide the resize bars. */ + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_NONE + || !e_day_view_get_long_event_position (day_view, event_num, + &start_day, &end_day, + &item_x, &item_y, + &item_w, &item_h)) { + gnome_canvas_item_hide (day_view->resize_long_event_rect_item); + return; + } + + x1 = item_x; + y1 = item_y; + x2 = item_x + item_w - 1; + y2 = item_y + item_h - 1; + + gnome_canvas_item_set (day_view->resize_long_event_rect_item, + "x1", x1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); + gnome_canvas_item_show (day_view->resize_long_event_rect_item); +} + + +static void +e_day_view_reshape_resize_rect_item (EDayView *day_view) +{ + gint day, event_num; + gint item_x, item_y, item_w, item_h; + gdouble x1, y1, x2, y2; + + day = day_view->resize_event_day; + event_num = day_view->resize_event_num; + + /* If we're not resizing an event, or the event is not shown, + hide the resize bars. */ + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_NONE + || !e_day_view_get_event_position (day_view, day, event_num, + &item_x, &item_y, + &item_w, &item_h)) { + gnome_canvas_item_hide (day_view->resize_rect_item); + return; + } + + x1 = item_x; + y1 = item_y; + x2 = item_x + item_w - 1; + y2 = item_y + item_h - 1; + + gnome_canvas_item_set (day_view->resize_rect_item, + "x1", x1 + E_DAY_VIEW_BAR_WIDTH - 1, + "y1", y1, + "x2", x2, + "y2", y2, + NULL); + gnome_canvas_item_show (day_view->resize_rect_item); + + gnome_canvas_item_set (day_view->resize_bar_item, + "x1", x1, + "y1", y1, + "x2", x1 + E_DAY_VIEW_BAR_WIDTH - 1, + "y2", y2, + NULL); + gnome_canvas_item_show (day_view->resize_bar_item); +} + + +static void +e_day_view_on_event_double_click (EDayView *day_view, + gint day, + gint event_num) +{ + g_print ("In e_day_view_on_event_double_click\n"); + +} + + +static void +e_day_view_on_event_right_click (EDayView *day_view, + GdkEventButton *bevent, + gint day, + gint event_num) +{ + EDayViewEvent *event; + int have_selection, not_being_edited, items, i; + struct menu_item *context_menu; + + static struct menu_item main_items[] = { + { N_("New appointment..."), (GtkSignalFunc) e_day_view_on_new_appointment, NULL, TRUE } + }; + + static struct menu_item child_items[] = { + { N_("Edit this appointment..."), (GtkSignalFunc) e_day_view_on_edit_appointment, NULL, TRUE }, + { N_("Delete this appointment"), (GtkSignalFunc) e_day_view_on_delete_appointment, NULL, TRUE }, + { NULL, NULL, NULL, TRUE }, + { N_("New appointment..."), (GtkSignalFunc) e_day_view_on_new_appointment, NULL, TRUE } + }; + + static struct menu_item recur_child_items[] = { + { N_("Make this appointment movable"), (GtkSignalFunc) e_day_view_on_unrecur_appointment, NULL, TRUE }, + { N_("Edit this appointment..."), (GtkSignalFunc) e_day_view_on_edit_appointment, NULL, TRUE }, + { N_("Delete this occurance"), (GtkSignalFunc) e_day_view_on_delete_occurance, NULL, TRUE }, + { N_("Delete all occurances"), (GtkSignalFunc) e_day_view_on_delete_appointment, NULL, TRUE }, + { NULL, NULL, NULL, TRUE }, + { N_("New appointment..."), (GtkSignalFunc) e_day_view_on_new_appointment, NULL, TRUE } + }; + + g_print ("In e_day_view_on_event_right_click\n"); + + have_selection = (day_view->selection_start_col != -1); + + if (event_num == -1) { + items = 1; + context_menu = &main_items[0]; + context_menu[0].sensitive = have_selection; + } else { + if (day == E_DAY_VIEW_LONG_EVENT) + event = &g_array_index (day_view->long_events, + EDayViewEvent, event_num); + else + event = &g_array_index (day_view->events[day], + EDayViewEvent, event_num); + + /* Check if the event is being edited in the event editor. */ + not_being_edited = (event->ico->user_data == NULL); + + if (event->ico->recur) { + items = 6; + context_menu = &recur_child_items[0]; + context_menu[3].sensitive = not_being_edited; + context_menu[5].sensitive = have_selection; + } else { + items = 4; + context_menu = &child_items[0]; + context_menu[3].sensitive = have_selection; + } + /* These settings are common for each context sensitive menu */ + context_menu[0].sensitive = not_being_edited; + context_menu[1].sensitive = not_being_edited; + context_menu[2].sensitive = not_being_edited; + } + + for (i = 0; i < items; i++) + context_menu[i].data = day_view; + + day_view->popup_event_day = day; + day_view->popup_event_num = event_num; + popup_menu (context_menu, items, bevent); +} + + +static void +e_day_view_on_new_appointment (GtkWidget *widget, gpointer data) +{ + EDayView *day_view; + GtkWidget *event_editor; + iCalObject *ico; + + day_view = E_DAY_VIEW (data); + + ico = ical_new ("", user_name, ""); + ico->new = 1; + + e_day_view_get_selection_range (day_view, &ico->dtstart, &ico->dtend); + event_editor = event_editor_new (day_view->calendar, ico); + gtk_widget_show (event_editor); +} + + +static void +e_day_view_on_edit_appointment (GtkWidget *widget, gpointer data) +{ + EDayView *day_view; + EDayViewEvent *event; + GtkWidget *event_editor; + + day_view = E_DAY_VIEW (data); + + event = e_day_view_get_popup_menu_event (day_view); + if (event == NULL) + return; + + event_editor = event_editor_new (day_view->calendar, event->ico); + gtk_widget_show (event_editor); +} + + +static void +e_day_view_on_delete_occurance (GtkWidget *widget, gpointer data) +{ + EDayView *day_view; + EDayViewEvent *event; + + day_view = E_DAY_VIEW (data); + + event = e_day_view_get_popup_menu_event (day_view); + if (event == NULL) + return; + + ical_object_add_exdate (event->ico, event->start); + gnome_calendar_object_changed (day_view->calendar, event->ico, + CHANGE_DATES); + save_default_calendar (day_view->calendar); +} + + +static void +e_day_view_on_delete_appointment (GtkWidget *widget, gpointer data) +{ + EDayView *day_view; + EDayViewEvent *event; + + day_view = E_DAY_VIEW (data); + + event = e_day_view_get_popup_menu_event (day_view); + if (event == NULL) + return; + + gnome_calendar_remove_object (day_view->calendar, event->ico); + save_default_calendar (day_view->calendar); +} + + +static void +e_day_view_on_unrecur_appointment (GtkWidget *widget, gpointer data) +{ + EDayView *day_view; + EDayViewEvent *event; + iCalObject *ico; + + day_view = E_DAY_VIEW (data); + + event = e_day_view_get_popup_menu_event (day_view); + if (event == NULL) + return; + + /* New object */ + ico = ical_object_duplicate (event->ico); + g_free (ico->recur); + ico->recur = 0; + ico->dtstart = event->start; + ico->dtend = event->end; + + /* Duplicate, and eliminate the recurrency fields */ + ical_object_add_exdate (event->ico, event->start); + gnome_calendar_object_changed (day_view->calendar, event->ico, + CHANGE_ALL); + gnome_calendar_add_object (day_view->calendar, ico); + save_default_calendar (day_view->calendar); +} + + +static EDayViewEvent* +e_day_view_get_popup_menu_event (EDayView *day_view) +{ + if (day_view->popup_event_num == -1) + return NULL; + + if (day_view->popup_event_day == E_DAY_VIEW_LONG_EVENT) + return &g_array_index (day_view->long_events, + EDayViewEvent, + day_view->popup_event_num); + else + return &g_array_index (day_view->events[day_view->popup_event_day], + EDayViewEvent, + day_view->popup_event_num); +} + + +static gboolean +e_day_view_on_top_canvas_button_release (GtkWidget *widget, + GdkEventButton *event, + EDayView *day_view) +{ + + g_print ("In e_day_view_on_top_canvas_button_release\n"); + + if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_NONE; + gdk_pointer_ungrab (event->time); + } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { + e_day_view_finish_long_event_resize (day_view); + gdk_pointer_ungrab (event->time); + } else if (day_view->pressed_event_day != -1) { + e_day_view_start_editing_event (day_view, + day_view->pressed_event_day, + day_view->pressed_event_num, + NULL); + } + + day_view->pressed_event_day = -1; + + return FALSE; +} + + +static gboolean +e_day_view_on_main_canvas_button_release (GtkWidget *widget, + GdkEventButton *event, + EDayView *day_view) +{ + + g_print ("In e_day_view_on_main_canvas_button_release\n"); + + if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_NONE; + gdk_pointer_ungrab (event->time); + e_day_view_stop_auto_scroll (day_view); + } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { + e_day_view_finish_resize (day_view); + gdk_pointer_ungrab (event->time); + e_day_view_stop_auto_scroll (day_view); + } else if (day_view->pressed_event_day != -1) { + e_day_view_start_editing_event (day_view, + day_view->pressed_event_day, + day_view->pressed_event_num, + NULL); + } + + day_view->pressed_event_day = -1; + + return FALSE; +} + + +static gboolean +e_day_view_on_top_canvas_motion (GtkWidget *widget, + GdkEventMotion *mevent, + EDayView *day_view) +{ + EDayViewPosition pos; + gint event_x, event_y, scroll_x, scroll_y, canvas_x, canvas_y; + gint day, event_num; + GdkCursor *cursor; + +#if 0 + g_print ("In e_day_view_on_top_canvas_motion\n"); +#endif + + /* Convert the coords to the main canvas window, or return if the + window is not found. */ + if (!e_day_view_convert_event_coords (day_view, (GdkEvent*) mevent, + GTK_LAYOUT (widget)->bin_window, + &event_x, &event_y)) + return FALSE; + + /* The top canvas doesn't scroll, but just in case. */ + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), + &scroll_x, &scroll_y); + canvas_x = event_x + scroll_x; + canvas_y = event_y + scroll_y; + + pos = e_day_view_convert_position_in_top_canvas (day_view, + canvas_x, canvas_y, + &day, &event_num); + + if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { + e_day_view_update_selection (day_view, -1, day); + return TRUE; + } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { + if (pos != E_DAY_VIEW_POS_OUTSIDE) { + e_day_view_update_long_event_resize (day_view, day); + return TRUE; + } + } else if (day_view->pressed_event_day == E_DAY_VIEW_LONG_EVENT) { + GtkTargetList *target_list; + + g_print ("Checking whether to start drag - Pressed %i,%i Canvas: %i,%i\n", day_view->drag_event_x, day_view->drag_event_y, canvas_x, canvas_y); + + if (abs (canvas_x - day_view->drag_event_x) > E_DAY_VIEW_DRAG_START_OFFSET + || abs (canvas_y - day_view->drag_event_y) > E_DAY_VIEW_DRAG_START_OFFSET) { + day_view->drag_event_day = day_view->pressed_event_day; + day_view->drag_event_num = day_view->pressed_event_num; + day_view->pressed_event_day = -1; + + target_list = gtk_target_list_new (target_table, + n_targets); + gtk_drag_begin (widget, target_list, + GDK_ACTION_COPY | GDK_ACTION_MOVE, + 1, (GdkEvent*)mevent); + gtk_target_list_unref (target_list); + } + } else { + cursor = day_view->normal_cursor; + switch (pos) { + case E_DAY_VIEW_POS_LEFT_EDGE: + case E_DAY_VIEW_POS_RIGHT_EDGE: + cursor = day_view->resize_width_cursor; + break; + default: + break; + } + /* Only set the cursor if it is different to last one set. */ + if (day_view->last_cursor_set_in_top_canvas != cursor) { + day_view->last_cursor_set_in_top_canvas = cursor; + gdk_window_set_cursor (widget->window, cursor); + } + + } + + return FALSE; +} + + +static gboolean +e_day_view_on_main_canvas_motion (GtkWidget *widget, + GdkEventMotion *mevent, + EDayView *day_view) +{ + EDayViewPosition pos; + gint event_x, event_y, scroll_x, scroll_y, canvas_x, canvas_y; + gint row, day, event_num; + GdkCursor *cursor; + +#if 0 + g_print ("In e_day_view_on_main_canvas_motion\n"); +#endif + + /* Convert the coords to the main canvas window, or return if the + window is not found. */ + if (!e_day_view_convert_event_coords (day_view, (GdkEvent*) mevent, + GTK_LAYOUT (widget)->bin_window, + &event_x, &event_y)) + return FALSE; + + day_view->last_mouse_x = event_x; + day_view->last_mouse_y = event_y; + + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), + &scroll_x, &scroll_y); + canvas_x = event_x + scroll_x; + canvas_y = event_y + scroll_y; + + pos = e_day_view_convert_position_in_main_canvas (day_view, + canvas_x, canvas_y, + &day, &row, + &event_num); + + if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { + if (pos != E_DAY_VIEW_POS_OUTSIDE) { + e_day_view_update_selection (day_view, row, day); + e_day_view_check_auto_scroll (day_view, event_y); + return TRUE; + } + } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { + if (pos != E_DAY_VIEW_POS_OUTSIDE) { + e_day_view_update_resize (day_view, row); + e_day_view_check_auto_scroll (day_view, event_y); + return TRUE; + } + } else if (day_view->pressed_event_day != -1) { + GtkTargetList *target_list; + + g_print ("Checking whether to start drag - Pressed %i,%i Canvas: %i,%i\n", day_view->drag_event_x, day_view->drag_event_y, canvas_x, canvas_y); + + if (abs (canvas_x - day_view->drag_event_x) > E_DAY_VIEW_DRAG_START_OFFSET + || abs (canvas_y - day_view->drag_event_y) > E_DAY_VIEW_DRAG_START_OFFSET) { + day_view->drag_event_day = day_view->pressed_event_day; + day_view->drag_event_num = day_view->pressed_event_num; + day_view->pressed_event_day = -1; + + target_list = gtk_target_list_new (target_table, + n_targets); + gtk_drag_begin (widget, target_list, + GDK_ACTION_COPY | GDK_ACTION_MOVE, + 1, (GdkEvent*)mevent); + gtk_target_list_unref (target_list); + } + } else { + cursor = day_view->normal_cursor; + switch (pos) { + case E_DAY_VIEW_POS_LEFT_EDGE: + cursor = day_view->move_cursor; + break; + case E_DAY_VIEW_POS_TOP_EDGE: + case E_DAY_VIEW_POS_BOTTOM_EDGE: + cursor = day_view->resize_height_cursor; + break; + default: + break; + } + /* Only set the cursor if it is different to last one set. */ + if (day_view->last_cursor_set_in_main_canvas != cursor) { + day_view->last_cursor_set_in_main_canvas = cursor; + gdk_window_set_cursor (widget->window, cursor); + } + } + + return FALSE; +} + + +static void +e_day_view_update_selection (EDayView *day_view, + gint row, + gint col) +{ + gint tmp_row, tmp_col; + gboolean need_redraw = FALSE; + +#if 0 + g_print ("Updating selection %i,%i\n", col, row); +#endif + + day_view->selection_in_top_canvas = (row == -1) ? TRUE : FALSE; + + if (day_view->selection_drag_pos == E_DAY_VIEW_DRAG_START) { + if (row != day_view->selection_start_row + || col != day_view->selection_start_col) { + need_redraw = TRUE; + day_view->selection_start_row = row; + day_view->selection_start_col = col; + } + } else { + if (row != day_view->selection_end_row + || col != day_view->selection_end_col) { + need_redraw = TRUE; + day_view->selection_end_row = row; + day_view->selection_end_col = col; + } + } + + /* Switch the drag position if necessary. */ + if (day_view->selection_start_col > day_view->selection_end_col + || (day_view->selection_start_col == day_view->selection_end_col + && day_view->selection_start_row > day_view->selection_end_row)) { + tmp_row = day_view->selection_start_row; + tmp_col = day_view->selection_start_col; + day_view->selection_start_col = day_view->selection_end_col; + day_view->selection_start_row = day_view->selection_end_row; + day_view->selection_end_col = tmp_col; + day_view->selection_end_row = tmp_row; + if (day_view->selection_drag_pos == E_DAY_VIEW_DRAG_START) + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_END; + else + day_view->selection_drag_pos = E_DAY_VIEW_DRAG_START; + } + + /* FIXME: Optimise? */ + if (need_redraw) { + gtk_widget_queue_draw (day_view->top_canvas); + gtk_widget_queue_draw (day_view->main_canvas); + } +} + + +static void +e_day_view_update_long_event_resize (EDayView *day_view, + gint day) +{ + EDayViewEvent *event; + gint event_num; + gboolean need_reshape = FALSE; + +#if 1 + g_print ("Updating resize Day:%i\n", day); +#endif + + event_num = day_view->resize_event_num; + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_LEFT_EDGE) { + day = MIN (day, day_view->resize_end_row); + if (day != day_view->resize_start_row) { + need_reshape = TRUE; + day_view->resize_start_row = day; + + } + } else { + day = MAX (day, day_view->resize_start_row); + if (day != day_view->resize_end_row) { + need_reshape = TRUE; + day_view->resize_end_row = day; + } + } + + /* FIXME: Optimise? */ + if (need_reshape) { + e_day_view_reshape_long_event (day_view, event_num); + e_day_view_reshape_resize_long_event_rect_item (day_view); + gtk_widget_queue_draw (day_view->top_canvas); + } +} + + +static void +e_day_view_update_resize (EDayView *day_view, + gint row) +{ + EDayViewEvent *event; + gint day, event_num; + gboolean need_reshape = FALSE; + +#if 0 + g_print ("Updating resize Row:%i\n", row); +#endif + + day = day_view->resize_event_day; + event_num = day_view->resize_event_num; + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_TOP_EDGE) { + row = MIN (row, day_view->resize_end_row); + if (row != day_view->resize_start_row) { + need_reshape = TRUE; + day_view->resize_start_row = row; + + } + } else { + row = MAX (row, day_view->resize_start_row); + if (row != day_view->resize_end_row) { + need_reshape = TRUE; + day_view->resize_end_row = row; + } + } + + /* FIXME: Optimise? */ + if (need_reshape) { + e_day_view_reshape_day_event (day_view, day, event_num); + e_day_view_reshape_resize_rect_item (day_view); + e_day_view_reshape_main_canvas_resize_bars (day_view); + gtk_widget_queue_draw (day_view->main_canvas); + } +} + + +/* This converts the resize start or end row back to a time and updates the + event. */ +static void +e_day_view_finish_long_event_resize (EDayView *day_view) +{ + EDayViewEvent *event; + gint event_num; + + + g_print ("In e_day_view_finish_long_event_resize\n"); + + event_num = day_view->resize_event_num; + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_LEFT_EDGE) { + event->ico->dtstart = day_view->day_starts[day_view->resize_start_row]; + } else { + event->ico->dtend = day_view->day_starts[day_view->resize_end_row + 1]; + } + + gnome_canvas_item_hide (day_view->resize_long_event_rect_item); + + day_view->resize_drag_pos = E_DAY_VIEW_POS_NONE; + + /* Notify calendar of change */ + gnome_calendar_object_changed (day_view->calendar, event->ico, + CHANGE_DATES); + save_default_calendar (day_view->calendar); +} + + +/* This converts the resize start or end row back to a time and updates the + event. */ +static void +e_day_view_finish_resize (EDayView *day_view) +{ + EDayViewEvent *event; + gint day, event_num; + + + g_print ("In e_day_view_finish_resize\n"); + + day = day_view->resize_event_day; + event_num = day_view->resize_event_num; + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_TOP_EDGE) { + event->ico->dtstart = e_day_view_convert_grid_position_to_time (day_view, day, day_view->resize_start_row); + } else { + event->ico->dtend = e_day_view_convert_grid_position_to_time (day_view, day, day_view->resize_end_row + 1); + } + + gnome_canvas_item_hide (day_view->resize_rect_item); + gnome_canvas_item_hide (day_view->resize_bar_item); + + /* Hide the horizontal bars. */ + g_print ("Hiding resize bars\n"); + day_view->resize_bars_event_day = -1; + day_view->resize_bars_event_num = -1; + gnome_canvas_item_hide (day_view->main_canvas_top_resize_bar_item); + gnome_canvas_item_hide (day_view->main_canvas_bottom_resize_bar_item); + + day_view->resize_drag_pos = E_DAY_VIEW_POS_NONE; + + /* Notify calendar of change */ + gnome_calendar_object_changed (day_view->calendar, event->ico, + CHANGE_DATES); + save_default_calendar (day_view->calendar); +} + + +static void +e_day_view_abort_resize (EDayView *day_view, + guint32 time) +{ + gint day, event_num; + + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_NONE) + return; + + g_print ("In e_day_view_abort_resize\n"); + + day_view->resize_drag_pos = E_DAY_VIEW_POS_NONE; + gdk_pointer_ungrab (time); + + day = day_view->resize_event_day; + event_num = day_view->resize_event_num; + + if (day == E_DAY_VIEW_LONG_EVENT) { + e_day_view_reshape_long_event (day_view, event_num); + gtk_widget_queue_draw (day_view->top_canvas); + + day_view->last_cursor_set_in_top_canvas = day_view->normal_cursor; + gdk_window_set_cursor (day_view->top_canvas->window, + day_view->normal_cursor); + gnome_canvas_item_hide (day_view->resize_long_event_rect_item); + } else { + e_day_view_reshape_day_event (day_view, day, event_num); + e_day_view_reshape_main_canvas_resize_bars (day_view); + gtk_widget_queue_draw (day_view->main_canvas); + + day_view->last_cursor_set_in_main_canvas = day_view->normal_cursor; + gdk_window_set_cursor (day_view->main_canvas->window, + day_view->normal_cursor); + gnome_canvas_item_hide (day_view->resize_rect_item); + gnome_canvas_item_hide (day_view->resize_bar_item); + } +} + + +static void +e_day_view_reload_events (EDayView *day_view) +{ + e_day_view_free_events (day_view); + + /* Reset all our indices. */ + day_view->editing_event_day = -1; + day_view->popup_event_day = -1; + day_view->resize_bars_event_day = -1; + day_view->resize_event_day = -1; + day_view->pressed_event_day = -1; + day_view->drag_event_day = -1; + + if (day_view->calendar) { + calendar_iterate (day_view->calendar->cal, + day_view->lower, + day_view->upper, + e_day_view_add_event, + day_view); + } + + e_day_view_check_layout (day_view); + + gtk_widget_queue_draw (day_view->top_canvas); + gtk_widget_queue_draw (day_view->main_canvas); +} + + +static void +e_day_view_free_events (EDayView *day_view) +{ + gint day; + + e_day_view_free_event_array (day_view, day_view->long_events); + + for (day = 0; day < E_DAY_VIEW_MAX_DAYS; day++) + e_day_view_free_event_array (day_view, day_view->events[day]); +} + + +static void +e_day_view_free_event_array (EDayView *day_view, + GArray *array) +{ + EDayViewEvent *event; + gint event_num; + + for (event_num = 0; event_num < array->len; event_num++) { + event = &g_array_index (array, EDayViewEvent, event_num); + if (event->canvas_item) + gtk_object_destroy (GTK_OBJECT (event->canvas_item)); + } + + g_array_set_size (array, 0); +} + + +/* This adds one event to the view, adding it to the appropriate array. */ +static int +e_day_view_add_event (iCalObject *ico, + time_t start, + time_t end, + gpointer data) + +{ + EDayView *day_view; + EDayViewEvent event; + gint day; + struct tm start_tm, end_tm; + + day_view = E_DAY_VIEW (data); + + /* Check that the event times are valid. */ + g_return_val_if_fail (start <= end, TRUE); + g_return_val_if_fail (start < day_view->upper, TRUE); + g_return_val_if_fail (end > day_view->lower, TRUE); + + start_tm = *(localtime (&start)); + end_tm = *(localtime (&end)); + + event.ico = ico; + event.start = start; + event.end = end; + event.canvas_item = NULL; + + /* Calculate the start & end minute, relative to the + top of the display. FIXME. */ + event.start_minute = start_tm.tm_hour * 60 + start_tm.tm_min; + event.end_minute = end_tm.tm_hour * 60 + end_tm.tm_min; + + event.start_row_or_col = -1; + event.num_columns = -1; + + /* Find out which array to add the event to. */ + for (day = 0; day < day_view->days_shown; day++) { + if (start >= day_view->day_starts[day] + && end <= day_view->day_starts[day + 1]) { + + /* Special case for when the appointment ends at + midnight, i.e. the start of the next day. */ + if (end == day_view->day_starts[day + 1]) { + + /* If the event last the entire day, then we + skip it here so it gets added to the top + canvas. */ + if (start == day_view->day_starts[day]) + break; + + event.end_minute = 24 * 60; + } + + g_array_append_val (day_view->events[day], event); + day_view->events_sorted[day] = FALSE; + day_view->need_layout[day] = TRUE; + return TRUE; + } + } + + /* The event wasn't within one day so it must be a long event, + i.e. shown in the top canvas. */ + g_array_append_val (day_view->long_events, event); + day_view->long_events_sorted = FALSE; + day_view->long_events_need_layout = TRUE; + return TRUE; +} + + +/* This lays out the short (less than 1 day) events in the columns. + Any long events are simply skipped. */ +void +e_day_view_check_layout (EDayView *day_view) +{ + gint day; + + /* Don't bother if we aren't visible. */ + if (!GTK_WIDGET_VISIBLE (day_view)) + return; + + /* Make sure the events are sorted (by start and size). */ + e_day_view_ensure_events_sorted (day_view); + + for (day = 0; day < day_view->days_shown; day++) { + if (day_view->need_layout[day]) + e_day_view_layout_day_events (day_view, day); + + if (day_view->need_layout[day] + || day_view->need_reshape[day]) { + e_day_view_reshape_day_events (day_view, day); + + if (day_view->resize_bars_event_day == day) + e_day_view_reshape_main_canvas_resize_bars (day_view); + } + + day_view->need_layout[day] = FALSE; + day_view->need_reshape[day] = FALSE; + } + + if (day_view->long_events_need_layout) + e_day_view_layout_long_events (day_view); + + if (day_view->long_events_need_layout + || day_view->long_events_need_reshape) + e_day_view_reshape_long_events (day_view); + + day_view->long_events_need_layout = FALSE; + day_view->long_events_need_reshape = FALSE; +} + + +static void +e_day_view_layout_long_events (EDayView *day_view) +{ + EDayViewEvent *event; + gint event_num, old_rows_in_top_display, top_canvas_height, top_rows; + guint8 *grid; + + /* This is a temporary 2-d grid which is used to place events. + Each element is 0 if the position is empty, or 1 if occupied. + We allocate the maximum size possible here, assuming that each + event will need its own row. */ + grid = g_new0 (guint8, + day_view->long_events->len * E_DAY_VIEW_MAX_DAYS); + + /* Reset the number of rows in the top display to 0. It will be + updated as events are layed out below. */ + old_rows_in_top_display = day_view->rows_in_top_display; + day_view->rows_in_top_display = 0; + + /* Iterate over the events, finding which days they cover, and putting + them in the first free row available. */ + for (event_num = 0; event_num < day_view->long_events->len; + event_num++) { + event = &g_array_index (day_view->long_events, + EDayViewEvent, event_num); + e_day_view_layout_long_event (day_view, event, grid); + } + + /* Free the grid. */ + g_free (grid); + + /* Set the height of the top canvas based on the row height and the + number of rows needed (min 1 + 1 for the dates + 1 space for DnD).*/ + if (day_view->rows_in_top_display != old_rows_in_top_display) { + top_rows = MAX (1, day_view->rows_in_top_display); + top_canvas_height = (top_rows + 2) * day_view->top_row_height; + gtk_widget_set_usize (day_view->top_canvas, -1, + top_canvas_height); + } +} + + +static void +e_day_view_layout_long_event (EDayView *day_view, + EDayViewEvent *event, + guint8 *grid) +{ + gint start_day, end_day, free_row, day, row; + + event->num_columns = 0; + + if (!e_day_view_find_long_event_days (day_view, event, + &start_day, &end_day)) + return; + + /* Try each row until we find a free one. */ + row = 0; + do { + free_row = row; + for (day = start_day; day <= end_day; day++) { + if (grid[row * E_DAY_VIEW_MAX_DAYS + day]) { + free_row = -1; + break; + } + } + row++; + } while (free_row == -1); + + event->start_row_or_col = free_row; + event->num_columns = 1; + + /* Mark the cells as full. */ + for (day = start_day; day <= end_day; day++) { + grid[free_row * E_DAY_VIEW_MAX_DAYS + day] = 1; + } + + /* Update the number of rows in the top canvas if necessary. */ + day_view->rows_in_top_display = MAX (day_view->rows_in_top_display, + free_row + 1); +} + + +static void +e_day_view_reshape_long_events (EDayView *day_view) +{ + EDayViewEvent *event; + gint event_num; + + for (event_num = 0; event_num < day_view->long_events->len; + event_num++) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + if (event->num_columns == 0) { + if (event->canvas_item) { + gtk_object_destroy (GTK_OBJECT (event->canvas_item)); + event->canvas_item = NULL; + } + } else { + e_day_view_reshape_long_event (day_view, event_num); + } + } +} + + +static void +e_day_view_reshape_long_event (EDayView *day_view, + gint event_num) +{ + EDayViewEvent *event; + gint start_day, end_day, item_x, item_y, item_w, item_h; + gint text_x, text_w, num_icons, icons_width, width, time_width; + iCalObject *ico; + gint min_text_x, max_text_w; + gdouble text_width; + gboolean show_icons = TRUE, use_max_width = FALSE; + + if (!e_day_view_get_long_event_position (day_view, event_num, + &start_day, &end_day, + &item_x, &item_y, + &item_w, &item_h)) { + if (event->canvas_item) { + gtk_object_destroy (GTK_OBJECT (event->canvas_item)); + event->canvas_item = NULL; + } + return; + } + + /* Take off the border and padding. */ + item_x += E_DAY_VIEW_LONG_EVENT_BORDER_WIDTH + E_DAY_VIEW_LONG_EVENT_X_PAD; + item_w -= (E_DAY_VIEW_LONG_EVENT_BORDER_WIDTH + E_DAY_VIEW_LONG_EVENT_X_PAD) * 2; + item_y += E_DAY_VIEW_LONG_EVENT_BORDER_HEIGHT + E_DAY_VIEW_LONG_EVENT_Y_PAD; + item_h -= (E_DAY_VIEW_LONG_EVENT_BORDER_HEIGHT + E_DAY_VIEW_LONG_EVENT_Y_PAD) * 2; + + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + /* We don't show the icons while resizing, since we'd have to + draw them on top of the resize rect. Nor when editing. */ + num_icons = 0; + ico = event->ico; + + if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE + && day_view->resize_event_day == E_DAY_VIEW_LONG_EVENT + && day_view->resize_event_num == event_num) + show_icons = FALSE; + + if (day_view->editing_event_day == E_DAY_VIEW_LONG_EVENT + && day_view->editing_event_num == event_num) { + g_print ("Reshaping long event which is being edited.\n"); + show_icons = FALSE; + use_max_width = TRUE; + } + + if (show_icons) { + if (ico->dalarm.enabled || ico->malarm.enabled + || ico->palarm.enabled || ico->aalarm.enabled) + num_icons++; + if (ico->recur) + num_icons++; + } + + /* FIXME: Handle item_w & item_h <= 0. */ + + if (!event->canvas_item) { + event->canvas_item = + gnome_canvas_item_new (GNOME_CANVAS_GROUP (GNOME_CANVAS (day_view->top_canvas)->root), + e_text_get_type (), + "font_gdk", GTK_WIDGET (day_view)->style->font, + "anchor", GTK_ANCHOR_NW, + "clip", TRUE, + "max_lines", 1, + "editable", TRUE, + NULL); + gtk_signal_connect (GTK_OBJECT (event->canvas_item), "event", + GTK_SIGNAL_FUNC (e_day_view_on_text_item_event), + day_view); + e_day_view_update_long_event_label (day_view, event_num); + } + + /* Calculate its position. We first calculate the ideal position which + is centered with the icons. We then make sure we haven't gone off + the left edge of the available space. Finally we make sure we don't + go off the right edge. */ + icons_width = (E_DAY_VIEW_ICON_WIDTH + E_DAY_VIEW_ICON_X_PAD) + * num_icons; + time_width = day_view->max_small_hour_width + day_view->colon_width + + day_view->max_minute_width; + + if (use_max_width) { + text_x = item_x; + text_w = item_w; + } else { + /* Get the requested size of the label. */ + gtk_object_get (GTK_OBJECT (event->canvas_item), + "text_width", &text_width, + NULL); + + width = text_width + icons_width; + text_x = item_x + (item_w - width) / 2; + + min_text_x = item_x; + if (event->start > day_view->day_starts[start_day]) + min_text_x += time_width + E_DAY_VIEW_LONG_EVENT_TIME_X_PAD; + + text_x = MAX (text_x, min_text_x); + + max_text_w = item_x + item_w - text_x; + if (event->end < day_view->day_starts[end_day + 1]) + max_text_w -= time_width + E_DAY_VIEW_LONG_EVENT_TIME_X_PAD; + + text_w = MIN (width, max_text_w); + } + + /* Now take out the space for the icons. */ + text_x += icons_width; + text_w -= icons_width; + + gnome_canvas_item_set (event->canvas_item, + "x", (gdouble) text_x, + "y", (gdouble) item_y, + "clip_width", (gdouble) text_w, + "clip_height", (gdouble) item_h, + NULL); +} + + +/* Find the start and end days for the event. */ +gboolean +e_day_view_find_long_event_days (EDayView *day_view, + EDayViewEvent *event, + gint *start_day_return, + gint *end_day_return) +{ + gint day, start_day, end_day; + + start_day = -1; + end_day = -1; + + for (day = 0; day < day_view->days_shown; day++) { + if (start_day == -1 + && event->start < day_view->day_starts[day + 1]) + start_day = day; + if (event->end > day_view->day_starts[day]) + end_day = day; + } + + /* Sanity check. */ + if (start_day < 0 || start_day >= day_view->days_shown + || end_day < 0 || end_day >= day_view->days_shown + || end_day < start_day) { + g_warning ("Invalid date range for event"); + return FALSE; + } + + *start_day_return = start_day; + *end_day_return = end_day; + + return TRUE; +} + + +static void +e_day_view_layout_day_events (EDayView *day_view, + gint day) +{ + EDayViewEvent *event; + gint row, event_num; + guint8 *grid; + + /* This is a temporary array which keeps track of rows which are + connected. When an appointment spans multiple rows then the number + of columns in each of these rows must be the same (i.e. the maximum + of all of them). Each element in the array corresponds to one row + and contains the index of the first row in the group of connected + rows. */ + guint16 group_starts[12 * 24]; + + /* Reset the cols_per_row array, and initialize the connected rows. */ + for (row = 0; row < day_view->rows; row++) { + day_view->cols_per_row[day][row] = 0; + group_starts[row] = row; + } + + /* This is a temporary 2-d grid which is used to place events. + Each element is 0 if the position is empty, or 1 if occupied. */ + grid = g_new0 (guint8, day_view->rows * E_DAY_VIEW_MAX_COLUMNS); + + + /* Iterate over the events, finding which rows they cover, and putting + them in the first free column available. Increment the number of + events in each of the rows it covers, and make sure they are all + in one group. */ + for (event_num = 0; event_num < day_view->events[day]->len; + event_num++) { + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + e_day_view_layout_day_event (day_view, day, event, + grid, group_starts); + } + + /* Recalculate the number of columns needed in each row. */ + e_day_view_recalc_cols_per_row (day_view, day, group_starts); + + /* Iterate over the events again, trying to expand events horizontally + if there is enough space. */ + for (event_num = 0; event_num < day_view->events[day]->len; + event_num++) { + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + e_day_view_expand_day_event (day_view, day, event, grid); + } + + /* Free the grid. */ + g_free (grid); +} + + +/* Finds the first free position to place the event in. + Increments the number of events in each of the rows it covers, and makes + sure they are all in one group. */ +static void +e_day_view_layout_day_event (EDayView *day_view, + gint day, + EDayViewEvent *event, + guint8 *grid, + guint16 *group_starts) +{ + gint start_row, end_row, free_col, col, row, group_start; + + start_row = event->start_minute / day_view->mins_per_row; + end_row = (event->end_minute - 1) / day_view->mins_per_row; + + /* Try each column until we find a free one. */ + for (col = 0; col < E_DAY_VIEW_MAX_COLUMNS; col++) { + free_col = col; + for (row = start_row; row <= end_row; row++) { + if (grid[row * E_DAY_VIEW_MAX_COLUMNS + col]) { + free_col = -1; + break; + } + } + + if (free_col != -1) + break; + } + + /* If we can't find space for the event, mark it as not displayed. */ + if (free_col == -1) { + event->num_columns = 0; + return; + } + + /* The event is assigned 1 col initially, but may be expanded later. */ + event->start_row_or_col = free_col; + event->num_columns = 1; + + /* Determine the start index of the group. */ + group_start = group_starts[start_row]; + + /* Increment number of events in each of the rows the event covers. + We use the cols_per_row array for this. It will be sorted out after + all the events have been layed out. Also make sure all the rows that + the event covers are in one group. */ + for (row = start_row; row <= end_row; row++) { + grid[row * E_DAY_VIEW_MAX_COLUMNS + free_col] = 1; + day_view->cols_per_row[day][row]++; + group_starts[row] = group_start; + } + + /* If any following rows should be in the same group, add them. */ + for (row = end_row + 1; row < day_view->rows; row++) { + if (group_starts[row] > end_row) + break; + group_starts[row] = group_start; + } +} + + +/* For each group of rows, find the max number of events in all the + rows, and set the number of cols in each of the rows to that. */ +static void +e_day_view_recalc_cols_per_row (EDayView *day_view, + gint day, + guint16 *group_starts) +{ + gint start_row = 0, row, next_start_row, max_events; + + while (start_row < day_view->rows) { + + max_events = 0; + for (row = start_row; row < day_view->rows && group_starts[row] == start_row; row++) + max_events = MAX (max_events, day_view->cols_per_row[day][row]); + + next_start_row = row; + + for (row = start_row; row < next_start_row; row++) + day_view->cols_per_row[day][row] = max_events; + + start_row = next_start_row; + } +} + + +/* Expands the event horizontally to fill any free space. */ +static void +e_day_view_expand_day_event (EDayView *day_view, + gint day, + EDayViewEvent *event, + guint8 *grid) +{ + gint start_row, end_row, col, row; + gboolean clashed; + + start_row = event->start_minute / day_view->mins_per_row; + end_row = (event->end_minute - 1) / day_view->mins_per_row; + + /* Try each column until we find a free one. */ + clashed = FALSE; + for (col = event->start_row_or_col + 1; col < day_view->cols_per_row[day][start_row]; col++) { + for (row = start_row; row <= end_row; row++) { + if (grid[row * E_DAY_VIEW_MAX_COLUMNS + col]) { + clashed = TRUE; + break; + } + } + + if (clashed) + break; + + event->num_columns++; + } +} + + +/* This creates or updates the sizes of the canvas items for one day of the + main canvas. */ +static void +e_day_view_reshape_day_events (EDayView *day_view, + gint day) +{ + gint event_num; + + g_print ("In e_day_view_reshape_day_events\n"); + + for (event_num = 0; event_num < day_view->events[day]->len; + event_num++) { + e_day_view_reshape_day_event (day_view, day, event_num); + } +} + + +static void +e_day_view_reshape_day_event (EDayView *day_view, + gint day, + gint event_num) +{ + EDayViewEvent *event; + gint item_x, item_y, item_w, item_h; + gint num_icons, icons_offset; + iCalObject *ico; + + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + ico = event->ico; + + if (!e_day_view_get_event_position (day_view, day, event_num, + &item_x, &item_y, + &item_w, &item_h)) { + if (event->canvas_item) { + gtk_object_destroy (GTK_OBJECT (event->canvas_item)); + event->canvas_item = NULL; + } + } else { + /* Skip the border and padding. */ + item_x += E_DAY_VIEW_BAR_WIDTH + E_DAY_VIEW_EVENT_X_PAD; + item_w -= E_DAY_VIEW_BAR_WIDTH + E_DAY_VIEW_EVENT_X_PAD * 2; + item_y += E_DAY_VIEW_EVENT_BORDER_HEIGHT + E_DAY_VIEW_EVENT_Y_PAD; + item_h -= (E_DAY_VIEW_EVENT_BORDER_HEIGHT + E_DAY_VIEW_EVENT_Y_PAD) * 2; + + /* We don't show the icons while resizing, since we'd have to + draw them on top of the resize rect. */ + num_icons = 0; + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_NONE + || day_view->resize_event_day != day + || day_view->resize_event_num != event_num) { + if (ico->dalarm.enabled || ico->malarm.enabled + || ico->palarm.enabled || ico->aalarm.enabled) + num_icons++; + if (ico->recur) + num_icons++; + } + + if (num_icons > 0) { + if (item_h >= (E_DAY_VIEW_ICON_HEIGHT + E_DAY_VIEW_ICON_Y_PAD) * num_icons) + icons_offset = E_DAY_VIEW_ICON_WIDTH + E_DAY_VIEW_ICON_X_PAD * 2; + else + icons_offset = (E_DAY_VIEW_ICON_WIDTH + E_DAY_VIEW_ICON_X_PAD) * num_icons + E_DAY_VIEW_ICON_X_PAD; + item_x += icons_offset; + item_w -= icons_offset; + } + + /* FIXME: Handle item_w & item_h <= 0. */ + + if (!event->canvas_item) { + event->canvas_item = + gnome_canvas_item_new (GNOME_CANVAS_GROUP (GNOME_CANVAS (day_view->main_canvas)->root), + e_text_get_type (), + "font_gdk", GTK_WIDGET (day_view)->style->font, + "anchor", GTK_ANCHOR_NW, + "line_wrap", TRUE, + "editable", TRUE, + "clip", TRUE, + NULL); + gtk_signal_connect (GTK_OBJECT (event->canvas_item), + "event", + GTK_SIGNAL_FUNC (e_day_view_on_text_item_event), + day_view); + e_day_view_update_event_label (day_view, day, + event_num); + } + + gnome_canvas_item_set (event->canvas_item, + "x", (gdouble) item_x, + "y", (gdouble) item_y, + "clip_width", (gdouble) item_w, + "clip_height", (gdouble) item_h, + NULL); + } +} + + +/* This creates or resizes the horizontal bars used to resize events in the + main canvas. */ +static void +e_day_view_reshape_main_canvas_resize_bars (EDayView *day_view) +{ + gint day, event_num; + gint item_x, item_y, item_w, item_h; + gdouble x, y, w, h; + + day = day_view->resize_bars_event_day; + event_num = day_view->resize_bars_event_num; + + /* If we're not editing an event, or the event is not shown, + hide the resize bars. */ + if (day != -1 && day == day_view->drag_event_day + && event_num == day_view->drag_event_num) { + gtk_object_get (GTK_OBJECT (day_view->drag_rect_item), + "x1", &x, + "y1", &y, + "x2", &w, + "y2", &h, + NULL); + w -= x; + x++; + h -= y; + } else if (day != -1 + && e_day_view_get_event_position (day_view, day, event_num, + &item_x, &item_y, + &item_w, &item_h)) { + x = item_x + E_DAY_VIEW_BAR_WIDTH; + y = item_y; + w = item_w - E_DAY_VIEW_BAR_WIDTH; + h = item_h; + } else { + gnome_canvas_item_hide (day_view->main_canvas_top_resize_bar_item); + gnome_canvas_item_hide (day_view->main_canvas_bottom_resize_bar_item); + return; + } + + gnome_canvas_item_set (day_view->main_canvas_top_resize_bar_item, + "x1", x - E_DAY_VIEW_BAR_WIDTH, + "y1", y - E_DAY_VIEW_BAR_HEIGHT, + "x2", x + w - 1, + "y2", y - 1, + NULL); + gnome_canvas_item_show (day_view->main_canvas_top_resize_bar_item); + + gnome_canvas_item_set (day_view->main_canvas_bottom_resize_bar_item, + "x1", x - E_DAY_VIEW_BAR_WIDTH, + "y1", y + h, + "x2", x + w - 1, + "y2", y + h + E_DAY_VIEW_BAR_HEIGHT - 1, + NULL); + gnome_canvas_item_show (day_view->main_canvas_bottom_resize_bar_item); +} + + +static void +e_day_view_ensure_events_sorted (EDayView *day_view) +{ + gint day; + + /* Sort the long events. */ + if (!day_view->long_events_sorted) { + qsort (day_view->long_events->data, + day_view->long_events->len, + sizeof (EDayViewEvent), + e_day_view_event_sort_func); + day_view->long_events_sorted = TRUE; + } + + /* Sort the events for each day. */ + for (day = 0; day < day_view->days_shown; day++) { + if (!day_view->events_sorted[day]) { + qsort (day_view->events[day]->data, + day_view->events[day]->len, + sizeof (EDayViewEvent), + e_day_view_event_sort_func); + day_view->events_sorted[day] = TRUE; + } + } +} + + +static gint +e_day_view_event_sort_func (const void *arg1, + const void *arg2) +{ + EDayViewEvent *event1, *event2; + + event1 = (EDayViewEvent*) arg1; + event2 = (EDayViewEvent*) arg2; + + if (event1->start < event2->start) + return -1; + if (event1->start > event2->start) + return 1; + + if (event1->end > event2->end) + return -1; + if (event1->end < event2->end) + return 1; + + return 0; +} + + +static gint +e_day_view_key_press (GtkWidget *widget, GdkEventKey *event) +{ + EDayView *day_view; + iCalObject *ico; + gint day, event_num; + gchar *initial_text; + + g_return_val_if_fail (widget != NULL, FALSE); + g_return_val_if_fail (E_IS_DAY_VIEW (widget), FALSE); + g_return_val_if_fail (event != NULL, FALSE); + + day_view = E_DAY_VIEW (widget); + + g_print ("In e_day_view_key_press\n"); + + /* The Escape key aborts a resize operation. */ + if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { + if (event->keyval == GDK_Escape) { + e_day_view_abort_resize (day_view, event->time); + } + return FALSE; + } + + + if (day_view->selection_start_col == -1) + return FALSE; + + /* We only want to start an edit with a return key or a simple + character. */ + if (event->keyval == GDK_Return) { + initial_text = NULL; + } else if ((event->keyval < 0x20) + || (event->keyval > 0xFF) + || (event->length == 0) + || (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))) { + return FALSE; + } else { + initial_text = event->string; + } + + /* Add a new event covering the selected range. + Note that user_name is a global variable. */ + ico = ical_new ("", user_name, ""); + ico->new = 1; + + e_day_view_get_selection_range (day_view, &ico->dtstart, &ico->dtend); + + gnome_calendar_add_object (day_view->calendar, ico); + save_default_calendar (day_view->calendar); + + /* gnome_calendar_add_object() should have resulted in a call to + e_day_view_update_event(), so the new event should now be layed out. + So we try to find it so we can start editing it. */ + if (e_day_view_find_event_from_ico (day_view, ico, &day, &event_num)) { + /* Start editing the new event. */ + e_day_view_start_editing_event (day_view, day, event_num, + initial_text); + } + + return TRUE; +} + + +static void +e_day_view_start_editing_event (EDayView *day_view, + gint day, + gint event_num, + gchar *initial_text) +{ + EDayViewEvent *event; + + /* If we are already editing the event, just return. */ + if (day == day_view->editing_event_day + && event_num == day_view->editing_event_num) + return; + + if (day == E_DAY_VIEW_LONG_EVENT) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + } else { + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + } + + /* If the event is not shown, don't try to edit it. */ + if (!event->canvas_item) + return; + + if (initial_text) { + gnome_canvas_item_set (event->canvas_item, + "text", initial_text, + NULL); + } + + e_canvas_item_grab_focus (event->canvas_item); +} + + +/* This stops the current edit. If accept is TRUE the event summary is update, + else the edit is cancelled. */ +static void +e_day_view_stop_editing_event (EDayView *day_view) +{ + GtkWidget *toplevel; + + /* Check we are editing an event. */ + if (day_view->editing_event_day == -1) + return; + + /* Set focus to the toplevel so the item loses focus. */ + toplevel = gtk_widget_get_toplevel (GTK_WIDGET (day_view)); + if (toplevel && GTK_IS_WINDOW (toplevel)) + gtk_window_set_focus (GTK_WINDOW (toplevel), NULL); +} + + +static gboolean +e_day_view_on_text_item_event (GnomeCanvasItem *item, + GdkEvent *event, + EDayView *day_view) +{ + switch (event->type) { + case GDK_BUTTON_PRESS: + case GDK_BUTTON_RELEASE: + /* Only let the EText handle the event while editing. */ + if (!E_TEXT (item)->editing) + gtk_signal_emit_stop_by_name (GTK_OBJECT (item), + "event"); + break; + case GDK_FOCUS_CHANGE: + if (event->focus_change.in) + e_day_view_on_editing_started (day_view, item); + else + e_day_view_on_editing_stopped (day_view, item); + + return FALSE; + default: + break; + } + + return FALSE; +} + + +static void +e_day_view_on_editing_started (EDayView *day_view, + GnomeCanvasItem *item) +{ + gint day, event_num; + + if (!e_day_view_find_event_from_item (day_view, item, + &day, &event_num)) + return; + + g_print ("In e_day_view_on_editing_started Day:%i Event:%i\n", + day, event_num); + + day_view->editing_event_day = day; + day_view->editing_event_num = event_num; + + if (day == E_DAY_VIEW_LONG_EVENT) { + e_day_view_reshape_long_event (day_view, event_num); + } else { + day_view->resize_bars_event_day = day; + day_view->resize_bars_event_num = event_num; + e_day_view_reshape_main_canvas_resize_bars (day_view); + } +} + + +static void +e_day_view_on_editing_stopped (EDayView *day_view, + GnomeCanvasItem *item) +{ + gint day, event_num; + gboolean editing_long_event = FALSE; + EDayViewEvent *event; + gchar *text = NULL; + + /* Note: the item we are passed here isn't reliable, so we just stop + the edit of whatever item was being edited. We also receive this + event twice for some reason. */ + day = day_view->editing_event_day; + event_num = day_view->editing_event_num; + + /* If no item is being edited, just return. */ + if (day == -1) + return; + + g_print ("In e_day_view_on_editing_stopped Day:%i Event:%i\n", + day, event_num); + + if (day == E_DAY_VIEW_LONG_EVENT) { + editing_long_event = TRUE; + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + } else { + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + /* Hide the horizontal bars. */ + gnome_canvas_item_hide (day_view->main_canvas_top_resize_bar_item); + gnome_canvas_item_hide (day_view->main_canvas_bottom_resize_bar_item); + } + + /* Reset the edit fields. */ + day_view->editing_event_day = -1; + day_view->editing_event_num = -1; + + day_view->resize_bars_event_day = -1; + day_view->resize_bars_event_num = -1; + + gtk_object_get (GTK_OBJECT (event->canvas_item), + "text", &text, + NULL); + + /* Only update the summary if necessary. */ + if (text && event->ico->summary + && !strcmp (text, event->ico->summary)) { + g_free (text); + + if (day == E_DAY_VIEW_LONG_EVENT) + e_day_view_reshape_long_event (day_view, event_num); + return; + } + + if (event->ico->summary) + g_free (event->ico->summary); + + event->ico->summary = text; + + /* Notify calendar of change. This will result in a call to update, + which will reset the event label as appropriate. */ + gnome_calendar_object_changed (day_view->calendar, event->ico, + CHANGE_SUMMARY); + save_default_calendar (day_view->calendar); +} + + +/* Converts the selected range into a start and end time. */ +static void +e_day_view_get_selection_range (EDayView *day_view, + time_t *start, + time_t *end) +{ + /* Check if the selection is only in the top canvas, in which case + we can simply use the day_starts array. */ + if (day_view->selection_in_top_canvas) { + *start = day_view->day_starts[day_view->selection_start_col]; + *end = day_view->day_starts[day_view->selection_end_col + 1]; + } else { + /* Convert the start col + row into a time. */ + *start = e_day_view_convert_grid_position_to_time (day_view, day_view->selection_start_col, day_view->selection_start_row); + *end = e_day_view_convert_grid_position_to_time (day_view, day_view->selection_end_col, day_view->selection_end_row + 1); + } +} + + +/* FIXME: It is possible that we may produce an invalid time due to daylight + saving times (i.e. when clocks go forward there is a range of time which + is not valid). I don't know the best way to handle daylight saving time. */ +static time_t +e_day_view_convert_grid_position_to_time (EDayView *day_view, + gint col, + gint row) +{ + struct tm *tmp_tm; + time_t val; + gint minutes; + + /* Calulate the number of minutes since the start of the day. */ + minutes = day_view->first_hour_shown * 60 + + day_view->first_minute_shown + + row * day_view->mins_per_row; + + /* A special case for midnight, where we can use the start of the + next day. */ + if (minutes == 60 * 24) + return day_view->day_starts[col + 1]; + + /* We convert the start of the day to a struct tm, then set the + hour and minute, then convert it back to a time_t. */ + tmp_tm = localtime (&day_view->day_starts[col]); + + tmp_tm->tm_hour = minutes / 60; + tmp_tm->tm_min = minutes % 60; + tmp_tm->tm_isdst = -1; + + val = mktime (tmp_tm); + return val; +} + + +/* This starts or stops auto-scrolling when dragging a selection or resizing + an event. */ +static void +e_day_view_check_auto_scroll (EDayView *day_view, + gint event_y) +{ + g_print ("Event Y:%i\n", event_y); + if (event_y < E_DAY_VIEW_AUTO_SCROLL_OFFSET) + e_day_view_start_auto_scroll (day_view, TRUE); + else if (event_y >= day_view->main_canvas->allocation.height + - E_DAY_VIEW_AUTO_SCROLL_OFFSET) + e_day_view_start_auto_scroll (day_view, FALSE); + else + e_day_view_stop_auto_scroll (day_view); +} + + +static void +e_day_view_start_auto_scroll (EDayView *day_view, + gboolean scroll_up) +{ + if (day_view->auto_scroll_timeout_id == 0) { + day_view->auto_scroll_timeout_id = g_timeout_add (E_DAY_VIEW_AUTO_SCROLL_TIMEOUT, e_day_view_auto_scroll_handler, day_view); + day_view->auto_scroll_delay = E_DAY_VIEW_AUTO_SCROLL_DELAY; + } + day_view->auto_scroll_up = scroll_up; +} + + +static void +e_day_view_stop_auto_scroll (EDayView *day_view) +{ + if (day_view->auto_scroll_timeout_id != 0) { + gtk_timeout_remove (day_view->auto_scroll_timeout_id); + day_view->auto_scroll_timeout_id = 0; + } +} + + +static gboolean +e_day_view_auto_scroll_handler (gpointer data) +{ + EDayView *day_view; + EDayViewPosition pos; + gint scroll_x, scroll_y, new_scroll_y, canvas_x, canvas_y, row, day; + GtkAdjustment *adj; + + g_return_val_if_fail (E_IS_DAY_VIEW (data), FALSE); + + day_view = E_DAY_VIEW (data); + + GDK_THREADS_ENTER (); + + if (day_view->auto_scroll_delay > 0) { + day_view->auto_scroll_delay--; + GDK_THREADS_LEAVE (); + return TRUE; + } + + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (day_view->main_canvas), + &scroll_x, &scroll_y); + + adj = GTK_LAYOUT (day_view->main_canvas)->vadjustment; + + if (day_view->auto_scroll_up) + new_scroll_y = MAX (scroll_y - adj->step_increment, 0); + else + new_scroll_y = MIN (scroll_y + adj->step_increment, + adj->upper - adj->page_size); + + if (new_scroll_y != scroll_y) { + /* NOTE: This reduces flicker, but only works if we don't use + canvas items which have X windows. */ + gtk_layout_freeze (GTK_LAYOUT (day_view->main_canvas)); + + gnome_canvas_scroll_to (GNOME_CANVAS (day_view->main_canvas), + scroll_x, new_scroll_y); + + gtk_layout_thaw (GTK_LAYOUT (day_view->main_canvas)); + } + + canvas_x = day_view->last_mouse_x + scroll_x; + canvas_y = day_view->last_mouse_y + new_scroll_y; + + /* Update the selection/resize/drag if necessary. */ + pos = e_day_view_convert_position_in_main_canvas (day_view, + canvas_x, canvas_y, + &day, &row, NULL); + + if (pos != E_DAY_VIEW_POS_OUTSIDE) { + if (day_view->selection_drag_pos != E_DAY_VIEW_DRAG_NONE) { + e_day_view_update_selection (day_view, row, day); + } else if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE) { + e_day_view_update_resize (day_view, row); + } else if (day_view->drag_item->object.flags + & GNOME_CANVAS_ITEM_VISIBLE) { + e_day_view_update_main_canvas_drag (day_view, row, + day); + } + } + + GDK_THREADS_LEAVE (); + return TRUE; +} + + +gboolean +e_day_view_get_event_position (EDayView *day_view, + gint day, + gint event_num, + gint *item_x, + gint *item_y, + gint *item_w, + gint *item_h) +{ + EDayViewEvent *event; + gint start_row, end_row, cols_in_row, start_col, num_columns; + + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + /* If the event is flagged as not displayed, return FALSE. */ + if (event->num_columns == 0) + return FALSE; + + start_row = event->start_minute / day_view->mins_per_row; + end_row = (event->end_minute - 1) / day_view->mins_per_row; + cols_in_row = day_view->cols_per_row[day][start_row]; + start_col = event->start_row_or_col; + num_columns = event->num_columns; + + /* If the event is being resize, use the resize position. */ + if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE + && day_view->resize_event_day == day + && day_view->resize_event_num == event_num) { + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_TOP_EDGE) + start_row = day_view->resize_start_row; + else if (day_view->resize_drag_pos == E_DAY_VIEW_POS_BOTTOM_EDGE) + end_row = day_view->resize_end_row; + } + + *item_x = day_view->day_offsets[day] + day_view->day_widths[day] * start_col / cols_in_row; + *item_w = day_view->day_widths[day] * num_columns / cols_in_row - E_DAY_VIEW_GAP_WIDTH; + *item_y = start_row * day_view->row_height; + *item_h = (end_row - start_row + 1) * day_view->row_height; + + return TRUE; +} + + +gboolean +e_day_view_get_long_event_position (EDayView *day_view, + gint event_num, + gint *start_day, + gint *end_day, + gint *item_x, + gint *item_y, + gint *item_w, + gint *item_h) +{ + EDayViewEvent *event; + + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + /* If the event is flagged as not displayed, return FALSE. */ + if (event->num_columns == 0) + return FALSE; + + if (!e_day_view_find_long_event_days (day_view, event, + start_day, end_day)) + return FALSE; + + /* If the event is being resize, use the resize position. */ + if (day_view->resize_drag_pos != E_DAY_VIEW_POS_NONE + && day_view->resize_event_day == E_DAY_VIEW_LONG_EVENT + && day_view->resize_event_num == event_num) { + if (day_view->resize_drag_pos == E_DAY_VIEW_POS_LEFT_EDGE) + *start_day = day_view->resize_start_row; + else if (day_view->resize_drag_pos == E_DAY_VIEW_POS_RIGHT_EDGE) + *end_day = day_view->resize_end_row; + } + + *item_x = day_view->day_offsets[*start_day] + E_DAY_VIEW_BAR_WIDTH; + *item_w = day_view->day_offsets[*end_day + 1] - *item_x + - E_DAY_VIEW_GAP_WIDTH; + *item_y = (event->start_row_or_col + 1) * day_view->top_row_height; + *item_h = day_view->top_row_height - E_DAY_VIEW_TOP_CANVAS_Y_GAP; + return TRUE; +} + + +/* Converts a position within the entire top canvas to a day & event and + a place within the event if appropriate. */ +static EDayViewPosition +e_day_view_convert_position_in_top_canvas (EDayView *day_view, + gint x, + gint y, + gint *day_return, + gint *event_num_return) +{ + EDayViewEvent *event; + gint day, row, col; + gint event_num, start_day, end_day, item_x, item_y, item_w, item_h; + + if (x < 0 || y < 0) + return E_DAY_VIEW_POS_OUTSIDE; + + row = y / day_view->top_row_height - 1; + + day = -1; + for (col = 1; col <= day_view->days_shown; col++) { + if (x < day_view->day_offsets[col]) { + day = col - 1; + break; + } + } + if (day == -1) + return E_DAY_VIEW_POS_OUTSIDE; + + *day_return = day; + + /* If only the grid position is wanted, return. */ + if (event_num_return == NULL) + return E_DAY_VIEW_POS_NONE; + + for (event_num = 0; event_num < day_view->long_events->len; + event_num++) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + + if (event->start_row_or_col != row) + continue; + + if (!e_day_view_get_long_event_position (day_view, event_num, + &start_day, &end_day, + &item_x, &item_y, + &item_w, &item_h)) + continue; + + if (x < item_x) + continue; + + if (x >= item_x + item_w) + continue; + + *event_num_return = event_num; + + if (x < item_x + E_DAY_VIEW_LONG_EVENT_BORDER_WIDTH + + E_DAY_VIEW_LONG_EVENT_X_PAD) + return E_DAY_VIEW_POS_LEFT_EDGE; + + if (x >= item_x + item_w - E_DAY_VIEW_LONG_EVENT_BORDER_WIDTH + - E_DAY_VIEW_LONG_EVENT_X_PAD) + return E_DAY_VIEW_POS_RIGHT_EDGE; + + return E_DAY_VIEW_POS_EVENT; + } + + return E_DAY_VIEW_POS_NONE; +} + + +/* Converts a position within the entire main canvas to a day, row, event and + a place within the event if appropriate. */ +static EDayViewPosition +e_day_view_convert_position_in_main_canvas (EDayView *day_view, + gint x, + gint y, + gint *day_return, + gint *row_return, + gint *event_num_return) +{ + gint day, row, col, event_num; + gint item_x, item_y, item_w, item_h; + + /* Check the position is inside the canvas, and determine the day + and row. */ + if (x < 0 || y < 0) + return E_DAY_VIEW_POS_OUTSIDE; + + row = y / day_view->row_height; + if (row >= day_view->rows) + return E_DAY_VIEW_POS_OUTSIDE; + + day = -1; + for (col = 1; col <= day_view->days_shown; col++) { + if (x < day_view->day_offsets[col]) { + day = col - 1; + break; + } + } + if (day == -1) + return E_DAY_VIEW_POS_OUTSIDE; + + *day_return = day; + *row_return = row; + + /* If only the grid position is wanted, return. */ + if (event_num_return == NULL) + return E_DAY_VIEW_POS_NONE; + + /* Check the selected item first, since the horizontal resizing bars + may be above other events. */ + if (day_view->resize_bars_event_day == day) { + if (e_day_view_get_event_position (day_view, day, + day_view->resize_bars_event_num, + &item_x, &item_y, + &item_w, &item_h)) { + if (x >= item_x && x < item_x + item_w) { + *event_num_return = day_view->resize_bars_event_num; + if (y >= item_y - E_DAY_VIEW_BAR_HEIGHT + && y < item_y + E_DAY_VIEW_EVENT_BORDER_HEIGHT) + return E_DAY_VIEW_POS_TOP_EDGE; + if (y >= item_y + item_h - E_DAY_VIEW_EVENT_BORDER_HEIGHT + && y < item_y + item_h + E_DAY_VIEW_BAR_HEIGHT) + return E_DAY_VIEW_POS_BOTTOM_EDGE; + } + } + } + + /* Try to find the event at the found position. */ + *event_num_return = -1; + for (event_num = 0; event_num < day_view->events[day]->len; + event_num++) { + if (!e_day_view_get_event_position (day_view, day, event_num, + &item_x, &item_y, + &item_w, &item_h)) + continue; + + if (x < item_x || x >= item_x + item_w + || y < item_y || y >= item_y + item_h) + continue; + + *event_num_return = event_num; + + if (x < item_x + E_DAY_VIEW_BAR_WIDTH) + return E_DAY_VIEW_POS_LEFT_EDGE; + + if (y < item_y + E_DAY_VIEW_EVENT_BORDER_HEIGHT + + E_DAY_VIEW_EVENT_Y_PAD) + return E_DAY_VIEW_POS_TOP_EDGE; + + if (y >= item_y + item_h - E_DAY_VIEW_EVENT_BORDER_HEIGHT + - E_DAY_VIEW_EVENT_Y_PAD) + return E_DAY_VIEW_POS_BOTTOM_EDGE; + + return E_DAY_VIEW_POS_EVENT; + } + + return E_DAY_VIEW_POS_NONE; +} + + +static gint +e_day_view_on_top_canvas_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + EDayView *day_view) +{ + gint scroll_x, scroll_y; + + g_print ("In e_day_view_on_top_canvas_drag_motion\n"); + + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), + &scroll_x, &scroll_y); + day_view->drag_event_x = x + scroll_x; + day_view->drag_event_y = y + scroll_y; + + e_day_view_reshape_top_canvas_drag_item (day_view); + + return TRUE; +} + + +static void +e_day_view_reshape_top_canvas_drag_item (EDayView *day_view) +{ + EDayViewPosition pos; + gint x, y, day; + + /* Calculate the day & start row of the event being dragged, using + the current mouse position. */ + x = day_view->drag_event_x; + y = day_view->drag_event_y; + pos = e_day_view_convert_position_in_top_canvas (day_view, x, y, + &day, NULL); + /* This shouldn't really happen in a drag. */ + if (pos == E_DAY_VIEW_POS_OUTSIDE) + return; + + if (day_view->drag_event_day == E_DAY_VIEW_LONG_EVENT) + day -= day_view->drag_event_offset; + day = MAX (day, 0); + + e_day_view_update_top_canvas_drag (day_view, day); +} + + +static void +e_day_view_update_top_canvas_drag (EDayView *day_view, + gint day) +{ + EDayViewEvent *event = NULL; + gint row, num_days, start_day, end_day; + gdouble item_x, item_y, item_w, item_h; + GdkFont *font; + gchar *text; + + + /* Calculate the event's position. If the event is in the same + position we started in, we use the same columns. */ + row = day_view->rows_in_top_display + 1; + num_days = 1; + + if (day_view->drag_event_day == E_DAY_VIEW_LONG_EVENT) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + day_view->drag_event_num); + row = event->start_row_or_col + 1; + + if (!e_day_view_find_long_event_days (day_view, event, + &start_day, &end_day)) + return; + + num_days = end_day - start_day + 1; + + /* Make sure we don't go off the screen. */ + day = MIN (day, day_view->days_shown - num_days); + + } else if (day_view->drag_event_day != -1) { + event = &g_array_index (day_view->events[day_view->drag_event_day], + EDayViewEvent, + day_view->drag_event_num); + } + + /* If the position hasn't changed, just return. */ + if (day_view->drag_last_day == day + && (day_view->drag_long_event_item->object.flags + & GNOME_CANVAS_ITEM_VISIBLE)) + return; + + day_view->drag_last_day = day; + + + item_x = day_view->day_offsets[day] + E_DAY_VIEW_BAR_WIDTH; + item_w = day_view->day_offsets[day + num_days] - item_x + - E_DAY_VIEW_GAP_WIDTH; + item_y = row * day_view->top_row_height; + item_h = day_view->top_row_height - E_DAY_VIEW_TOP_CANVAS_Y_GAP; + + + g_print ("Moving to %g,%g %gx%g\n", item_x, item_y, item_w, item_h); + + /* Set the positions of the event & associated items. */ + gnome_canvas_item_set (day_view->drag_long_event_rect_item, + "x1", item_x, + "y1", item_y, + "x2", item_x + item_w - 1, + "y2", item_y + item_h - 1, + NULL); + + font = GTK_WIDGET (day_view)->style->font; + gnome_canvas_item_set (day_view->drag_long_event_item, + "font_gdk", font, + "x", item_x + E_DAY_VIEW_LONG_EVENT_BORDER_WIDTH + E_DAY_VIEW_LONG_EVENT_X_PAD, + "y", item_y + E_DAY_VIEW_LONG_EVENT_BORDER_HEIGHT + E_DAY_VIEW_LONG_EVENT_Y_PAD, + "clip_width", item_w - (E_DAY_VIEW_LONG_EVENT_BORDER_WIDTH + E_DAY_VIEW_LONG_EVENT_X_PAD) * 2, + "clip_height", item_h - (E_DAY_VIEW_LONG_EVENT_BORDER_HEIGHT + E_DAY_VIEW_LONG_EVENT_Y_PAD) * 2, + NULL); + + if (!(day_view->drag_long_event_rect_item->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) { + gnome_canvas_item_raise_to_top (day_view->drag_long_event_rect_item); + gnome_canvas_item_show (day_view->drag_long_event_rect_item); + } + + /* Set the text, if necessary. We don't want to set the text every + time it moves, so we check if it is currently invisible and only + set the text then. */ + if (!(day_view->drag_long_event_item->object.flags + & GNOME_CANVAS_ITEM_VISIBLE)) { + if (event) + text = event->ico->summary; + else + text = NULL; + + gnome_canvas_item_set (day_view->drag_long_event_item, + "text", text ? text : "", + NULL); + gnome_canvas_item_raise_to_top (day_view->drag_long_event_item); + gnome_canvas_item_show (day_view->drag_long_event_item); + } +} + + +static gint +e_day_view_on_main_canvas_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time, + EDayView *day_view) +{ + gint scroll_x, scroll_y; + + g_print ("In e_day_view_on_main_canvas_drag_motion\n"); + + day_view->last_mouse_x = x; + day_view->last_mouse_y = y; + + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), + &scroll_x, &scroll_y); + day_view->drag_event_x = x + scroll_x; + day_view->drag_event_y = y + scroll_y; + + e_day_view_reshape_main_canvas_drag_item (day_view); + e_day_view_reshape_main_canvas_resize_bars (day_view); + + e_day_view_check_auto_scroll (day_view, y); + + return TRUE; +} + + +static void +e_day_view_reshape_main_canvas_drag_item (EDayView *day_view) +{ + EDayViewPosition pos; + gint x, y, day, row; + + /* Calculate the day & start row of the event being dragged, using + the current mouse position. */ + x = day_view->drag_event_x; + y = day_view->drag_event_y; + pos = e_day_view_convert_position_in_main_canvas (day_view, x, y, + &day, &row, NULL); + /* This shouldn't really happen in a drag. */ + if (pos == E_DAY_VIEW_POS_OUTSIDE) + return; + + if (day_view->drag_event_day != -1 + && day_view->drag_event_day != E_DAY_VIEW_LONG_EVENT) + row -= day_view->drag_event_offset; + row = MAX (row, 0); + + e_day_view_update_main_canvas_drag (day_view, row, day); +} + + +static void +e_day_view_update_main_canvas_drag (EDayView *day_view, + gint row, + gint day) +{ + EDayViewEvent *event = NULL; + gint cols_in_row, start_col, num_columns, num_rows, start_row, end_row; + gdouble item_x, item_y, item_w, item_h; + GdkFont *font; + gchar *text; + + /* If the position hasn't changed, just return. */ + if (day_view->drag_last_day == day + && day_view->drag_last_row == row + && (day_view->drag_item->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) + return; + + day_view->drag_last_day = day; + day_view->drag_last_row = row; + + /* Calculate the event's position. If the event is in the same + position we started in, we use the same columns. */ + cols_in_row = 1; + start_col = 0; + num_columns = 1; + num_rows = 1; + + if (day_view->drag_event_day == E_DAY_VIEW_LONG_EVENT) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + day_view->drag_event_num); + } else if (day_view->drag_event_day != -1) { + event = &g_array_index (day_view->events[day_view->drag_event_day], + EDayViewEvent, + day_view->drag_event_num); + start_row = event->start_minute / day_view->mins_per_row; + end_row = (event->end_minute - 1) / day_view->mins_per_row; + num_rows = end_row - start_row + 1; + } + + if (day_view->drag_event_day == day && start_row == row) { + cols_in_row = day_view->cols_per_row[day][row]; + start_col = event->start_row_or_col; + num_columns = event->num_columns; + } + + item_x = day_view->day_offsets[day] + + day_view->day_widths[day] * start_col / cols_in_row; + item_w = day_view->day_widths[day] * num_columns / cols_in_row + - E_DAY_VIEW_GAP_WIDTH; + item_y = row * day_view->row_height; + item_h = num_rows * day_view->row_height; + + g_print ("Moving to %g,%g %gx%g\n", item_x, item_y, item_w, item_h); + + /* Set the positions of the event & associated items. */ + gnome_canvas_item_set (day_view->drag_rect_item, + "x1", item_x + E_DAY_VIEW_BAR_WIDTH - 1, + "y1", item_y, + "x2", item_x + item_w - 1, + "y2", item_y + item_h - 1, + NULL); + + gnome_canvas_item_set (day_view->drag_bar_item, + "x1", item_x, + "y1", item_y, + "x2", item_x + E_DAY_VIEW_BAR_WIDTH - 1, + "y2", item_y + item_h - 1, + NULL); + + font = GTK_WIDGET (day_view)->style->font; + gnome_canvas_item_set (day_view->drag_item, + "font_gdk", font, + "x", item_x + E_DAY_VIEW_BAR_WIDTH + E_DAY_VIEW_EVENT_X_PAD, + "y", item_y + E_DAY_VIEW_EVENT_BORDER_HEIGHT + E_DAY_VIEW_EVENT_Y_PAD, + "clip_width", item_w - E_DAY_VIEW_BAR_WIDTH - E_DAY_VIEW_EVENT_X_PAD * 2, + "clip_height", item_h - (E_DAY_VIEW_EVENT_BORDER_HEIGHT + E_DAY_VIEW_EVENT_Y_PAD) * 2, + NULL); + + if (!(day_view->drag_bar_item->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) { + gnome_canvas_item_raise_to_top (day_view->drag_bar_item); + gnome_canvas_item_show (day_view->drag_bar_item); + } + + if (!(day_view->drag_rect_item->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) { + gnome_canvas_item_raise_to_top (day_view->drag_rect_item); + gnome_canvas_item_show (day_view->drag_rect_item); + } + + /* Set the text, if necessary. We don't want to set the text every + time it moves, so we check if it is currently invisible and only + set the text then. */ + if (!(day_view->drag_item->object.flags & GNOME_CANVAS_ITEM_VISIBLE)) { + if (event) + text = event->ico->summary; + else + text = NULL; + + gnome_canvas_item_set (day_view->drag_item, + "text", text ? text : "", + NULL); + gnome_canvas_item_raise_to_top (day_view->drag_item); + gnome_canvas_item_show (day_view->drag_item); + } +} + + +static void +e_day_view_on_top_canvas_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + EDayView *day_view) +{ + g_print ("In e_day_view_on_top_canvas_drag_leave\n"); + + day_view->drag_last_day = -1; + + gnome_canvas_item_hide (day_view->drag_long_event_rect_item); + gnome_canvas_item_hide (day_view->drag_long_event_item); +} + + +static void +e_day_view_on_main_canvas_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time, + EDayView *day_view) +{ + g_print ("In e_day_view_on_main_canvas_drag_leave\n"); + + day_view->drag_last_day = -1; + + e_day_view_stop_auto_scroll (day_view); + + gnome_canvas_item_hide (day_view->drag_rect_item); + gnome_canvas_item_hide (day_view->drag_bar_item); + gnome_canvas_item_hide (day_view->drag_item); + + /* Hide the resize bars if they are being used in the drag. */ + if (day_view->drag_event_day == day_view->resize_bars_event_day + && day_view->drag_event_num == day_view->resize_bars_event_num) { + gnome_canvas_item_hide (day_view->main_canvas_top_resize_bar_item); + gnome_canvas_item_hide (day_view->main_canvas_bottom_resize_bar_item); + } +} + + +static void +e_day_view_on_drag_begin (GtkWidget *widget, + GdkDragContext *context, + EDayView *day_view) +{ + EDayViewEvent *event; + gint day, event_num; + + g_print ("In e_day_view_on_main_canvas_drag_begin\n"); + + day = day_view->drag_event_day; + event_num = day_view->drag_event_num; + + /* These should both be set. */ + g_return_if_fail (day != -1); + g_return_if_fail (event_num != -1); + + if (day == E_DAY_VIEW_LONG_EVENT) + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + else + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + + /* Hide the text item, since it will be shown in the special drag + items. */ + gnome_canvas_item_hide (event->canvas_item); +} + + +static void +e_day_view_on_drag_end (GtkWidget *widget, + GdkDragContext *context, + EDayView *day_view) +{ + EDayViewEvent *event; + gint day, event_num; + + g_print ("In e_day_view_on_main_canvas_drag_end\n"); + + day = day_view->drag_event_day; + event_num = day_view->drag_event_num; + + /* If the calendar has already been updated in drag_data_received() + we just return. */ + if (day == -1 || event_num == -1) + return; + + if (day == E_DAY_VIEW_LONG_EVENT) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + event_num); + gtk_widget_queue_draw (day_view->top_canvas); + } else { + event = &g_array_index (day_view->events[day], EDayViewEvent, + event_num); + gtk_widget_queue_draw (day_view->main_canvas); + } + + /* Show the text item again. */ + gnome_canvas_item_show (event->canvas_item); + + day_view->drag_event_day = -1; + day_view->drag_event_num = -1; +} + + +static void +e_day_view_on_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *selection_data, + guint info, + guint time, + EDayView *day_view) +{ + EDayViewEvent *event; + gint day, event_num; + gchar *event_uid; + + g_print ("In e_day_view_on_drag_data_get\n"); + + day = day_view->drag_event_day; + event_num = day_view->drag_event_num; + + /* These should both be set. */ + g_return_if_fail (day != -1); + g_return_if_fail (event_num != -1); + + if (day == E_DAY_VIEW_LONG_EVENT) + event = &g_array_index (day_view->long_events, + EDayViewEvent, event_num); + else + event = &g_array_index (day_view->events[day], + EDayViewEvent, event_num); + + event_uid = event->ico->uid; + g_return_if_fail (event_uid != NULL); + + if (info == TARGET_CALENDAR_EVENT) { + gtk_selection_data_set (selection_data, selection_data->target, + 8, event_uid, strlen (event_uid)); + } +} + + +static void +e_day_view_on_top_canvas_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + EDayView *day_view) +{ + EDayViewEvent *event; + EDayViewPosition pos; + gint day, row, scroll_x, scroll_y, start_day, end_day, num_days; + gchar *event_uid; + + g_print ("In e_day_view_on_top_canvas_drag_data_received\n"); + + if ((data->length >= 0) && (data->format == 8)) { + pos = e_day_view_convert_position_in_top_canvas (day_view, + x, y, &day, + NULL); + if (pos != E_DAY_VIEW_POS_OUTSIDE) { + num_days = 1; + if (day_view->drag_event_day == E_DAY_VIEW_LONG_EVENT) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + day_view->drag_event_num); + day -= day_view->drag_event_offset; + day = MAX (day, 0); + + e_day_view_find_long_event_days (day_view, + event, + &start_day, + &end_day); + num_days = end_day - start_day + 1; + /* Make sure we don't go off the screen. */ + day = MIN (day, day_view->days_shown - num_days); + } else if (day_view->drag_event_day != -1) { + event = &g_array_index (day_view->events[day_view->drag_event_day], + EDayViewEvent, + day_view->drag_event_num); + } + + event_uid = data->data; + + g_print ("Dropped Day:%i UID:%s\n", day, event_uid); + + if (!event_uid || !event->ico->uid + || strcmp (event_uid, event->ico->uid)) + g_warning ("Unexpected event UID"); + + event->ico->dtstart = day_view->day_starts[day]; + event->ico->dtend = day_view->day_starts[day + num_days]; + + gtk_drag_finish (context, TRUE, TRUE, time); + + /* Reset this since it will be invalid. */ + day_view->drag_event_day = -1; + + /* Notify calendar of change */ + gnome_calendar_object_changed (day_view->calendar, + event->ico, + CHANGE_DATES); + save_default_calendar (day_view->calendar); + + return; + } + } + + gtk_drag_finish (context, FALSE, FALSE, time); +} + + +static void +e_day_view_on_main_canvas_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *data, + guint info, + guint time, + EDayView *day_view) +{ + EDayViewEvent *event; + EDayViewPosition pos; + gint day, start_row, end_row, num_rows; + gchar *event_uid; + + g_print ("In e_day_view_on_main_canvas_drag_data_received\n"); + + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (widget), + &scroll_x, &scroll_y); + x += scroll_x; + y += scroll_y; + + if ((data->length >= 0) && (data->format == 8)) { + pos = e_day_view_convert_position_in_main_canvas (day_view, + x, y, &day, + &row, NULL); + if (pos != E_DAY_VIEW_POS_OUTSIDE) { + num_rows = 1; + if (day_view->drag_event_day == E_DAY_VIEW_LONG_EVENT) { + event = &g_array_index (day_view->long_events, EDayViewEvent, + day_view->drag_event_num); + } else if (day_view->drag_event_day != -1) { + event = &g_array_index (day_view->events[day_view->drag_event_day], + EDayViewEvent, + day_view->drag_event_num); + row -= day_view->drag_event_offset; + + /* Calculate time offset from start row. */ + start_row = event->start_minute / day_view->mins_per_row; + end_row = (event->end_minute - 1) / day_view->mins_per_row; + num_rows = end_row - start_row + 1; + } + + event_uid = data->data; + + g_print ("Dropped Day:%i Row:%i UID:%s\n", day, row, + event_uid); + + if (!event_uid || !event->ico->uid + || strcmp (event_uid, event->ico->uid)) + g_warning ("Unexpected event UID"); + + event->ico->dtstart = e_day_view_convert_grid_position_to_time (day_view, day, row); + event->ico->dtend = e_day_view_convert_grid_position_to_time (day_view, day, row + num_rows); + + gtk_drag_finish (context, TRUE, TRUE, time); + + /* Reset this since it will be invalid. */ + day_view->drag_event_day = -1; + + /* Notify calendar of change */ + gnome_calendar_object_changed (day_view->calendar, + event->ico, + CHANGE_DATES); + save_default_calendar (day_view->calendar); + + return; + } + } + + gtk_drag_finish (context, FALSE, FALSE, time); +} + diff --git a/calendar/gui/e-day-view.h b/calendar/gui/e-day-view.h new file mode 100644 index 0000000000..b829e76384 --- /dev/null +++ b/calendar/gui/e-day-view.h @@ -0,0 +1,463 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@helixcode.com> + * + * 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 + */ +#ifndef _E_DAY_VIEW_H_ +#define _E_DAY_VIEW_H_ + +#include <time.h> +#include <gtk/gtktable.h> +#include <libgnomeui/gnome-canvas.h> + +#include "gnome-cal.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * EDayView - displays the Day & Work-Week views of the calendar. + */ + +/* The maximum number of days shown. We use 7 since we only show 1 week max. */ +#define E_DAY_VIEW_MAX_DAYS 7 + +/* This is used as a special code to signify a long event instead of the day + of a normal event. */ +#define E_DAY_VIEW_LONG_EVENT E_DAY_VIEW_MAX_DAYS + +/* The maximum number of columns of appointments within a day. */ +#define E_DAY_VIEW_MAX_COLUMNS 6 + +/* The width of the gap between appointments. This should be at least + E_DAY_VIEW_BAR_WIDTH. */ +#define E_DAY_VIEW_GAP_WIDTH 6 + +/* The width of the bars down the left of each column and appointment. + This includes the borders on each side of it. */ +#define E_DAY_VIEW_BAR_WIDTH 6 + +/* The height of the horizontal bar above & beneath the selected event. + This includes the borders on the top and bottom. */ +#define E_DAY_VIEW_BAR_HEIGHT 6 + +/* The size of the reminder & recurrence icons, and padding around them. */ +#define E_DAY_VIEW_ICON_WIDTH 16 +#define E_DAY_VIEW_ICON_HEIGHT 16 +#define E_DAY_VIEW_ICON_X_PAD 0 +#define E_DAY_VIEW_ICON_Y_PAD 0 + +/* The size of the border around the event. */ +#define E_DAY_VIEW_EVENT_BORDER_WIDTH 1 +#define E_DAY_VIEW_EVENT_BORDER_HEIGHT 1 + +/* The padding on each side of the event text. */ +#define E_DAY_VIEW_EVENT_X_PAD 2 +#define E_DAY_VIEW_EVENT_Y_PAD 1 + +/* The padding on each side of the event text for events in the top canvas. */ +#define E_DAY_VIEW_LONG_EVENT_X_PAD 2 +#define E_DAY_VIEW_LONG_EVENT_Y_PAD 2 + +/* The size of the border around the long events in the top canvas. */ +#define E_DAY_VIEW_LONG_EVENT_BORDER_WIDTH 1 +#define E_DAY_VIEW_LONG_EVENT_BORDER_HEIGHT 1 + +/* The space between the time and the icon/text in the top canvas. */ +#define E_DAY_VIEW_LONG_EVENT_TIME_X_PAD 2 + +/* The gap between rows in the top canvas. */ +#define E_DAY_VIEW_TOP_CANVAS_Y_GAP 2 + + +/* These are used to specify the type of an appointment. They match those + used in EMeetingTimeSelector. */ +typedef enum +{ + E_DAY_VIEW_BUSY_TENTATIVE = 0, + E_DAY_VIEW_BUSY_OUT_OF_OFFICE = 1, + E_DAY_VIEW_BUSY_BUSY = 2, + + E_DAY_VIEW_BUSY_LAST = 3 +} EDayViewBusyType; + +/* This is used to specify the format used when displaying the dates. + The full format is like 'September 12'. The abbreviated format is like + 'Sep 12'. The short format is like '12'. The actual format used is + determined in style_set(), once we know the font being used. */ +typedef enum +{ + E_DAY_VIEW_DATE_FULL, + E_DAY_VIEW_DATE_ABBREVIATED, + E_DAY_VIEW_DATE_SHORT +} EDayViewDateFormat; + +/* These index our colors array. */ +typedef enum +{ + E_DAY_VIEW_COLOR_BG_WORKING, + E_DAY_VIEW_COLOR_BG_NOT_WORKING, + E_DAY_VIEW_COLOR_EVENT_VBAR, + + E_DAY_VIEW_COLOR_EVENT_BACKGROUND, + E_DAY_VIEW_COLOR_EVENT_BORDER, + + E_DAY_VIEW_COLOR_LAST +} EDayViewColors; + +/* These specify which part of the selection we are dragging, if any. */ +typedef enum +{ + E_DAY_VIEW_DRAG_NONE, + E_DAY_VIEW_DRAG_START, + E_DAY_VIEW_DRAG_END +} EDayViewDragPosition; + +/* Specifies the position of the mouse. */ +typedef enum +{ + E_DAY_VIEW_POS_OUTSIDE, + E_DAY_VIEW_POS_NONE, + E_DAY_VIEW_POS_EVENT, + E_DAY_VIEW_POS_LEFT_EDGE, + E_DAY_VIEW_POS_RIGHT_EDGE, + E_DAY_VIEW_POS_TOP_EDGE, + E_DAY_VIEW_POS_BOTTOM_EDGE +} EDayViewPosition; + +typedef struct _EDayViewEvent EDayViewEvent; +struct _EDayViewEvent { + iCalObject *ico; + time_t start; + time_t end; + guint8 start_row_or_col;/* The start column for normal events, or the + start row for long events. */ + guint8 num_columns; /* 0 indicates not displayed. For long events + this is just 1 if the event is shown. */ + guint16 start_minute; /* Offsets from the start of the display. */ + guint16 end_minute; + GnomeCanvasItem *canvas_item; +}; + + +#define E_DAY_VIEW(obj) GTK_CHECK_CAST (obj, e_day_view_get_type (), EDayView) +#define E_DAY_VIEW_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, e_day_view_get_type (), EDayViewClass) +#define E_IS_DAY_VIEW(obj) GTK_CHECK_TYPE (obj, e_day_view_get_type ()) + + +typedef struct _EDayView EDayView; +typedef struct _EDayViewClass EDayViewClass; + +struct _EDayView +{ + GtkTable table; + + /* The top canvas where the dates and long appointments are shown. */ + GtkWidget *top_canvas; + GnomeCanvasItem *top_canvas_item; + + /* The main canvas where the rest of the appointments are shown. */ + GtkWidget *main_canvas; + GnomeCanvasItem *main_canvas_item; + + /* The canvas displaying the times of the day. */ + GtkWidget *time_canvas; + GnomeCanvasItem *time_canvas_item; + + GtkWidget *vscrollbar; + + /* The calendar we are associated with. */ + GnomeCalendar *calendar; + + /* The start and end of the day shown. */ + time_t lower; + time_t upper; + + /* The number of days we are showing. Usually 1 or 5. Maybe 6 or 7. */ + gint days_shown; + + /* The start of each day & an extra element to hold the last time. */ + time_t day_starts[E_DAY_VIEW_MAX_DAYS + 1]; + + + /* An array of EDayViewEvent elements for the top view and each day. */ + GArray *long_events; + GArray *events[E_DAY_VIEW_MAX_DAYS]; + + /* These are set to FALSE whenever an event in the corresponding array + is changed. Any function that needs the events sorted calls + e_day_view_ensure_events_sorted(). */ + gboolean long_events_sorted; + gboolean events_sorted[E_DAY_VIEW_MAX_DAYS]; + + /* This is TRUE if we need to relayout the events before drawing. */ + gboolean long_events_need_layout; + gboolean need_layout[E_DAY_VIEW_MAX_DAYS]; + + /* This is TRUE if we need to reshape the canvas items, but a full + layout is not necessary. */ + gboolean long_events_need_reshape; + gboolean need_reshape[E_DAY_VIEW_MAX_DAYS]; + + /* The number of minutes per row. 5, 10, 15, 30 or 60. */ + gint mins_per_row; + + /* The number of rows needed, depending on the times shown and the + minutes per row. */ + gint rows; + + /* The height of each row. */ + gint row_height; + + /* The number of rows in the top display. */ + gint rows_in_top_display; + + /* The height of each row in the top canvas. */ + gint top_row_height; + + /* The first and last times shown in the display. The last time isn't + included in the range. Default is 0:00-24:00 */ + gint first_hour_shown; + gint first_minute_shown; + gint last_hour_shown; + gint last_minute_shown; + + /* The start and end of the work day, rounded to the nearest row. */ + gint work_day_start_hour; + gint work_day_start_minute; + gint work_day_end_hour; + gint work_day_end_minute; + + /* This is set to TRUE when the widget is created, so it scrolls to + the start of the working day when first shown. */ + gboolean scroll_to_work_day; + + /* This is the width & offset of each of the day columns in the + display. */ + gint day_widths[E_DAY_VIEW_MAX_DAYS]; + gint day_offsets[E_DAY_VIEW_MAX_DAYS + 1]; + + /* An array holding the number of columns in each row, in each day. */ + guint8 cols_per_row[E_DAY_VIEW_MAX_DAYS][12 * 24]; + + /* Sizes of the various time strings. */ + gint large_hour_widths[24]; + gint small_hour_widths[24]; + gint minute_widths[12]; /* intervals of 5 minutes. */ + gint max_small_hour_width; + gint max_large_hour_width; + gint max_minute_width; + gint colon_width; + + /* This specifies how we are displaying the dates at the top. */ + EDayViewDateFormat date_format; + + /* These are the maximum widths of the different types of dates. */ + gint long_format_width; + gint abbreviated_format_width; + + /* The large font use to display the hours. I don't think we need a + fontset since we only display numbers. */ + GdkFont *large_font; + + /* The GC used for painting in different colors. */ + GdkGC *main_gc; + + /* The icons. */ + GdkPixmap *reminder_icon; + GdkBitmap *reminder_mask; + GdkPixmap *recurrence_icon; + GdkBitmap *recurrence_mask; + + /* Colors for drawing. */ + GdkColor colors[E_DAY_VIEW_COLOR_LAST]; + + /* The normal & resizing cursors. */ + GdkCursor *normal_cursor; + GdkCursor *move_cursor; + GdkCursor *resize_width_cursor; + GdkCursor *resize_height_cursor; + + /* This remembers the last cursor set on the window. */ + GdkCursor *last_cursor_set_in_top_canvas; + GdkCursor *last_cursor_set_in_main_canvas; + + /* + * Editing, Selection & Dragging data + */ + + /* The horizontal bars to resize events in the main canvas. */ + GnomeCanvasItem *main_canvas_top_resize_bar_item; + GnomeCanvasItem *main_canvas_bottom_resize_bar_item; + + /* The event currently being edited. The day is -1 if no event is + being edited, or E_DAY_VIEW_LONG_EVENT if a long event is edited. */ + gint editing_event_day; + gint editing_event_num; + + /* This is a GnomeCanvasRect which is placed around an item while it + is being resized, so we can raise it above all other EText items. */ + GnomeCanvasItem *resize_long_event_rect_item; + GnomeCanvasItem *resize_rect_item; + GnomeCanvasItem *resize_bar_item; + + /* The event for which a popup menu is being displayed, as above. */ + gint popup_event_day; + gint popup_event_num; + + /* The currently selected region. If selection_start_col is -1 there is + no current selection. If start_row or end_row is -1 then the + selection is in the top canvas. */ + gint selection_start_col; + gint selection_end_col; + gint selection_start_row; + gint selection_end_row; + + /* This is TRUE if the user is selecting a region on the calendar. */ + EDayViewDragPosition selection_drag_pos; + + /* This is TRUE if the selection is in the top canvas only (i.e. if the + last motion event was in the top canvas). */ + gboolean selection_in_top_canvas; + + /* The last mouse position, relative to the main canvas window. + Used when auto-scrolling to update the selection. */ + gint last_mouse_x; + gint last_mouse_y; + + /* Auto-scroll info for when selecting an area or dragging an item. */ + gint auto_scroll_timeout_id; + gint auto_scroll_delay; + gboolean auto_scroll_up; + + /* These are used for the resize bars. */ + gint resize_bars_event_day; + gint resize_bars_event_num; + + /* These are used when resizing events. */ + gint resize_event_day; + gint resize_event_num; + EDayViewPosition resize_drag_pos; + gint resize_start_row; + gint resize_end_row; + + /* This is the event the mouse button was pressed on. If the button + is released we start editing it, but if the mouse is dragged we set + this to -1. */ + gint pressed_event_day; + gint pressed_event_num; + + /* These are used when dragging events. If drag_event_day is not -1 we + know that we are dragging one of the EDayView events around. */ + gint drag_event_day; + gint drag_event_num; + + /* The last mouse position when dragging, in the entire canvas. */ + gint drag_event_x; + gint drag_event_y; + + /* The offset of the mouse from the top of the event, in rows. + In the top canvas this is the offset from the left, in days. */ + gint drag_event_offset; + + /* The last day & row dragged to, so we know when we need to update + the dragged event's position. */ + gint drag_last_day; + gint drag_last_row; + + /* This is a GnomeCanvasRect which is placed around an item while it + is being resized, so we can raise it above all other EText items. */ + GnomeCanvasItem *drag_long_event_rect_item; + GnomeCanvasItem *drag_long_event_item; + GnomeCanvasItem *drag_rect_item; + GnomeCanvasItem *drag_bar_item; + GnomeCanvasItem *drag_item; +}; + +struct _EDayViewClass +{ + GtkTableClass parent_class; +}; + + +GtkType e_day_view_get_type (void); +GtkWidget* e_day_view_new (void); + +void e_day_view_set_calendar (EDayView *day_view, + GnomeCalendar *calendar); + +void e_day_view_set_interval (EDayView *day_view, + time_t lower, + time_t upper); + +void e_day_view_update_event (EDayView *fullday, + iCalObject *ico, + int flags); + + + + +gint e_day_view_get_days_shown (EDayView *day_view); +void e_day_view_set_days_shown (EDayView *day_view, + gint days_shown); + +gint e_day_view_get_mins_per_row (EDayView *day_view); +void e_day_view_set_mins_per_row (EDayView *day_view, + gint mins_per_row); + + + +/* + * Internal functions called by the associated canvas items. + */ +void e_day_view_check_layout (EDayView *day_view); +gint e_day_view_convert_time_to_row (EDayView *day_view, + gint hour, + gint minute); +gint e_day_view_convert_time_to_position (EDayView *day_view, + gint hour, + gint minute); +gboolean e_day_view_get_event_position (EDayView *day_view, + gint day, + gint event_num, + gint *item_x, + gint *item_y, + gint *item_w, + gint *item_h); +gboolean e_day_view_get_long_event_position (EDayView *day_view, + gint event_num, + gint *start_day, + gint *end_day, + gint *item_x, + gint *item_y, + gint *item_w, + gint *item_h); +gboolean e_day_view_find_long_event_days (EDayView *day_view, + EDayViewEvent *event, + gint *start_day, + gint *end_day); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _E_DAY_VIEW_H_ */ |