/* * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with the program; if not, see * * * Authors: * Damon Chaplin * Rodrigo Moya * * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) * */ /* * EWeekView - displays the Week & Month views of the calendar. */ #ifdef HAVE_CONFIG_H #include #endif #include "e-week-view.h" #include #include #include #include #include "dialogs/delete-comp.h" #include "dialogs/delete-error.h" #include "dialogs/send-comp.h" #include "dialogs/cancel-comp.h" #include "dialogs/recur-comp.h" #include "dialogs/goto-dialog.h" #include "calendar-config.h" #include "calendar-config.h" #include "comp-util.h" #include "e-cal-model-calendar.h" #include "e-week-view-event-item.h" #include "e-week-view-layout.h" #include "e-week-view-main-item.h" #include "e-week-view-titles-item.h" #include "ea-calendar.h" #include "itip-utils.h" #include "misc.h" #include "print.h" /* Images */ #include "art/jump.xpm" #define E_WEEK_VIEW_GET_PRIVATE(obj) \ (G_TYPE_INSTANCE_GET_PRIVATE \ ((obj), E_TYPE_WEEK_VIEW, EWeekViewPrivate)) #define E_WEEK_VIEW_SMALL_FONT_PTSIZE 7 #define E_WEEK_VIEW_JUMP_BUTTON_WIDTH 16 #define E_WEEK_VIEW_JUMP_BUTTON_HEIGHT 8 #define E_WEEK_VIEW_JUMP_BUTTON_X_PAD 3 #define E_WEEK_VIEW_JUMP_BUTTON_Y_PAD 3 #define E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS -1 /* The timeout before we do a layout, so we don't do a layout for each event * we get from the server. */ #define E_WEEK_VIEW_LAYOUT_TIMEOUT 100 struct _EWeekViewPrivate { /* The first day shown in the view. */ GDate first_day_shown; /* If we are displaying multiple weeks in rows. If this is * FALSE only one week is shown, with a different layout. */ gboolean multi_week_view; /* How many weeks we are showing. This is only relevant if * multi_week_view is TRUE. */ gint weeks_shown; /* If Sat & Sun are compressed. Only applicable in month view, * since they are always compressed into 1 cell in week view. */ gboolean compress_weekend; /* Whether we show event end times. */ gboolean show_event_end_times; /* Whether to update the base date when the time range changes. */ gboolean update_base_date; /* The first day of the week we display. This will usually be * week_start_day, but if the Sat & Sun are compressed and the * week starts on Sunday then we have to use Saturday. */ GDateWeekday display_start_day; }; typedef struct { EWeekView *week_view; ECalModelComponent *comp_data; } AddEventData; static void e_week_view_set_colors (EWeekView *week_view, GtkWidget *widget); static void e_week_view_recalc_cell_sizes (EWeekView *week_view); static gboolean e_week_view_get_next_tab_event (EWeekView *week_view, GtkDirectionType direction, gint current_event_num, gint current_span_num, gint *next_event_num, gint *next_span_num); static void e_week_view_update_query (EWeekView *week_view); static gboolean e_week_view_on_button_press (GtkWidget *widget, GdkEvent *button_event, EWeekView *week_view); static gboolean e_week_view_on_button_release (GtkWidget *widget, GdkEvent *button_event, EWeekView *week_view); static gboolean e_week_view_on_scroll (GtkWidget *widget, GdkEventScroll *scroll, EWeekView *week_view); static gboolean e_week_view_on_motion (GtkWidget *widget, GdkEventMotion *event, EWeekView *week_view); static gint e_week_view_convert_position_to_day (EWeekView *week_view, gint x, gint y); static void e_week_view_update_selection (EWeekView *week_view, gint day); static void e_week_view_free_events (EWeekView *week_view); static gboolean e_week_view_add_event (ECalComponent *comp, time_t start, time_t end, gboolean prepend, gpointer data); static void e_week_view_check_layout (EWeekView *week_view); static void e_week_view_ensure_events_sorted (EWeekView *week_view); static void e_week_view_reshape_events (EWeekView *week_view); static void e_week_view_reshape_event_span (EWeekView *week_view, gint event_num, gint span_num); static void e_week_view_recalc_day_starts (EWeekView *week_view, time_t lower); static void e_week_view_on_editing_started (EWeekView *week_view, GnomeCanvasItem *item); static void e_week_view_on_editing_stopped (EWeekView *week_view, GnomeCanvasItem *item); static gboolean e_week_view_find_event_from_uid (EWeekView *week_view, ECalClient *client, const gchar *uid, const gchar *rid, gint *event_num_return); typedef gboolean (* EWeekViewForeachEventCallback) (EWeekView *week_view, gint event_num, gpointer data); static void e_week_view_foreach_event_with_uid (EWeekView *week_view, const gchar *uid, EWeekViewForeachEventCallback callback, gpointer data); static gboolean e_week_view_on_text_item_event (GnomeCanvasItem *item, GdkEvent *event, EWeekView *week_view); static gboolean e_week_view_event_move (ECalendarView *cal_view, ECalViewMoveDirection direction); static gint e_week_view_get_day_offset_of_event (EWeekView *week_view, time_t event_time); static void e_week_view_change_event_time (EWeekView *week_view, time_t start_dt, time_t end_dt, gboolean is_all_day); static gboolean e_week_view_on_jump_button_event (GnomeCanvasItem *item, GdkEvent *event, EWeekView *week_view); static gboolean e_week_view_do_key_press (GtkWidget *widget, GdkEventKey *event); static gint e_week_view_get_adjust_days_for_move_up (EWeekView *week_view, gint current_day); static gint e_week_view_get_adjust_days_for_move_down (EWeekView *week_view,gint current_day); static gint e_week_view_get_adjust_days_for_move_left (EWeekView *week_view,gint current_day); static gint e_week_view_get_adjust_days_for_move_right (EWeekView *week_view,gint current_day); static gboolean e_week_view_remove_event_cb (EWeekView *week_view, gint event_num, gpointer data); static gboolean e_week_view_recalc_display_start_day (EWeekView *week_view); static void e_week_view_queue_layout (EWeekView *week_view); static void e_week_view_cancel_layout (EWeekView *week_view); static gboolean e_week_view_layout_timeout_cb (gpointer data); G_DEFINE_TYPE (EWeekView, e_week_view, E_TYPE_CALENDAR_VIEW) enum { PROP_0, PROP_COMPRESS_WEEKEND, PROP_SHOW_EVENT_END_TIMES, PROP_IS_EDITING }; static gint map_left[] = {0, 1, 2, 0, 1, 2, 2}; static gint map_right[] = {3, 4, 5, 3, 4, 5, 6}; static void week_view_process_component (EWeekView *week_view, ECalModelComponent *comp_data) { ECalComponent *comp = NULL; AddEventData add_event_data; /* rid is never used in this function? */ const gchar *uid; gchar *rid = NULL; /* If we don't have a valid date set yet, just return. */ if (!g_date_valid (&week_view->priv->first_day_shown)) return; comp = e_cal_component_new (); if (!e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (comp_data->icalcomp))) { g_object_unref (comp); g_message (G_STRLOC ": Could not set icalcomponent on ECalComponent"); return; } e_cal_component_get_uid (comp, &uid); if (e_cal_component_is_instance (comp)) rid = e_cal_component_get_recurid_as_string (comp); else rid = NULL; /* Add the object */ add_event_data.week_view = week_view; add_event_data.comp_data = comp_data; e_week_view_add_event (comp, comp_data->instance_start, comp_data->instance_end, FALSE, &add_event_data); g_object_unref (comp); g_free (rid); } static void week_view_update_row (EWeekView *week_view, gint row) { ECalModelComponent *comp_data; ECalModel *model; gint event_num; const gchar *uid; gchar *rid = NULL; model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)); comp_data = e_cal_model_get_component_at (model, row); g_return_if_fail (comp_data != NULL); uid = icalcomponent_get_uid (comp_data->icalcomp); if (e_cal_util_component_is_instance (comp_data->icalcomp)) { icalproperty *prop; prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_RECURRENCEID_PROPERTY); if (prop) rid = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (comp_data->icalcomp)); } if (e_week_view_find_event_from_uid (week_view, comp_data->client, uid, rid, &event_num)) e_week_view_remove_event_cb (week_view, event_num, NULL); g_free (rid); week_view_process_component (week_view, comp_data); gtk_widget_queue_draw (week_view->main_canvas); e_week_view_queue_layout (week_view); } static void week_view_model_cell_changed_cb (EWeekView *week_view, gint col, gint row) { if (!E_CALENDAR_VIEW (week_view)->in_focus) { e_week_view_free_events (week_view); week_view->requires_update = TRUE; return; } week_view_update_row (week_view, row); } static void week_view_model_comps_deleted_cb (EWeekView *week_view, gpointer data) { GSList *l, *list = data; /* FIXME Stop editing? */ if (!E_CALENDAR_VIEW (week_view)->in_focus) { e_week_view_free_events (week_view); week_view->requires_update = TRUE; return; } for (l = list; l != NULL; l = g_slist_next (l)) { gint event_num; const gchar *uid; gchar *rid = NULL; ECalModelComponent *comp_data = l->data; uid = icalcomponent_get_uid (comp_data->icalcomp); if (e_cal_util_component_is_instance (comp_data->icalcomp)) { icalproperty *prop; prop = icalcomponent_get_first_property (comp_data->icalcomp, ICAL_RECURRENCEID_PROPERTY); if (prop) rid = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (comp_data->icalcomp)); } if (e_week_view_find_event_from_uid (week_view, comp_data->client, uid, rid, &event_num)) e_week_view_remove_event_cb (week_view, event_num, NULL); g_free (rid); } gtk_widget_queue_draw (week_view->main_canvas); e_week_view_queue_layout (week_view); } static void week_view_model_row_changed_cb (EWeekView *week_view, gint row) { if (!E_CALENDAR_VIEW (week_view)->in_focus) { e_week_view_free_events (week_view); week_view->requires_update = TRUE; return; } week_view_update_row (week_view, row); } static void week_view_model_rows_inserted_cb (EWeekView *week_view, gint row, gint count) { ECalModel *model; gint i; if (!E_CALENDAR_VIEW (week_view)->in_focus) { e_week_view_free_events (week_view); week_view->requires_update = TRUE; return; } model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)); for (i = 0; i < count; i++) { ECalModelComponent *comp_data; comp_data = e_cal_model_get_component_at (model, row + i); if (comp_data == NULL) { g_warning ("comp_data is NULL\n"); continue; } week_view_process_component (week_view, comp_data); } gtk_widget_queue_draw (week_view->main_canvas); e_week_view_queue_layout (week_view); } static void week_view_time_range_changed_cb (EWeekView *week_view, time_t start_time, time_t end_time, ECalModel *model) { GDate date, base_date; GDateWeekday weekday; GDateWeekday display_start_day; guint day_offset, week_start_offset; gint num_days; gboolean update_adjustment_value = FALSE; g_return_if_fail (E_IS_WEEK_VIEW (week_view)); time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); weekday = g_date_get_weekday (&date); display_start_day = e_week_view_get_display_start_day (week_view); /* Convert it to an offset from the start of the display. */ week_start_offset = e_weekday_get_days_between ( display_start_day, weekday); /* Set the day_offset to the result, so we move back to the * start of the week. */ day_offset = week_start_offset; /* Calculate the base date, i.e. the first day shown when the * scrollbar adjustment value is 0. */ base_date = date; g_date_subtract_days (&base_date, day_offset); /* See if we need to update the base date. */ if (!g_date_valid (&week_view->base_date) || e_week_view_get_update_base_date (week_view)) { week_view->base_date = base_date; update_adjustment_value = TRUE; } /* See if we need to update the first day shown. */ if (!g_date_valid (&week_view->priv->first_day_shown) || g_date_compare (&week_view->priv->first_day_shown, &base_date)) { week_view->priv->first_day_shown = base_date; start_time = time_add_day_with_zone ( start_time, -((gint) day_offset), e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); start_time = time_day_begin_with_zone ( start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); e_week_view_recalc_day_starts (week_view, start_time); } /* Reset the adjustment value to 0 if the base address has changed. * Note that we do this after updating first_day_shown so that our * signal handler will not try to reload the events. */ if (update_adjustment_value) { GtkRange *range; GtkAdjustment *adjustment; range = GTK_RANGE (week_view->vscrollbar); adjustment = gtk_range_get_adjustment (range); gtk_adjustment_set_value (adjustment, 0); } if (!E_CALENDAR_VIEW (week_view)->in_focus) { e_week_view_free_events (week_view); week_view->requires_update = TRUE; return; } gtk_widget_queue_draw (week_view->main_canvas); num_days = e_week_view_get_weeks_shown (week_view) * 7; /* FIXME Preserve selection if possible */ if (week_view->selection_start_day == -1 || num_days <= week_view->selection_start_day) e_calendar_view_set_selected_time_range ( E_CALENDAR_VIEW (week_view), start_time, start_time); } static void timezone_changed_cb (ECalModel *cal_model, icaltimezone *old_zone, icaltimezone *new_zone, gpointer user_data) { ECalendarView *cal_view = (ECalendarView *) user_data; GDate *first_day_shown; struct icaltimetype tt = icaltime_null_time (); time_t lower; EWeekView *week_view = (EWeekView *) cal_view; g_return_if_fail (E_IS_WEEK_VIEW (week_view)); first_day_shown = &week_view->priv->first_day_shown; if (!cal_view->in_focus) { e_week_view_free_events (week_view); week_view->requires_update = TRUE; return; } /* If we don't have a valid date set yet, just return. */ if (!g_date_valid (first_day_shown)) return; /* Recalculate the new start of the first week. We just use exactly * the same time, but with the new timezone. */ tt.year = g_date_get_year (first_day_shown); tt.month = g_date_get_month (first_day_shown); tt.day = g_date_get_day (first_day_shown); lower = icaltime_as_timet_with_zone (tt, new_zone); e_week_view_recalc_day_starts (week_view, lower); e_week_view_update_query (week_view); } static void week_view_notify_week_start_day_cb (EWeekView *week_view) { GDate *first_day_shown; first_day_shown = &week_view->priv->first_day_shown; e_week_view_recalc_display_start_day (week_view); /* Recalculate the days shown and reload if necessary. */ if (g_date_valid (first_day_shown)) e_week_view_set_first_day_shown (week_view, first_day_shown); gtk_widget_queue_draw (week_view->titles_canvas); gtk_widget_queue_draw (week_view->main_canvas); } static void month_scroll_by_week_changed_cb (GSettings *settings, const gchar *key, gpointer user_data) { EWeekView *week_view = user_data; g_return_if_fail (week_view != NULL); g_return_if_fail (E_IS_WEEK_VIEW (week_view)); if (e_week_view_get_multi_week_view (week_view) && week_view->month_scroll_by_week != calendar_config_get_month_scroll_by_week ()) { week_view->priv->multi_week_view = FALSE; e_week_view_set_multi_week_view (week_view, TRUE); } } /* FIXME: This is also needed in e-day-view-time-item.c. We should probably use * pango's approximation function, but it needs a language tag. Find out how to * get one of those properly. */ static gint get_digit_width (PangoLayout *layout) { gint digit; gint max_digit_width = 1; for (digit = '0'; digit <= '9'; digit++) { gchar digit_char; gint digit_width; digit_char = digit; pango_layout_set_text (layout, &digit_char, 1); pango_layout_get_pixel_size (layout, &digit_width, NULL); max_digit_width = MAX (max_digit_width, digit_width); } return max_digit_width; } static gint get_string_width (PangoLayout *layout, const gchar *string) { gint width; pango_layout_set_text (layout, string, -1); pango_layout_get_pixel_size (layout, &width, NULL); return width; } static gboolean e_week_view_add_new_event_in_selected_range (EWeekView *week_view, const gchar *initial_text) { ECalClient *client; ECalModel *model; ECalComponent *comp = NULL; icalcomponent *icalcomp; gint event_num; ECalComponentDateTime date; struct icaltimetype itt; time_t dtstart, dtend; const gchar *uid; AddEventData add_event_data; EWeekViewEvent *wvevent; EWeekViewEventSpan *span; gboolean success = FALSE; model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)); client = e_cal_model_ref_default_client (model); /* Check if the client is read only */ if (e_client_is_readonly (E_CLIENT (client))) goto exit; /* Add a new event covering the selected range. */ icalcomp = e_cal_model_create_component_with_defaults (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), TRUE); if (!icalcomp) goto exit; uid = icalcomponent_get_uid (icalcomp); comp = e_cal_component_new (); e_cal_component_set_icalcomponent (comp, icalcomp); dtstart = week_view->day_starts[week_view->selection_start_day]; dtend = week_view->day_starts[week_view->selection_end_day + 1]; date.value = &itt; date.tzid = NULL; /* We use DATE values now, so we don't need the timezone. */ /*date.tzid = icaltimezone_get_tzid (e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)));*/ *date.value = icaltime_from_timet_with_zone (dtstart, TRUE, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); e_cal_component_set_dtstart (comp, &date); *date.value = icaltime_from_timet_with_zone (dtend, TRUE, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); e_cal_component_set_dtend (comp, &date); /* Editor default in week/month view */ e_cal_component_set_transparency (comp, E_CAL_COMPONENT_TRANSP_TRANSPARENT); e_cal_component_set_categories ( comp, e_calendar_view_get_default_category (E_CALENDAR_VIEW (week_view))); /* We add the event locally and start editing it. We don't send it * to the server until the user finishes editing it. */ add_event_data.week_view = week_view; add_event_data.comp_data = NULL; e_week_view_add_event (comp, dtstart, dtend, TRUE, &add_event_data); e_week_view_check_layout (week_view); gtk_widget_queue_draw (week_view->main_canvas); if (!e_week_view_find_event_from_uid (week_view, client, uid, NULL, &event_num)) { g_warning ("Couldn't find event to start editing.\n"); goto exit; } if (!is_array_index_in_bounds (week_view->events, event_num)) goto exit; wvevent = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_array_index_in_bounds (week_view->spans, wvevent->spans_index + 0)) goto exit; span = &g_array_index (week_view->spans, EWeekViewEventSpan, wvevent->spans_index + 0); /* If the event can't be fit on the screen, don't try to edit it. */ if (!span->text_item) { e_week_view_foreach_event_with_uid (week_view, uid, e_week_view_remove_event_cb, NULL); goto exit; } e_week_view_start_editing_event ( week_view, event_num, 0, (gchar *) initial_text); success = TRUE; exit: g_clear_object (&comp); g_clear_object (&client); return success; } static void week_view_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_COMPRESS_WEEKEND: e_week_view_set_compress_weekend ( E_WEEK_VIEW (object), g_value_get_boolean (value)); return; case PROP_SHOW_EVENT_END_TIMES: e_week_view_set_show_event_end_times ( E_WEEK_VIEW (object), g_value_get_boolean (value)); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void week_view_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_COMPRESS_WEEKEND: g_value_set_boolean ( value, e_week_view_get_compress_weekend ( E_WEEK_VIEW (object))); return; case PROP_SHOW_EVENT_END_TIMES: g_value_set_boolean ( value, e_week_view_get_show_event_end_times ( E_WEEK_VIEW (object))); return; case PROP_IS_EDITING: g_value_set_boolean (value, e_week_view_is_editing (E_WEEK_VIEW (object))); return; } G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } static void week_view_dispose (GObject *object) { EWeekView *week_view; week_view = E_WEEK_VIEW (object); e_week_view_cancel_layout (week_view); if (week_view->events) { e_week_view_free_events (week_view); g_array_free (week_view->events, TRUE); week_view->events = NULL; } if (week_view->small_font_desc) { pango_font_description_free (week_view->small_font_desc); week_view->small_font_desc = NULL; } if (week_view->normal_cursor) { g_object_unref (week_view->normal_cursor); week_view->normal_cursor = NULL; } if (week_view->move_cursor) { g_object_unref (week_view->move_cursor); week_view->move_cursor = NULL; } if (week_view->resize_width_cursor) { g_object_unref (week_view->resize_width_cursor); week_view->resize_width_cursor = NULL; } calendar_config_remove_notification ( month_scroll_by_week_changed_cb, week_view); /* Chain up to parent's dispose() method. */ G_OBJECT_CLASS (e_week_view_parent_class)->dispose (object); } static void week_view_constructed (GObject *object) { ECalModel *model; ECalendarView *calendar_view; /* Chain up to parent's constructed() method. */ G_OBJECT_CLASS (e_week_view_parent_class)->constructed (object); calendar_view = E_CALENDAR_VIEW (object); model = e_calendar_view_get_model (calendar_view); e_week_view_recalc_display_start_day (E_WEEK_VIEW (object)); g_signal_connect_swapped ( model, "notify::week-start-day", G_CALLBACK (week_view_notify_week_start_day_cb), object); g_signal_connect_swapped ( model, "comps-deleted", G_CALLBACK (week_view_model_comps_deleted_cb), object); g_signal_connect_swapped ( model, "model-cell-changed", G_CALLBACK (week_view_model_cell_changed_cb), object); g_signal_connect_swapped ( model, "model-row-changed", G_CALLBACK (week_view_model_row_changed_cb), object); g_signal_connect_swapped ( model, "model-rows-inserted", G_CALLBACK (week_view_model_rows_inserted_cb), object); g_signal_connect_swapped ( model, "time-range-changed", G_CALLBACK (week_view_time_range_changed_cb), object); } static void week_view_realize (GtkWidget *widget) { EWeekView *week_view; if (GTK_WIDGET_CLASS (e_week_view_parent_class)->realize) (*GTK_WIDGET_CLASS (e_week_view_parent_class)->realize)(widget); week_view = E_WEEK_VIEW (widget); /* Allocate the colors. */ e_week_view_set_colors (week_view, widget); /* Create the pixmaps. */ week_view->reminder_icon = e_icon_factory_get_icon ("stock_bell", GTK_ICON_SIZE_MENU); week_view->recurrence_icon = e_icon_factory_get_icon ("view-refresh", GTK_ICON_SIZE_MENU); week_view->timezone_icon = e_icon_factory_get_icon ("stock_timezone", GTK_ICON_SIZE_MENU); week_view->attach_icon = e_icon_factory_get_icon ("mail-attachment", GTK_ICON_SIZE_MENU); week_view->meeting_icon = e_icon_factory_get_icon ("stock_people", GTK_ICON_SIZE_MENU); } static void week_view_unrealize (GtkWidget *widget) { EWeekView *week_view; week_view = E_WEEK_VIEW (widget); g_object_unref (week_view->reminder_icon); week_view->reminder_icon = NULL; g_object_unref (week_view->recurrence_icon); week_view->recurrence_icon = NULL; g_object_unref (week_view->timezone_icon); week_view->timezone_icon = NULL; g_object_unref (week_view->attach_icon); week_view->attach_icon = NULL; g_object_unref (week_view->meeting_icon); week_view->meeting_icon = NULL; if (GTK_WIDGET_CLASS (e_week_view_parent_class)->unrealize) (*GTK_WIDGET_CLASS (e_week_view_parent_class)->unrealize)(widget); } static void week_view_style_set (GtkWidget *widget, GtkStyle *previous_style) { EWeekView *week_view; GtkStyle *style; gint day, day_width, max_day_width, max_abbr_day_width; gint month, month_width, max_month_width, max_abbr_month_width; gint span_num; const gchar *name; PangoFontDescription *font_desc; PangoContext *pango_context; PangoFontMetrics *font_metrics; PangoLayout *layout; EWeekViewEventSpan *span; if (GTK_WIDGET_CLASS (e_week_view_parent_class)->style_set) (*GTK_WIDGET_CLASS (e_week_view_parent_class)->style_set)(widget, previous_style); week_view = E_WEEK_VIEW (widget); style = gtk_widget_get_style (widget); e_week_view_set_colors (week_view, widget); if (week_view->spans) { for (span_num = 0; span_num < week_view->spans->len; span_num++) { span = &g_array_index (week_view->spans, EWeekViewEventSpan, span_num); if (span->text_item) { gnome_canvas_item_set ( span->text_item, "fill_color_gdk", &style->text[GTK_STATE_NORMAL], NULL); } } } /* Set up Pango prerequisites */ font_desc = style->font_desc; pango_context = gtk_widget_get_pango_context (widget); font_metrics = pango_context_get_metrics ( pango_context, font_desc, pango_context_get_language (pango_context)); layout = pango_layout_new (pango_context); /* Recalculate the height of each row based on the font size. */ week_view->row_height = PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) + E_WEEK_VIEW_EVENT_BORDER_HEIGHT * 2 + E_WEEK_VIEW_EVENT_TEXT_Y_PAD * 2; week_view->row_height = MAX (week_view->row_height, E_WEEK_VIEW_ICON_HEIGHT + E_WEEK_VIEW_ICON_Y_PAD + E_WEEK_VIEW_EVENT_BORDER_HEIGHT * 2); /* Check that the small font is smaller than the default font. * If it isn't, we won't use it. */ if (week_view->small_font_desc) { if (PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) <= E_WEEK_VIEW_SMALL_FONT_PTSIZE) week_view->use_small_font = FALSE; } /* Set the height of the top canvas. */ gtk_widget_set_size_request ( week_view->titles_canvas, -1, PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) + 5); /* Save the sizes of various strings in the font, so we can quickly * decide which date formats to use. */ max_day_width = 0; max_abbr_day_width = 0; for (day = 0; day < 7; day++) { name = e_get_weekday_name (day + 1, FALSE); day_width = get_string_width (layout, name); week_view->day_widths[day] = day_width; max_day_width = MAX (max_day_width, day_width); name = e_get_weekday_name (day + 1, TRUE); day_width = get_string_width (layout, name); week_view->abbr_day_widths[day] = day_width; max_abbr_day_width = MAX (max_abbr_day_width, day_width); } max_month_width = 0; max_abbr_month_width = 0; for (month = 0; month < 12; month++) { name = e_get_month_name (month + 1, FALSE); month_width = get_string_width (layout, name); week_view->month_widths[month] = month_width; max_month_width = MAX (max_month_width, month_width); name = e_get_month_name (month + 1, TRUE); month_width = get_string_width (layout, name); week_view->abbr_month_widths[month] = month_width; max_abbr_month_width = MAX (max_abbr_month_width, month_width); } week_view->space_width = get_string_width (layout, " "); week_view->colon_width = get_string_width (layout, ":"); week_view->slash_width = get_string_width (layout, "/"); week_view->digit_width = get_digit_width (layout); if (week_view->small_font_desc) { pango_layout_set_font_description (layout, week_view->small_font_desc); week_view->small_digit_width = get_digit_width (layout); pango_layout_set_font_description (layout, style->font_desc); } week_view->max_day_width = max_day_width; week_view->max_abbr_day_width = max_abbr_day_width; week_view->max_month_width = max_month_width; week_view->max_abbr_month_width = max_abbr_month_width; week_view->am_string_width = get_string_width ( layout, week_view->am_string); week_view->pm_string_width = get_string_width ( layout, week_view->pm_string); g_object_unref (layout); pango_font_metrics_unref (font_metrics); } static void week_view_size_allocate (GtkWidget *widget, GtkAllocation *allocation) { EWeekView *week_view; GtkAllocation canvas_allocation; gdouble old_x2, old_y2, new_x2, new_y2; week_view = E_WEEK_VIEW (widget); (*GTK_WIDGET_CLASS (e_week_view_parent_class)->size_allocate) (widget, allocation); e_week_view_recalc_cell_sizes (week_view); /* Set the scroll region of the top canvas to its allocated size. */ gnome_canvas_get_scroll_region ( GNOME_CANVAS (week_view->titles_canvas), NULL, NULL, &old_x2, &old_y2); gtk_widget_get_allocation ( week_view->titles_canvas, &canvas_allocation); new_x2 = canvas_allocation.width - 1; new_y2 = canvas_allocation.height - 1; if (old_x2 != new_x2 || old_y2 != new_y2) gnome_canvas_set_scroll_region ( GNOME_CANVAS (week_view->titles_canvas), 0, 0, new_x2, new_y2); /* 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 (week_view->main_canvas), NULL, NULL, &old_x2, &old_y2); gtk_widget_get_allocation ( week_view->main_canvas, &canvas_allocation); new_x2 = canvas_allocation.width - 1; new_y2 = canvas_allocation.height - 1; if (old_x2 != new_x2 || old_y2 != new_y2) gnome_canvas_set_scroll_region ( GNOME_CANVAS (week_view->main_canvas), 0, 0, new_x2, new_y2); /* Flag that we need to reshape the events. */ if (old_x2 != new_x2 || old_y2 != new_y2) { week_view->events_need_reshape = TRUE; e_week_view_check_layout (week_view); } } static gint week_view_focus_in (GtkWidget *widget, GdkEventFocus *event) { EWeekView *week_view; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); week_view = E_WEEK_VIEW (widget); /* XXX Can't access flags directly anymore, but is it really needed? * If so, could we call gtk_widget_send_focus_change() instead? */ #if 0 GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS); #endif if (E_CALENDAR_VIEW (week_view)->in_focus && week_view->requires_update) { time_t my_start = 0, my_end = 0, model_start = 0, model_end = 0; week_view->requires_update = FALSE; e_cal_model_get_time_range (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), &model_start, &model_end); if (e_calendar_view_get_visible_time_range (E_CALENDAR_VIEW (week_view), &my_start, &my_end) && model_start == my_start && model_end == my_end) { /* update only when the same time range is set in a view and in a model; * otherwise time range change invokes also query update */ e_week_view_update_query (week_view); } } gtk_widget_queue_draw (week_view->main_canvas); return FALSE; } static gint week_view_focus_out (GtkWidget *widget, GdkEventFocus *event) { EWeekView *week_view; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); week_view = E_WEEK_VIEW (widget); /* XXX Can't access flags directly anymore, but is it really needed? * If so, could we call gtk_widget_send_focus_change() instead? */ #if 0 GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS); #endif gtk_widget_queue_draw (week_view->main_canvas); return FALSE; } static gboolean week_view_key_press (GtkWidget *widget, GdkEventKey *event) { gboolean handled = FALSE; handled = e_week_view_do_key_press (widget, event); /* if not handled, try key bindings */ if (!handled) handled = GTK_WIDGET_CLASS (e_week_view_parent_class)->key_press_event (widget, event); return handled; } static gboolean week_view_focus (GtkWidget *widget, GtkDirectionType direction) { EWeekView *week_view; gint new_event_num; gint new_span_num; gint event_loop; gboolean editable = FALSE; static gint last_focus_event_num = -1, last_focus_span_num = -1; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE); week_view = E_WEEK_VIEW (widget); e_week_view_check_layout (week_view); if (week_view->focused_jump_button == E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS) { last_focus_event_num = week_view->editing_event_num; last_focus_span_num = week_view->editing_span_num; } /* if there is not event, just grab week_view */ if (week_view->events->len == 0) { gtk_widget_grab_focus (widget); return TRUE; } for (event_loop = 0; event_loop < week_view->events->len; ++event_loop) { if (!e_week_view_get_next_tab_event (week_view, direction, last_focus_event_num, last_focus_span_num, &new_event_num, &new_span_num)) return FALSE; if (new_event_num == -1) { /* focus should go to week_view widget */ gtk_widget_grab_focus (widget); return TRUE; } editable = e_week_view_start_editing_event ( week_view, new_event_num, new_span_num, NULL); last_focus_event_num = new_event_num; last_focus_span_num = new_span_num; if (editable) break; else { /* check if we should go to the jump button */ EWeekViewEvent *event; EWeekViewEventSpan *span; gint current_day; if (!is_array_index_in_bounds (week_view->events, new_event_num)) break; event = &g_array_index (week_view->events, EWeekViewEvent, new_event_num); if (!is_array_index_in_bounds (week_view->spans, event->spans_index + new_span_num)) break; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + new_span_num); current_day = span->start_day; if ((week_view->focused_jump_button != current_day) && e_week_view_is_jump_button_visible (week_view, current_day)) { /* focus go to the jump button */ e_week_view_stop_editing_event (week_view); gnome_canvas_item_grab_focus (week_view->jump_buttons[current_day]); return TRUE; } } } return editable; } static gboolean week_view_popup_menu (GtkWidget *widget) { EWeekView *week_view = E_WEEK_VIEW (widget); e_week_view_show_popup_menu ( week_view, NULL, week_view->editing_event_num); return TRUE; } static GList * week_view_get_selected_events (ECalendarView *cal_view) { EWeekViewEvent *event = NULL; GList *list = NULL; EWeekView *week_view = (EWeekView *) cal_view; g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), NULL); if (week_view->editing_event_num != -1) { if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num)) { week_view->editing_event_num = -1; g_object_notify (G_OBJECT (week_view), "is-editing"); return NULL; } event = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num); } else if (week_view->popup_event_num != -1) { if (!is_array_index_in_bounds (week_view->events, week_view->popup_event_num)) return NULL; event = &g_array_index (week_view->events, EWeekViewEvent, week_view->popup_event_num); } if (event) list = g_list_prepend (list, event); return list; } static gboolean week_view_get_selected_time_range (ECalendarView *cal_view, time_t *start_time, time_t *end_time) { gint start_day, end_day; EWeekView *week_view = E_WEEK_VIEW (cal_view); start_day = week_view->selection_start_day; end_day = week_view->selection_end_day; if (start_day == -1) { start_day = 0; end_day = 0; } if (start_time) *start_time = week_view->day_starts[start_day]; if (end_time) *end_time = week_view->day_starts[end_day + 1]; return TRUE; } /* This sets the selected time range. The EWeekView will show the corresponding * month and the days between start_time and end_time will be selected. * To select a single day, use the same value for start_time & end_time. */ static void week_view_set_selected_time_range (ECalendarView *cal_view, time_t start_time, time_t end_time) { GDate date, end_date; gint num_days; gboolean update_adjustment_value = FALSE; EWeekView *week_view = E_WEEK_VIEW (cal_view); g_return_if_fail (E_IS_WEEK_VIEW (week_view)); if (!g_date_valid (&week_view->base_date)) { /* This view has not been initialized/shown yet, thus skip this. */ return; } time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); /* Set the selection to the given days. */ week_view->selection_start_day = g_date_get_julian (&date) - g_date_get_julian (&week_view->base_date); if (end_time == start_time || end_time <= time_add_day_with_zone (start_time, 1, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)))) week_view->selection_end_day = week_view->selection_start_day; else { time_to_gdate_with_zone (&end_date, end_time - 60, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); week_view->selection_end_day = g_date_get_julian (&end_date) - g_date_get_julian (&week_view->base_date); } /* Make sure the selection is valid. */ num_days = (e_week_view_get_weeks_shown (week_view) * 7) - 1; week_view->selection_start_day = CLAMP ( week_view->selection_start_day, 0, num_days); week_view->selection_end_day = CLAMP ( week_view->selection_end_day, week_view->selection_start_day, num_days); /* Reset the adjustment value to 0 if the base address has changed. * Note that we do this after updating first_day_shown so that our * signal handler will not try to reload the events. */ if (update_adjustment_value) { GtkRange *range; GtkAdjustment *adjustment; range = GTK_RANGE (week_view->vscrollbar); adjustment = gtk_range_get_adjustment (range); gtk_adjustment_set_value (adjustment, 0); } gtk_widget_queue_draw (week_view->main_canvas); } static gboolean week_view_get_visible_time_range (ECalendarView *cal_view, time_t *start_time, time_t *end_time) { gint num_days; EWeekView *week_view = E_WEEK_VIEW (cal_view); /* If we don't have a valid date set yet, return FALSE. */ if (!g_date_valid (&week_view->priv->first_day_shown)) return FALSE; num_days = e_week_view_get_weeks_shown (week_view) * 7; *start_time = week_view->day_starts[0]; *end_time = week_view->day_starts[num_days]; return TRUE; } static void week_view_paste_text (ECalendarView *cal_view) { EWeekViewEvent *event; EWeekViewEventSpan *span; EWeekView *week_view; g_return_if_fail (E_IS_WEEK_VIEW (cal_view)); week_view = E_WEEK_VIEW (cal_view); if (week_view->editing_event_num == -1 && !e_week_view_add_new_event_in_selected_range (week_view, NULL)) return; if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num)) return; event = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num); if (!is_array_index_in_bounds (week_view->spans, event->spans_index + week_view->editing_span_num)) return; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + week_view->editing_span_num); if (span->text_item && E_IS_TEXT (span->text_item) && E_TEXT (span->text_item)->editing) { e_text_paste_clipboard (E_TEXT (span->text_item)); } } static void week_view_cursor_key_up (EWeekView *week_view) { if (week_view->selection_start_day == -1) return; week_view->selection_start_day--; if (week_view->selection_start_day < 0) { e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_UP); week_view->selection_start_day = 6; } week_view->selection_end_day = week_view->selection_start_day; g_signal_emit_by_name (week_view, "selected_time_changed"); gtk_widget_queue_draw (week_view->main_canvas); } static void week_view_cursor_key_down (EWeekView *week_view) { if (week_view->selection_start_day == -1) return; week_view->selection_start_day++; if (week_view->selection_start_day > 6) { e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_DOWN); week_view->selection_start_day = 0; } week_view->selection_end_day = week_view->selection_start_day; g_signal_emit_by_name (week_view, "selected_time_changed"); gtk_widget_queue_draw (week_view->main_canvas); } static void week_view_cursor_key_left (EWeekView *week_view) { if (week_view->selection_start_day == -1) return; week_view->selection_start_day = map_left[week_view->selection_start_day]; week_view->selection_end_day = week_view->selection_start_day; g_signal_emit_by_name (week_view, "selected_time_changed"); gtk_widget_queue_draw (week_view->main_canvas); } static void week_view_cursor_key_right (EWeekView *week_view) { if (week_view->selection_start_day == -1) return; week_view->selection_start_day = map_right[week_view->selection_start_day]; week_view->selection_end_day = week_view->selection_start_day; g_signal_emit_by_name (week_view, "selected_time_changed"); gtk_widget_queue_draw (week_view->main_canvas); } static void e_week_view_class_init (EWeekViewClass *class) { GObjectClass *object_class; GtkWidgetClass *widget_class; ECalendarViewClass *view_class; g_type_class_add_private (class, sizeof (EWeekViewPrivate)); object_class = G_OBJECT_CLASS (class); object_class->set_property = week_view_set_property; object_class->get_property = week_view_get_property; object_class->dispose = week_view_dispose; object_class->constructed = week_view_constructed; widget_class = GTK_WIDGET_CLASS (class); widget_class->realize = week_view_realize; widget_class->unrealize = week_view_unrealize; widget_class->style_set = week_view_style_set; widget_class->size_allocate = week_view_size_allocate; widget_class->focus_in_event = week_view_focus_in; widget_class->focus_out_event = week_view_focus_out; widget_class->key_press_event = week_view_key_press; widget_class->focus = week_view_focus; widget_class->popup_menu = week_view_popup_menu; view_class = E_CALENDAR_VIEW_CLASS (class); view_class->get_selected_events = week_view_get_selected_events; view_class->get_selected_time_range = week_view_get_selected_time_range; view_class->set_selected_time_range = week_view_set_selected_time_range; view_class->get_visible_time_range = week_view_get_visible_time_range; view_class->paste_text = week_view_paste_text; class->cursor_key_up = week_view_cursor_key_up; class->cursor_key_down = week_view_cursor_key_down; class->cursor_key_left = week_view_cursor_key_left; class->cursor_key_right = week_view_cursor_key_right; /* XXX This property really belongs in EMonthView, * but too much drawing code is tied to it. */ g_object_class_install_property ( object_class, PROP_COMPRESS_WEEKEND, g_param_spec_boolean ( "compress-weekend", "Compress Weekend", NULL, TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_install_property ( object_class, PROP_SHOW_EVENT_END_TIMES, g_param_spec_boolean ( "show-event-end-times", "Show Event End Times", NULL, TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); g_object_class_override_property ( object_class, PROP_IS_EDITING, "is-editing"); /* init the accessibility support for e_week_view */ e_week_view_a11y_init (); } static void e_week_view_init (EWeekView *week_view) { GnomeCanvasGroup *canvas_group; GtkAdjustment *adjustment; GdkPixbuf *pixbuf; gint i; week_view->priv = E_WEEK_VIEW_GET_PRIVATE (week_view); week_view->priv->weeks_shown = 6; week_view->priv->compress_weekend = TRUE; week_view->priv->show_event_end_times = TRUE; week_view->priv->update_base_date = TRUE; week_view->priv->display_start_day = G_DATE_MONDAY; gtk_widget_set_can_focus (GTK_WIDGET (week_view), TRUE); week_view->event_destroyed = FALSE; week_view->events = g_array_new ( FALSE, FALSE, sizeof (EWeekViewEvent)); week_view->events_sorted = TRUE; week_view->events_need_layout = FALSE; week_view->events_need_reshape = FALSE; week_view->layout_timeout_id = 0; week_view->spans = NULL; week_view->month_scroll_by_week = FALSE; week_view->scroll_by_week_notif_id = 0; week_view->rows = 6; week_view->columns = 2; g_date_clear (&week_view->base_date, 1); g_date_clear (&week_view->priv->first_day_shown, 1); week_view->row_height = 10; week_view->rows_per_cell = 1; week_view->selection_start_day = -1; week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE; week_view->pressed_event_num = -1; week_view->editing_event_num = -1; week_view->last_edited_comp_string = NULL; /* Create the small font. */ week_view->use_small_font = TRUE; week_view->small_font_desc = pango_font_description_copy (gtk_widget_get_style (GTK_WIDGET (week_view))->font_desc); pango_font_description_set_size ( week_view->small_font_desc, E_WEEK_VIEW_SMALL_FONT_PTSIZE * PANGO_SCALE); /* String to use in 12-hour time format for times in the morning. */ week_view->am_string = _("am"); /* String to use in 12-hour time format for times in the afternoon. */ week_view->pm_string = _("pm"); week_view->bc_event_time = 0; week_view->before_click_dtstart = 0; week_view->before_click_dtend = 0; /* * Titles Canvas. Note that we don't show it is only shown in the * Month view. */ week_view->titles_canvas = e_canvas_new (); gtk_table_attach ( GTK_TABLE (week_view), week_view->titles_canvas, 1, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->titles_canvas)->root); week_view->titles_canvas_item = gnome_canvas_item_new ( canvas_group, e_week_view_titles_item_get_type (), "EWeekViewTitlesItem::week_view", week_view, NULL); /* * Main Canvas */ week_view->main_canvas = e_canvas_new (); gtk_table_attach ( GTK_TABLE (week_view), week_view->main_canvas, 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 1, 1); gtk_widget_show (week_view->main_canvas); canvas_group = GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root); week_view->main_canvas_item = gnome_canvas_item_new ( canvas_group, e_week_view_main_item_get_type (), "EWeekViewMainItem::week_view", week_view, NULL); g_signal_connect_after ( week_view->main_canvas, "button_press_event", G_CALLBACK (e_week_view_on_button_press), week_view); g_signal_connect ( week_view->main_canvas, "button_release_event", G_CALLBACK (e_week_view_on_button_release), week_view); g_signal_connect ( week_view->main_canvas, "scroll_event", G_CALLBACK (e_week_view_on_scroll), week_view); g_signal_connect ( week_view->main_canvas, "motion_notify_event", G_CALLBACK (e_week_view_on_motion), week_view); /* Create the buttons to jump to each days. */ pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) jump_xpm); for (i = 0; i < E_WEEK_VIEW_MAX_WEEKS * 7; i++) { week_view->jump_buttons[i] = gnome_canvas_item_new (canvas_group, gnome_canvas_pixbuf_get_type (), "GnomeCanvasPixbuf::pixbuf", pixbuf, NULL); g_signal_connect ( week_view->jump_buttons[i], "event", G_CALLBACK (e_week_view_on_jump_button_event), week_view); } week_view->focused_jump_button = E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS; g_object_unref (pixbuf); /* * Scrollbar. */ adjustment = GTK_ADJUSTMENT (gtk_adjustment_new (0, -52, 52, 1, 1, 1)); week_view->vscrollbar = gtk_scrollbar_new ( GTK_ORIENTATION_VERTICAL, adjustment); gtk_table_attach ( GTK_TABLE (week_view), week_view->vscrollbar, 2, 3, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0); gtk_widget_show (week_view->vscrollbar); /* Create the cursors. */ week_view->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR); week_view->move_cursor = gdk_cursor_new (GDK_FLEUR); week_view->resize_width_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); week_view->last_cursor_set = NULL; week_view->requires_update = FALSE; } /** * e_week_view_new: * @Returns: a new #EWeekView. * * Creates a new #EWeekView. **/ ECalendarView * e_week_view_new (ECalModel *model) { ECalendarView *view; g_return_val_if_fail (E_IS_CAL_MODEL (model), NULL); view = g_object_new (E_TYPE_WEEK_VIEW, "model", model, NULL); g_signal_connect ( model, "timezone_changed", G_CALLBACK (timezone_changed_cb), view); return view; } static GdkColor color_inc (GdkColor c, gint amount) { #define dec(x) \ if (x + amount >= 0 \ && x + amount <= 0xFFFF) \ x += amount; \ else if (amount <= 0) \ x = 0; \ else \ x = 0xFFFF; dec (c.red); dec (c.green); dec (c.blue); #undef dec return c; } static void e_week_view_set_colors (EWeekView *week_view, GtkWidget *widget) { GtkStyle *style; style = gtk_widget_get_style (widget); week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS] = style->base[GTK_STATE_INSENSITIVE]; week_view->colors[E_WEEK_VIEW_COLOR_ODD_MONTHS] = style->base[GTK_STATE_NORMAL]; week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND] = style->base[GTK_STATE_NORMAL]; week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BORDER] = style->dark[GTK_STATE_NORMAL]; week_view->colors[E_WEEK_VIEW_COLOR_EVENT_TEXT] = style->text[GTK_STATE_NORMAL]; week_view->colors[E_WEEK_VIEW_COLOR_GRID] = style->dark[GTK_STATE_NORMAL]; week_view->colors[E_WEEK_VIEW_COLOR_SELECTED] = style->base[GTK_STATE_SELECTED]; week_view->colors[E_WEEK_VIEW_COLOR_SELECTED_UNFOCUSSED] = style->bg[GTK_STATE_SELECTED]; week_view->colors[E_WEEK_VIEW_COLOR_DATES] = style->text[GTK_STATE_NORMAL]; week_view->colors[E_WEEK_VIEW_COLOR_DATES_SELECTED] = style->text[GTK_STATE_SELECTED]; week_view->colors[E_WEEK_VIEW_COLOR_TODAY] = style->base[GTK_STATE_SELECTED]; week_view->colors[E_WEEK_VIEW_COLOR_TODAY_BACKGROUND] = get_today_background (week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND]); week_view->colors[E_WEEK_VIEW_COLOR_MONTH_NONWORKING_DAY] = color_inc (week_view->colors[E_WEEK_VIEW_COLOR_EVEN_MONTHS], -0x0A0A); } static GdkColor e_week_view_get_text_color (EWeekView *week_view, EWeekViewEvent *event, GtkWidget *widget) { GtkStyle *style; GdkColor bg_color; guint16 red, green, blue; gdouble cc = 65535.0; red = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].red; green = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].green; blue = week_view->colors[E_WEEK_VIEW_COLOR_EVENT_BACKGROUND].blue; if (is_comp_data_valid (event) && gdk_color_parse (e_cal_model_get_color_for_component (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), event->comp_data), &bg_color)) { red = bg_color.red; green = bg_color.green; blue = bg_color.blue; } style = gtk_widget_get_style (widget); if ((red / cc > 0.7) || (green / cc > 0.7) || (blue / cc > 0.7)) return style->black; else return style->white; } static void e_week_view_recalc_cell_sizes (EWeekView *week_view) { gfloat canvas_width, canvas_height, offset; gint row, col; GtkAllocation allocation; GtkWidget *widget; GtkStyle *style; gint width, height, time_width; PangoFontDescription *font_desc; PangoContext *pango_context; PangoFontMetrics *font_metrics; if (e_week_view_get_multi_week_view (week_view)) { week_view->rows = e_week_view_get_weeks_shown (week_view) * 2; week_view->columns = e_week_view_get_compress_weekend (week_view) ? 6 : 7; } else { week_view->rows = 6; week_view->columns = 2; } gtk_widget_get_allocation (week_view->main_canvas, &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. * We also add one to the width so that the right border of the last * column is off the edge of the displayed area. */ canvas_width = allocation.width + 1; canvas_width /= week_view->columns; offset = 0; for (col = 0; col <= week_view->columns; col++) { week_view->col_offsets[col] = floor (offset + 0.5); offset += canvas_width; } /* Calculate the cell widths based on the offsets. */ for (col = 0; col < week_view->columns; col++) { week_view->col_widths[col] = week_view->col_offsets[col + 1] - week_view->col_offsets[col]; } /* Now do the same for the row heights. */ canvas_height = allocation.height + 1; canvas_height /= week_view->rows; offset = 0; for (row = 0; row <= week_view->rows; row++) { week_view->row_offsets[row] = floor (offset + 0.5); offset += canvas_height; } /* Calculate the cell heights based on the offsets. */ for (row = 0; row < week_view->rows; row++) { week_view->row_heights[row] = week_view->row_offsets[row + 1] - week_view->row_offsets[row]; } /* If the font hasn't been set yet just return. */ widget = GTK_WIDGET (week_view); style = gtk_widget_get_style (widget); if (!style) return; font_desc = style->font_desc; if (!font_desc) return; pango_context = gtk_widget_get_pango_context (widget); font_metrics = pango_context_get_metrics ( pango_context, font_desc, pango_context_get_language (pango_context)); /* Calculate the number of rows of events in each cell, for the large * cells and the compressed weekend cells. */ if (e_week_view_get_multi_week_view (week_view)) { week_view->events_y_offset = E_WEEK_VIEW_DATE_T_PAD + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) + E_WEEK_VIEW_DATE_B_PAD; } else { week_view->events_y_offset = E_WEEK_VIEW_DATE_T_PAD + PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)) + E_WEEK_VIEW_DATE_LINE_T_PAD + 1 + E_WEEK_VIEW_DATE_LINE_B_PAD; } height = week_view->row_heights[0]; week_view->rows_per_cell = (height * 2 - week_view->events_y_offset) / (week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING); week_view->rows_per_cell = MIN ( week_view->rows_per_cell, E_WEEK_VIEW_MAX_ROWS_PER_CELL); week_view->rows_per_compressed_cell = (height - week_view->events_y_offset) / (week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING); week_view->rows_per_compressed_cell = MIN ( week_view->rows_per_compressed_cell, E_WEEK_VIEW_MAX_ROWS_PER_CELL); /* Determine which time format to use, based on the width of the cells. * We only allow the time to take up about half of the width. */ width = week_view->col_widths[0]; time_width = e_week_view_get_time_string_width (week_view); week_view->time_format = E_WEEK_VIEW_TIME_NONE; if (week_view->use_small_font && week_view->small_font_desc) { if (e_week_view_get_show_event_end_times (week_view) && width / 2 > time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING) week_view->time_format = E_WEEK_VIEW_TIME_BOTH_SMALL_MIN; else if (width / 2 > time_width) week_view->time_format = E_WEEK_VIEW_TIME_START_SMALL_MIN; } else { if (e_week_view_get_show_event_end_times (week_view) && width / 2 > time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING) week_view->time_format = E_WEEK_VIEW_TIME_BOTH; else if (width / 2 > time_width) week_view->time_format = E_WEEK_VIEW_TIME_START; } pango_font_metrics_unref (font_metrics); } /** * e_week_view_get_next_tab_event * @week_view: the week_view widget operate on * @direction: GTK_DIR_TAB_BACKWARD or GTK_DIR_TAB_FORWARD. * @current_event_num and @current_span_num: current status. * @next_event_num: the event number focus should go next. * -1 indicates focus should go to week_view widget. * @next_span_num: always return 0. **/ static gboolean e_week_view_get_next_tab_event (EWeekView *week_view, GtkDirectionType direction, gint current_event_num, gint current_span_num, gint *next_event_num, gint *next_span_num) { gint event_num; g_return_val_if_fail (week_view != NULL, FALSE); g_return_val_if_fail (next_event_num != NULL, FALSE); g_return_val_if_fail (next_span_num != NULL, FALSE); if (week_view->events->len <= 0) return FALSE; /* we only tab through events not spans */ *next_span_num = 0; switch (direction) { case GTK_DIR_TAB_BACKWARD: event_num = current_event_num - 1; break; case GTK_DIR_TAB_FORWARD: event_num = current_event_num + 1; break; default: return FALSE; } if (event_num == -1) /* backward, out of event range, go to week view widget */ *next_event_num = -1; else if (event_num < -1) /* backward from week_view, go to the last event */ *next_event_num = week_view->events->len - 1; else if (event_num >= week_view->events->len) /* forward, out of event range, go to week view widget */ *next_event_num = -1; else *next_event_num = event_num; return TRUE; } /* Restarts a query for the week view */ static void e_week_view_update_query (EWeekView *week_view) { gint rows, r; if (!E_CALENDAR_VIEW (week_view)->in_focus) { e_week_view_free_events (week_view); week_view->requires_update = TRUE; return; } gtk_widget_queue_draw (week_view->main_canvas); e_week_view_free_events (week_view); e_week_view_queue_layout (week_view); rows = e_table_model_row_count (E_TABLE_MODEL (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)))); for (r = 0; r < rows; r++) { ECalModelComponent *comp_data; comp_data = e_cal_model_get_component_at (e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)), r); if (comp_data == NULL) { g_warning ("comp_data is NULL\n"); continue; } week_view_process_component (week_view, comp_data); } } void e_week_view_set_selected_time_range_visible (EWeekView *week_view, time_t start_time, time_t end_time) { GDate *first_day_shown; GDate date, end_date; gint num_days; g_return_if_fail (E_IS_WEEK_VIEW (week_view)); first_day_shown = &week_view->priv->first_day_shown; time_to_gdate_with_zone (&date, start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); /* Set the selection to the given days. */ week_view->selection_start_day = g_date_get_julian (&date) - g_date_get_julian (first_day_shown); if (end_time == start_time || end_time <= time_add_day_with_zone (start_time, 1, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view)))) week_view->selection_end_day = week_view->selection_start_day; else { time_to_gdate_with_zone (&end_date, end_time - 60, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); week_view->selection_end_day = g_date_get_julian (&end_date) - g_date_get_julian (first_day_shown); } /* Make sure the selection is valid. */ num_days = (e_week_view_get_weeks_shown (week_view) * 7) - 1; week_view->selection_start_day = CLAMP ( week_view->selection_start_day, 0, num_days); week_view->selection_end_day = CLAMP ( week_view->selection_end_day, week_view->selection_start_day, num_days); gtk_widget_queue_draw (week_view->main_canvas); } /* Note that the returned date may be invalid if no date has been set yet. */ void e_week_view_get_first_day_shown (EWeekView *week_view, GDate *date) { *date = week_view->priv->first_day_shown; } /* This sets the first day shown in the view. It will be rounded down to the * nearest week. */ void e_week_view_set_first_day_shown (EWeekView *week_view, GDate *date) { GDate base_date; GDateWeekday weekday; GDateWeekday display_start_day; guint day_offset; gint num_days; gboolean update_adjustment_value = FALSE; guint32 old_selection_start_julian = 0, old_selection_end_julian = 0; struct icaltimetype start_tt = icaltime_null_time (); time_t start_time; g_return_if_fail (E_IS_WEEK_VIEW (week_view)); /* Calculate the old selection range. */ if (week_view->selection_start_day != -1) { old_selection_start_julian = g_date_get_julian (&week_view->base_date) + week_view->selection_start_day; old_selection_end_julian = g_date_get_julian (&week_view->base_date) + week_view->selection_end_day; } weekday = g_date_get_weekday (date); display_start_day = e_week_view_get_display_start_day (week_view); /* Convert it to an offset from the start of the display. */ day_offset = e_weekday_get_days_between (display_start_day, weekday); /* Calculate the base date, i.e. the first day shown when the * scrollbar adjustment value is 0. */ base_date = *date; g_date_subtract_days (&base_date, day_offset); /* See if we need to update the base date. */ if (!g_date_valid (&week_view->base_date) || g_date_compare (&week_view->base_date, &base_date)) { week_view->base_date = base_date; update_adjustment_value = TRUE; } /* See if we need to update the first day shown. */ if (!g_date_valid (&week_view->priv->first_day_shown) || g_date_compare (&week_view->priv->first_day_shown, &base_date)) { week_view->priv->first_day_shown = base_date; start_tt.year = g_date_get_year (&base_date); start_tt.month = g_date_get_month (&base_date); start_tt.day = g_date_get_day (&base_date); start_time = icaltime_as_timet_with_zone ( start_tt, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); e_week_view_recalc_day_starts (week_view, start_time); e_week_view_update_query (week_view); } /* Try to keep the previous selection, but if it is no longer shown * just select the first day. */ if (week_view->selection_start_day != -1) { week_view->selection_start_day = old_selection_start_julian - g_date_get_julian (&base_date); week_view->selection_end_day = old_selection_end_julian - g_date_get_julian (&base_date); /* Make sure the selection is valid. */ num_days = (e_week_view_get_weeks_shown (week_view) * 7) - 1; week_view->selection_start_day = CLAMP ( week_view->selection_start_day, 0, num_days); week_view->selection_end_day = CLAMP ( week_view->selection_end_day, week_view->selection_start_day, num_days); } /* Reset the adjustment value to 0 if the base address has changed. * Note that we do this after updating first_day_shown so that our * signal handler will not try to reload the events. */ if (update_adjustment_value) { GtkRange *range; GtkAdjustment *adjustment; range = GTK_RANGE (week_view->vscrollbar); adjustment = gtk_range_get_adjustment (range); gtk_adjustment_set_value (adjustment, 0); } e_week_view_update_query (week_view); gtk_widget_queue_draw (week_view->main_canvas); } GDateWeekday e_week_view_get_display_start_day (EWeekView *week_view) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), G_DATE_BAD_WEEKDAY); return week_view->priv->display_start_day; } /* Recalculates the time_t corresponding to the start of each day. */ static void e_week_view_recalc_day_starts (EWeekView *week_view, time_t lower) { gint num_days, day; time_t tmp_time; num_days = e_week_view_get_weeks_shown (week_view) * 7; tmp_time = lower; week_view->day_starts[0] = tmp_time; for (day = 1; day <= num_days; day++) { tmp_time = time_add_day_with_zone ( tmp_time, 1, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); week_view->day_starts[day] = tmp_time; } } gboolean e_week_view_get_multi_week_view (EWeekView *week_view) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE); return week_view->priv->multi_week_view; } void e_week_view_set_multi_week_view (EWeekView *week_view, gboolean multi_week_view) { GtkRange *range; GtkAdjustment *adjustment; gint page_increment, page_size; g_return_if_fail (E_IS_WEEK_VIEW (week_view)); if (multi_week_view == week_view->priv->multi_week_view) return; week_view->priv->multi_week_view = multi_week_view; if (multi_week_view) { gtk_widget_show (week_view->titles_canvas); week_view->month_scroll_by_week = calendar_config_get_month_scroll_by_week (); calendar_config_add_notification_month_scroll_by_week ( month_scroll_by_week_changed_cb, week_view); if (week_view->month_scroll_by_week) { page_increment = 1; page_size = 1; } else { page_increment = 4; page_size = 5; } } else { gtk_widget_hide (week_view->titles_canvas); page_increment = page_size = 1; if (week_view->scroll_by_week_notif_id) { calendar_config_remove_notification ( month_scroll_by_week_changed_cb, week_view); week_view->scroll_by_week_notif_id = 0; } } range = GTK_RANGE (week_view->vscrollbar); adjustment = gtk_range_get_adjustment (range); gtk_adjustment_set_page_increment (adjustment, page_increment); gtk_adjustment_set_page_size (adjustment, page_size); e_week_view_recalc_display_start_day (week_view); e_week_view_recalc_cell_sizes (week_view); if (g_date_valid (&week_view->priv->first_day_shown)) e_week_view_set_first_day_shown ( week_view, &week_view->priv->first_day_shown); } gboolean e_week_view_get_update_base_date (EWeekView *week_view) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE); return week_view->priv->update_base_date; } void e_week_view_set_update_base_date (EWeekView *week_view, gboolean update_base_date) { g_return_if_fail (E_IS_WEEK_VIEW (week_view)); week_view->priv->update_base_date = update_base_date; } gint e_week_view_get_weeks_shown (EWeekView *week_view) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), 1); /* Give a sensible answer for single-week view. */ if (!e_week_view_get_multi_week_view (week_view)) return 1; return week_view->priv->weeks_shown; } void e_week_view_set_weeks_shown (EWeekView *week_view, gint weeks_shown) { GtkRange *range; GtkAdjustment *adjustment; gint page_increment, page_size; g_return_if_fail (E_IS_WEEK_VIEW (week_view)); weeks_shown = MIN (weeks_shown, E_WEEK_VIEW_MAX_WEEKS); if (weeks_shown == week_view->priv->weeks_shown) return; week_view->priv->weeks_shown = weeks_shown; if (e_week_view_get_multi_week_view (week_view)) { if (week_view->month_scroll_by_week) { page_increment = 1; page_size = 1; } else { page_increment = 4; page_size = 5; } range = GTK_RANGE (week_view->vscrollbar); adjustment = gtk_range_get_adjustment (range); gtk_adjustment_set_page_increment (adjustment, page_increment); gtk_adjustment_set_page_size (adjustment, page_size); e_week_view_recalc_cell_sizes (week_view); if (g_date_valid (&week_view->priv->first_day_shown)) e_week_view_set_first_day_shown ( week_view, &week_view->priv->first_day_shown); e_week_view_update_query (week_view); } } gboolean e_week_view_get_compress_weekend (EWeekView *week_view) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE); return week_view->priv->compress_weekend; } void e_week_view_set_compress_weekend (EWeekView *week_view, gboolean compress_weekend) { gboolean need_reload = FALSE; g_return_if_fail (E_IS_WEEK_VIEW (week_view)); if (compress_weekend == week_view->priv->compress_weekend) return; week_view->priv->compress_weekend = compress_weekend; /* The option only affects the month view. */ if (!e_week_view_get_multi_week_view (week_view)) return; e_week_view_recalc_cell_sizes (week_view); need_reload = e_week_view_recalc_display_start_day (week_view); /* If the display_start_day has changed we need to recalculate the * date range shown and reload all events, otherwise we only need to * do a reshape. */ if (need_reload) { /* Recalculate the days shown and reload if necessary. */ if (g_date_valid (&week_view->priv->first_day_shown)) e_week_view_set_first_day_shown ( week_view, &week_view->priv->first_day_shown); } else { week_view->events_need_reshape = TRUE; e_week_view_check_layout (week_view); } gtk_widget_queue_draw (week_view->titles_canvas); gtk_widget_queue_draw (week_view->main_canvas); g_object_notify (G_OBJECT (week_view), "compress-weekend"); } /* Whether we display event end times. */ gboolean e_week_view_get_show_event_end_times (EWeekView *week_view) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), TRUE); return week_view->priv->show_event_end_times; } void e_week_view_set_show_event_end_times (EWeekView *week_view, gboolean show_event_end_times) { g_return_if_fail (E_IS_WEEK_VIEW (week_view)); if (show_event_end_times == week_view->priv->show_event_end_times) return; week_view->priv->show_event_end_times = show_event_end_times; e_week_view_recalc_cell_sizes (week_view); week_view->events_need_reshape = TRUE; e_week_view_check_layout (week_view); gtk_widget_queue_draw (week_view->titles_canvas); gtk_widget_queue_draw (week_view->main_canvas); g_object_notify (G_OBJECT (week_view), "show-event-end-times"); } static gboolean e_week_view_recalc_display_start_day (EWeekView *week_view) { ECalModel *model; GDateWeekday week_start_day; GDateWeekday display_start_day; gboolean changed; model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)); week_start_day = e_cal_model_get_week_start_day (model); /* The display start day defaults to week_start_day, but we have * to use Saturday if the weekend is compressed and week_start_day * is Sunday. */ display_start_day = week_start_day; if (display_start_day == G_DATE_SUNDAY) { if (!e_week_view_get_multi_week_view (week_view)) display_start_day = G_DATE_SATURDAY; if (e_week_view_get_compress_weekend (week_view)) display_start_day = G_DATE_SATURDAY; } changed = (display_start_day != week_view->priv->display_start_day); week_view->priv->display_start_day = display_start_day; return changed; } /* Checks if the users participation status is NEEDS-ACTION and shows the summary as bold text */ static void set_text_as_bold (EWeekViewEvent *event, EWeekViewEventSpan *span, ESourceRegistry *registry) { ECalComponent *comp; GSList *attendees = NULL, *l; gchar *address; ECalComponentAttendee *at = NULL; if (!is_comp_data_valid (event)) return; comp = e_cal_component_new (); e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp)); address = itip_get_comp_attendee ( registry, comp, event->comp_data->client); e_cal_component_get_attendee_list (comp, &attendees); for (l = attendees; l; l = l->next) { ECalComponentAttendee *attendee = l->data; if ((g_str_equal (itip_strip_mailto (attendee->value), address)) || (attendee->sentby && g_str_equal (itip_strip_mailto (attendee->sentby), address))) { at = attendee; break; } } /* The attendee has not yet accepted the meeting, display the summary as bolded. * If the attendee is not present, it might have come through a mailing list. * In that case, we never show the meeting as bold even if it is unaccepted. */ if (at && (at->status == ICAL_PARTSTAT_NEEDSACTION)) gnome_canvas_item_set (span->text_item, "bold", TRUE, NULL); e_cal_component_free_attendee_list (attendees); g_free (address); g_object_unref (comp); } /* This calls a given function for each event instance that matches the given * uid. Note that it is safe for the callback to remove the event (since we * step backwards through the arrays). */ static void e_week_view_foreach_event_with_uid (EWeekView *week_view, const gchar *uid, EWeekViewForeachEventCallback callback, gpointer data) { EWeekViewEvent *event; gint event_num; for (event_num = week_view->events->len - 1; event_num >= 0; event_num--) { const gchar *u; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) continue; u = icalcomponent_get_uid (event->comp_data->icalcomp); if (u && !strcmp (uid, u)) { if (!(*callback) (week_view, event_num, data)) return; } } } static gboolean e_week_view_remove_event_cb (EWeekView *week_view, gint event_num, gpointer data) { EWeekViewEvent *event; EWeekViewEventSpan *span; gint span_num; if (!is_array_index_in_bounds (week_view->events, event_num)) return TRUE; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!event) return TRUE; /* If we were editing this event, set editing_event_num to -1 so * on_editing_stopped doesn't try to update the event. */ if (week_view->editing_event_num == event_num) { week_view->editing_event_num = -1; g_object_notify (G_OBJECT (week_view), "is-editing"); } if (week_view->popup_event_num == event_num) week_view->popup_event_num = -1; if (is_comp_data_valid (event)) g_object_unref (event->comp_data); event->comp_data = NULL; if (week_view->spans) { /* We leave the span elements in the array, but set the canvas item * pointers to NULL. */ for (span_num = 0; span_num < event->num_spans; span_num++) { if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) break; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); if (span->text_item) { g_object_run_dispose (G_OBJECT (span->text_item)); span->text_item = NULL; } if (span->background_item) { g_object_run_dispose (G_OBJECT (span->background_item)); span->background_item = NULL; } } /* Update event_num numbers for already created spans with event_num higher than our event_num */ for (span_num = 0; span_num < week_view->spans->len; span_num++) { span = &g_array_index (week_view->spans, EWeekViewEventSpan, span_num); if (span && span->background_item && E_IS_WEEK_VIEW_EVENT_ITEM (span->background_item)) { EWeekViewEventItem *wveitem = E_WEEK_VIEW_EVENT_ITEM (span->background_item); gint wveitem_event_num; wveitem_event_num = e_week_view_event_item_get_event_num (wveitem); if (wveitem_event_num > event_num) e_week_view_event_item_set_event_num ( wveitem, wveitem_event_num - 1); } } } g_array_remove_index (week_view->events, event_num); week_view->events_need_layout = TRUE; return TRUE; } void e_week_view_get_day_position (EWeekView *week_view, gint day, gint *day_x, gint *day_y, gint *day_w, gint *day_h) { gint cell_x, cell_y, cell_h; e_week_view_layout_get_day_position ( day, e_week_view_get_multi_week_view (week_view), e_week_view_get_weeks_shown (week_view), e_week_view_get_display_start_day (week_view), e_week_view_get_compress_weekend (week_view), &cell_x, &cell_y, &cell_h); *day_x = week_view->col_offsets[cell_x]; *day_y = week_view->row_offsets[cell_y]; *day_w = week_view->col_widths[cell_x]; *day_h = week_view->row_heights[cell_y]; while (cell_h > 1) { *day_h += week_view->row_heights[cell_y + 1]; cell_h--; cell_y++; } } /* Returns the bounding box for a span of an event. Usually this can easily * be determined by the start & end days and row of the span, which are set in * e_week_view_layout_event (). Though we need a special case for the weekends * when they are compressed, since the span may not fit. * The bounding box includes the entire width of the days in the view (but * not the vertical line down the right of the last day), though the displayed * event doesn't normally extend to the edges of the day. * It returns FALSE if the span isn't visible. */ gboolean e_week_view_get_span_position (EWeekView *week_view, gint event_num, gint span_num, gint *span_x, gint *span_y, gint *span_w) { EWeekViewEvent *event; EWeekViewEventSpan *span; gint num_days; gint start_x, start_y, start_w, start_h; gint end_x, end_y, end_w, end_h; g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE); g_return_val_if_fail (event_num < week_view->events->len, FALSE); event = &g_array_index (week_view->events, EWeekViewEvent, event_num); g_return_val_if_fail (span_num < event->num_spans, FALSE); if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) return FALSE; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); if (!e_week_view_layout_get_span_position ( event, span, week_view->rows_per_cell, week_view->rows_per_compressed_cell, e_week_view_get_display_start_day (week_view), e_week_view_get_multi_week_view (week_view), e_week_view_get_compress_weekend (week_view), &num_days)) { return FALSE; } e_week_view_get_day_position ( week_view, span->start_day, &start_x, &start_y, &start_w, &start_h); *span_y = start_y + week_view->events_y_offset + span->row * (week_view->row_height + E_WEEK_VIEW_EVENT_Y_SPACING); if (num_days == 1) { *span_x = start_x; *span_w = start_w - 1; } else { e_week_view_get_day_position ( week_view, span->start_day + num_days - 1, &end_x, &end_y, &end_w, &end_h); *span_x = start_x; *span_w = end_x + end_w - start_x - 1; } return TRUE; } static gboolean ewv_pass_gdkevent_to_etext (EWeekView *week_view, GdkEvent *gevent) { g_return_val_if_fail (week_view != NULL, FALSE); g_return_val_if_fail (gevent != NULL, FALSE); if (week_view->editing_event_num != -1 && week_view->editing_span_num != -1) { EWeekViewEvent *event; EWeekViewEventSpan *span; if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num)) return FALSE; event = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num); if (!is_array_index_in_bounds (week_view->spans, event->spans_index + week_view->editing_span_num)) return FALSE; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + week_view->editing_span_num); if (span->text_item && E_IS_TEXT (span->text_item)) { GNOME_CANVAS_ITEM_GET_CLASS (span->text_item)->event (span->text_item, gevent); return TRUE; } } return FALSE; } static gboolean e_week_view_on_button_press (GtkWidget *widget, GdkEvent *button_event, EWeekView *week_view) { guint event_button = 0; gdouble event_x_win = 0; gdouble event_y_win = 0; gint x, y, day; gdk_event_get_button (button_event, &event_button); gdk_event_get_coords (button_event, &event_x_win, &event_y_win); /* Convert the mouse position to a week & day. */ x = (gint) event_x_win; y = (gint) event_y_win; day = e_week_view_convert_position_to_day (week_view, x, y); if (day == -1) return FALSE; if (ewv_pass_gdkevent_to_etext (week_view, button_event)) return TRUE; /* If an event is pressed just return. */ if (week_view->pressed_event_num != -1) return FALSE; if (event_button == 1 && button_event->type == GDK_2BUTTON_PRESS) { time_t dtstart, dtend; e_calendar_view_get_selected_time_range ((ECalendarView *) week_view, &dtstart, &dtend); if (dtstart < week_view->before_click_dtend && dtend > week_view->before_click_dtstart) { e_calendar_view_set_selected_time_range ( E_CALENDAR_VIEW (week_view), week_view->before_click_dtstart, week_view->before_click_dtend); } e_calendar_view_new_appointment_full (E_CALENDAR_VIEW (week_view), FALSE, FALSE, calendar_config_get_prefer_meeting ()); return TRUE; } if (event_button == 1) { GdkGrabStatus grab_status; GdkWindow *window; GdkDevice *event_device; guint32 event_time; /* Start the selection drag. */ if (!gtk_widget_has_focus (GTK_WIDGET (week_view)) && !gtk_widget_has_focus (GTK_WIDGET (week_view->main_canvas))) gtk_widget_grab_focus (GTK_WIDGET (week_view)); window = gtk_layout_get_bin_window (GTK_LAYOUT (widget)); event_device = gdk_event_get_device (button_event); event_time = gdk_event_get_time (button_event); grab_status = gdk_device_grab ( event_device, window, GDK_OWNERSHIP_NONE, FALSE, GDK_POINTER_MOTION_MASK | GDK_BUTTON_RELEASE_MASK, NULL, event_time); if (grab_status == GDK_GRAB_SUCCESS) { if (event_time - week_view->bc_event_time > 250) e_calendar_view_get_selected_time_range ( E_CALENDAR_VIEW (week_view), &week_view->before_click_dtstart, &week_view->before_click_dtend); week_view->bc_event_time = event_time; week_view->selection_start_day = day; week_view->selection_end_day = day; week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_END; g_signal_emit_by_name (week_view, "selected_time_changed"); /* FIXME: Optimise? */ gtk_widget_queue_draw (week_view->main_canvas); } } else if (event_button == 3) { if (!gtk_widget_has_focus (GTK_WIDGET (week_view))) gtk_widget_grab_focus (GTK_WIDGET (week_view)); if (day < week_view->selection_start_day || day > week_view->selection_end_day) { week_view->selection_start_day = day; week_view->selection_end_day = day; week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE; /* FIXME: Optimise? */ gtk_widget_queue_draw (week_view->main_canvas); } e_week_view_show_popup_menu (week_view, button_event, -1); } return TRUE; } static gboolean e_week_view_on_button_release (GtkWidget *widget, GdkEvent *button_event, EWeekView *week_view) { GdkDevice *event_device; guint32 event_time; event_device = gdk_event_get_device (button_event); event_time = gdk_event_get_time (button_event); if (week_view->selection_drag_pos != E_WEEK_VIEW_DRAG_NONE) { week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_NONE; gdk_device_ungrab (event_device, event_time); } else { ewv_pass_gdkevent_to_etext (week_view, button_event); } return FALSE; } static gboolean e_week_view_on_scroll (GtkWidget *widget, GdkEventScroll *scroll, EWeekView *week_view) { GtkRange *range; GtkAdjustment *adjustment; gdouble page_increment; gdouble new_value; gdouble page_size; gdouble lower; gdouble upper; gdouble value; GtkWidget *tool_window = g_object_get_data (G_OBJECT (week_view), "tooltip-window"); guint timeout; timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (week_view), "tooltip-timeout")); if (timeout) { g_source_remove (timeout); g_object_set_data (G_OBJECT (week_view), "tooltip-timeout", NULL); } if (tool_window) { gtk_widget_destroy (tool_window); g_object_set_data (G_OBJECT (week_view), "tooltip-window", NULL); } range = GTK_RANGE (week_view->vscrollbar); adjustment = gtk_range_get_adjustment (range); page_increment = gtk_adjustment_get_page_increment (adjustment); page_size = gtk_adjustment_get_page_size (adjustment); lower = gtk_adjustment_get_lower (adjustment); upper = gtk_adjustment_get_upper (adjustment); value = gtk_adjustment_get_value (adjustment); switch (scroll->direction) { case GDK_SCROLL_UP: new_value = value - page_increment; break; case GDK_SCROLL_DOWN: new_value = value + page_increment; break; case GDK_SCROLL_SMOOTH: if (scroll->delta_y < -0.001 || scroll->delta_y > 0.001) { new_value = value + scroll->delta_y * page_increment; break; } return FALSE; default: return FALSE; } new_value = CLAMP (new_value, lower, upper - page_size); gtk_adjustment_set_value (adjustment, new_value); return TRUE; } static gboolean e_week_view_on_motion (GtkWidget *widget, GdkEventMotion *mevent, EWeekView *week_view) { gint x, y, day; /* Convert the mouse position to a week & day. */ x = mevent->x; y = mevent->y; day = e_week_view_convert_position_to_day (week_view, x, y); if (day == -1) return FALSE; if (week_view->selection_drag_pos != E_WEEK_VIEW_DRAG_NONE) { e_week_view_update_selection (week_view, day); return TRUE; } ewv_pass_gdkevent_to_etext (week_view, (GdkEvent *) mevent); return FALSE; } /* Converts a position in the canvas window to a day offset from the first * day displayed. Returns -1 if the position is outside the grid. */ static gint e_week_view_convert_position_to_day (EWeekView *week_view, gint x, gint y) { GDateWeekday display_start_day; gint col, row, grid_x = -1, grid_y = -1, week, day; gint weekend_col; display_start_day = e_week_view_get_display_start_day (week_view); /* First we convert it to a grid position. */ for (col = 0; col <= week_view->columns; col++) { if (x < week_view->col_offsets[col]) { grid_x = col - 1; break; } } for (row = 0; row <= week_view->rows; row++) { if (y < week_view->row_offsets[row]) { grid_y = row - 1; break; } } /* If the mouse is outside the grid return FALSE. */ if (grid_x == -1 || grid_y == -1) return -1; /* Now convert the grid position to a week and day. */ if (e_week_view_get_multi_week_view (week_view)) { week = grid_y / 2; day = grid_x; if (e_week_view_get_compress_weekend (week_view)) { weekend_col = e_weekday_get_days_between ( display_start_day, G_DATE_SATURDAY); if (grid_x > weekend_col || (grid_x == weekend_col && grid_y % 2 == 1)) day++; } } else { week = 0; for (day = 0; day < 7; day++) { gint day_x = 0, day_y = 0, rows = 0; e_week_view_layout_get_day_position ( day, FALSE, 1, e_week_view_get_display_start_day (week_view), e_week_view_get_compress_weekend (week_view), &day_x, &day_y, &rows); if (grid_x == day_x && grid_y >= day_y && grid_y < day_y + rows) break; } if (day == 7) return -1; } return week * 7 + day; } static void e_week_view_update_selection (EWeekView *week_view, gint day) { gint tmp_day; gboolean need_redraw = FALSE; if (week_view->selection_drag_pos == E_WEEK_VIEW_DRAG_START) { if (day != week_view->selection_start_day) { need_redraw = TRUE; week_view->selection_start_day = day; } } else { if (day != week_view->selection_end_day) { need_redraw = TRUE; week_view->selection_end_day = day; } } /* Switch the drag position if necessary. */ if (week_view->selection_start_day > week_view->selection_end_day) { tmp_day = week_view->selection_start_day; week_view->selection_start_day = week_view->selection_end_day; week_view->selection_end_day = tmp_day; if (week_view->selection_drag_pos == E_WEEK_VIEW_DRAG_START) week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_END; else week_view->selection_drag_pos = E_WEEK_VIEW_DRAG_START; } /* FIXME: Optimise? */ if (need_redraw) { gtk_widget_queue_draw (week_view->main_canvas); } } static void e_week_view_free_events (EWeekView *week_view) { EWeekViewEvent *event; EWeekViewEventSpan *span; gint event_num, span_num, num_days, day; gboolean did_editing = week_view->editing_event_num != -1; /* Reset all our indices. */ week_view->pressed_event_num = -1; week_view->pressed_span_num = -1; week_view->editing_event_num = -1; week_view->editing_span_num = -1; week_view->popup_event_num = -1; for (event_num = 0; event_num < week_view->events->len; event_num++) { event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (is_comp_data_valid (event)) g_object_unref (event->comp_data); } g_array_set_size (week_view->events, 0); /* Destroy all the old canvas items. */ if (week_view->spans) { for (span_num = 0; span_num < week_view->spans->len; span_num++) { span = &g_array_index (week_view->spans, EWeekViewEventSpan, span_num); if (span->background_item) g_object_run_dispose (G_OBJECT (span->background_item)); if (span->text_item) g_object_run_dispose (G_OBJECT (span->text_item)); } g_array_free (week_view->spans, TRUE); week_view->spans = NULL; } /* Clear the number of rows used per day. */ num_days = e_week_view_get_weeks_shown (week_view) * 7; for (day = 0; day <= num_days; day++) { week_view->rows_per_day[day] = 0; } /* Hide all the jump buttons. */ for (day = 0; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) { gnome_canvas_item_hide (week_view->jump_buttons[day]); } if (did_editing) g_object_notify (G_OBJECT (week_view), "is-editing"); } /* This adds one event to the view, adding it to the appropriate array. */ static gboolean e_week_view_add_event (ECalComponent *comp, time_t start, time_t end, gboolean prepend, gpointer data) { AddEventData *add_event_data; EWeekViewEvent event; gint num_days; struct icaltimetype start_tt, end_tt; add_event_data = data; /* Check that the event times are valid. */ num_days = e_week_view_get_weeks_shown (add_event_data->week_view) * 7; g_return_val_if_fail (start <= end, TRUE); g_return_val_if_fail (start < add_event_data->week_view->day_starts[num_days], TRUE); g_return_val_if_fail (end > add_event_data->week_view->day_starts[0], TRUE); start_tt = icaltime_from_timet_with_zone ( start, FALSE, e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view))); end_tt = icaltime_from_timet_with_zone ( end, FALSE, e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view))); if (add_event_data->comp_data) { event.comp_data = g_object_ref (add_event_data->comp_data); } else { event.comp_data = g_object_new (E_TYPE_CAL_MODEL_COMPONENT, NULL); event.comp_data->client = e_cal_model_ref_default_client (e_calendar_view_get_model (E_CALENDAR_VIEW (add_event_data->week_view))); e_cal_component_abort_sequence (comp); event.comp_data->icalcomp = icalcomponent_new_clone (e_cal_component_get_icalcomponent (comp)); } event.start = start; event.end = end; event.tooltip = NULL; event.timeout = -1; event.color = NULL; event.spans_index = 0; event.num_spans = 0; event.comp_data->instance_start = start; event.comp_data->instance_end = end; event.start_minute = start_tt.hour * 60 + start_tt.minute; event.end_minute = end_tt.hour * 60 + end_tt.minute; if (event.end_minute == 0 && start != end) event.end_minute = 24 * 60; event.different_timezone = FALSE; if (!cal_comp_util_compare_event_timezones ( comp, event.comp_data->client, e_calendar_view_get_timezone (E_CALENDAR_VIEW (add_event_data->week_view)))) event.different_timezone = TRUE; if (prepend) g_array_prepend_val (add_event_data->week_view->events, event); else g_array_append_val (add_event_data->week_view->events, event); add_event_data->week_view->events_sorted = FALSE; add_event_data->week_view->events_need_layout = TRUE; return TRUE; } /* This lays out the events, or reshapes them, as necessary. */ static void e_week_view_check_layout (EWeekView *week_view) { /* Don't bother if we aren't visible. */ if (!E_CALENDAR_VIEW (week_view)->in_focus) { e_week_view_free_events (week_view); week_view->requires_update = TRUE; return; } /* Make sure the events are sorted (by start and size). */ e_week_view_ensure_events_sorted (week_view); if (week_view->events_need_layout) week_view->spans = e_week_view_layout_events ( week_view->events, week_view->spans, e_week_view_get_multi_week_view (week_view), e_week_view_get_weeks_shown (week_view), e_week_view_get_compress_weekend (week_view), e_week_view_get_display_start_day (week_view), week_view->day_starts, week_view->rows_per_day); if (week_view->events_need_layout || week_view->events_need_reshape) e_week_view_reshape_events (week_view); week_view->events_need_layout = FALSE; week_view->events_need_reshape = FALSE; } static void e_week_view_ensure_events_sorted (EWeekView *week_view) { if (!week_view->events_sorted) { qsort ( week_view->events->data, week_view->events->len, sizeof (EWeekViewEvent), e_week_view_event_sort_func); week_view->events_sorted = TRUE; } } gint e_week_view_event_sort_func (gconstpointer arg1, gconstpointer arg2) { EWeekViewEvent *event1, *event2; event1 = (EWeekViewEvent *) arg1; event2 = (EWeekViewEvent *) 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 void e_week_view_reshape_events (EWeekView *week_view) { EWeekViewEvent *event; GDateWeekday display_start_day; gint event_num, span_num; gint num_days, day, day_x, day_y, day_w, day_h, max_rows; gboolean is_weekend; for (event_num = 0; event_num < week_view->events->len; event_num++) { event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) continue; for (span_num = 0; span_num < event->num_spans; span_num++) { gchar *current_comp_string; e_week_view_reshape_event_span ( week_view, event_num, span_num); if (week_view->last_edited_comp_string == NULL) continue; current_comp_string = icalcomponent_as_ical_string_r (event->comp_data->icalcomp); if (strncmp (current_comp_string, week_view->last_edited_comp_string,50) == 0) { EWeekViewEventSpan *span; if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) { g_free (current_comp_string); continue; } span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); e_canvas_item_grab_focus (span->text_item, TRUE); g_free (week_view->last_edited_comp_string); week_view->last_edited_comp_string = NULL; } g_free (current_comp_string); } } /* Reshape the jump buttons and show/hide them as appropriate. */ num_days = e_week_view_get_weeks_shown (week_view) * 7; display_start_day = e_week_view_get_display_start_day (week_view); for (day = 0; day < num_days; day++) { switch (e_weekday_add_days (display_start_day, day)) { case G_DATE_SATURDAY: case G_DATE_SUNDAY: is_weekend = TRUE; break; default: is_weekend = FALSE; break; } if (!is_weekend || ( e_week_view_get_multi_week_view (week_view) && !e_week_view_get_compress_weekend (week_view))) max_rows = week_view->rows_per_cell; else max_rows = week_view->rows_per_compressed_cell; /* Determine whether the jump button should be shown. */ if (week_view->rows_per_day[day] <= max_rows) { gnome_canvas_item_hide (week_view->jump_buttons[day]); } else { cairo_matrix_t matrix; e_week_view_get_day_position ( week_view, day, &day_x, &day_y, &day_w, &day_h); cairo_matrix_init_translate ( &matrix, day_x + day_w - E_WEEK_VIEW_JUMP_BUTTON_X_PAD - E_WEEK_VIEW_JUMP_BUTTON_WIDTH, day_y + day_h - E_WEEK_VIEW_JUMP_BUTTON_Y_PAD - E_WEEK_VIEW_JUMP_BUTTON_HEIGHT); gnome_canvas_item_set_matrix (week_view->jump_buttons[day], &matrix); gnome_canvas_item_show (week_view->jump_buttons[day]); gnome_canvas_item_raise_to_top (week_view->jump_buttons[day]); } } for (day = num_days; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) { gnome_canvas_item_hide (week_view->jump_buttons[day]); } } static EWeekViewEvent * tooltip_get_view_event (EWeekView *week_view, gint day, gint event_num) { EWeekViewEvent *pevent; if (!is_array_index_in_bounds (week_view->events, event_num)) return NULL; pevent = &g_array_index (week_view->events, EWeekViewEvent, event_num); return pevent; } static void tooltip_destroy (EWeekView *week_view, GnomeCanvasItem *item) { gint event_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num")); EWeekViewEvent *pevent; guint timeout; timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (week_view), "tooltip-timeout")); if (timeout) { g_source_remove (timeout); g_object_set_data (G_OBJECT (week_view), "tooltip-timeout", NULL); } pevent = tooltip_get_view_event (week_view, -1, event_num); if (pevent) { if (pevent->tooltip && g_object_get_data (G_OBJECT (week_view), "tooltip-window")) { gtk_widget_destroy (pevent->tooltip); pevent->tooltip = NULL; } g_object_set_data (G_OBJECT (week_view), "tooltip-window", NULL); } } static gboolean tooltip_event_cb (GnomeCanvasItem *item, GdkEvent *event, EWeekView *view) { gint event_num = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num")); EWeekViewEvent *pevent; pevent = tooltip_get_view_event (view, -1, event_num); switch (event->type) { case GDK_ENTER_NOTIFY: if (view->editing_event_num == -1) { ECalendarViewEventData *data; data = g_malloc (sizeof (ECalendarViewEventData)); pevent->x = ((GdkEventCrossing *) event)->x_root; pevent->y = ((GdkEventCrossing *) event)->y_root; pevent->tooltip = NULL; data->cal_view = (ECalendarView *) view; data->day = -1; data->event_num = event_num; data->get_view_event = (ECalendarViewEvent * (*)(ECalendarView *, int, gint)) tooltip_get_view_event; pevent->timeout = g_timeout_add_full ( G_PRIORITY_DEFAULT, 500, (GSourceFunc) e_calendar_view_get_tooltips, data, (GDestroyNotify) g_free); g_object_set_data ((GObject *) view, "tooltip-timeout", GUINT_TO_POINTER (pevent->timeout)); return TRUE; } else { return FALSE; } case GDK_MOTION_NOTIFY: pevent->x = ((GdkEventMotion *) event)->x_root; pevent->y = ((GdkEventMotion *) event)->y_root; pevent->tooltip = (GtkWidget *) g_object_get_data (G_OBJECT (view), "tooltip-window"); if (pevent->tooltip) { e_calendar_view_move_tip (pevent->tooltip, pevent->x + 16, pevent->y + 16); } return TRUE; case GDK_LEAVE_NOTIFY: case GDK_KEY_PRESS: case GDK_BUTTON_PRESS: tooltip_destroy (view, item); default: return FALSE; } } static const gchar * get_comp_summary (ECalClient *client, icalcomponent *icalcomp, gboolean *free_text) { const gchar *my_summary, *location; const gchar *summary; gboolean my_free_text = FALSE; g_return_val_if_fail (icalcomp != NULL && free_text != NULL, NULL); my_summary = e_calendar_view_get_icalcomponent_summary (client, icalcomp, &my_free_text); location = icalcomponent_get_location (icalcomp); if (location && *location) { *free_text = TRUE; summary = g_strdup_printf ("%s (%s)", my_summary, location); if (my_free_text) g_free ((gchar *) my_summary); } else { *free_text = my_free_text; summary = my_summary; } return summary; } static void e_week_view_reshape_event_span (EWeekView *week_view, gint event_num, gint span_num) { ECalendarView *cal_view; ECalModel *model; ESourceRegistry *registry; EWeekViewEvent *event; EWeekViewEventSpan *span; gint span_x, span_y, span_w, num_icons, icons_width, time_width; gint min_text_x, max_text_w, width; gboolean show_icons = TRUE, use_max_width = FALSE; gboolean one_day_event; ECalComponent *comp; gdouble text_x, text_y, text_w, text_h; gchar *text, *end_of_line; gint line_len, text_width; PangoFontDescription *font_desc; PangoContext *pango_context; PangoFontMetrics *font_metrics; PangoLayout *layout; cal_view = E_CALENDAR_VIEW (week_view); model = e_calendar_view_get_model (cal_view); registry = e_cal_model_get_registry (model); if (!is_array_index_in_bounds (week_view->events, event_num)) return; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) return; if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) return; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); comp = e_cal_component_new (); e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp)); one_day_event = e_week_view_is_one_day_event (week_view, event_num); /* If the span will not be visible destroy the canvas items and * return. */ if (!e_week_view_get_span_position (week_view, event_num, span_num, &span_x, &span_y, &span_w)) { if (span->background_item) g_object_run_dispose (G_OBJECT (span->background_item)); if (span->text_item) g_object_run_dispose (G_OBJECT (span->text_item)); span->background_item = NULL; span->text_item = NULL; g_object_unref (comp); return; } /* Set up Pango prerequisites */ font_desc = gtk_widget_get_style (GTK_WIDGET (week_view))->font_desc; pango_context = gtk_widget_get_pango_context (GTK_WIDGET (week_view)); font_metrics = pango_context_get_metrics ( pango_context, font_desc, pango_context_get_language (pango_context)); layout = pango_layout_new (pango_context); /* If we are editing a long event we don't show the icons and the EText * item uses the maximum width available. */ if (!one_day_event && week_view->editing_event_num == event_num && week_view->editing_span_num == span_num) { show_icons = FALSE; use_max_width = TRUE; } /* Calculate how many icons we need to show. */ num_icons = 0; if (show_icons) { if (e_cal_component_has_alarms (comp)) num_icons++; if (e_cal_component_has_recurrences (comp) || e_cal_component_is_instance (comp)) num_icons++; if (e_cal_component_has_attachments (comp)) num_icons++; if (e_cal_component_has_attendees (comp)) num_icons++; if (event->different_timezone) num_icons++; num_icons += cal_comp_util_get_n_icons (comp, NULL); } /* Create the background canvas item if necessary. */ if (!span->background_item) { span->background_item = gnome_canvas_item_new ( GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root), e_week_view_event_item_get_type (), NULL); } g_object_set_data ((GObject *) span->background_item, "event-num", GINT_TO_POINTER (event_num)); g_signal_connect ( span->background_item, "event", G_CALLBACK (tooltip_event_cb), week_view); gnome_canvas_item_set ( span->background_item, "event_num", event_num, "span_num", span_num, NULL); /* Create the text item if necessary. */ if (!span->text_item) { const gchar *summary; GtkWidget *widget; GdkColor color; gboolean free_text = FALSE; widget = (GtkWidget *) week_view; color = e_week_view_get_text_color (week_view, event, widget); summary = get_comp_summary (event->comp_data->client, event->comp_data->icalcomp, &free_text); span->text_item = gnome_canvas_item_new ( GNOME_CANVAS_GROUP (GNOME_CANVAS (week_view->main_canvas)->root), e_text_get_type (), "clip", TRUE, "max_lines", 1, "editable", TRUE, "text", summary ? summary : "", "use_ellipsis", TRUE, "fill_color_gdk", &color, "im_context", E_CANVAS (week_view->main_canvas)->im_context, NULL); if (free_text) g_free ((gchar *) summary); if (e_client_check_capability (E_CLIENT (event->comp_data->client), CAL_STATIC_CAPABILITY_HAS_UNACCEPTED_MEETING) && e_cal_util_component_has_attendee (event->comp_data->icalcomp)) { set_text_as_bold (event, span, registry); } g_object_set_data (G_OBJECT (span->text_item), "event-num", GINT_TO_POINTER (event_num)); g_signal_connect ( span->text_item, "event", G_CALLBACK (e_week_view_on_text_item_event), week_view); g_signal_emit_by_name ( G_OBJECT (week_view), "event_added", event); } /* Calculate the position of the text item. * For events < 1 day it starts after the times & icons and ends at the * right edge of the span. * For events >= 1 day we need to determine whether times are shown at * the start and end of the span, then try to center the text item with * the icons in the middle, but making sure we don't go over the times. */ /* Calculate the space necessary to display a time, e.g. "13:00". */ time_width = e_week_view_get_time_string_width (week_view); /* Calculate the space needed for the icons. */ icons_width = (E_WEEK_VIEW_ICON_WIDTH + E_WEEK_VIEW_ICON_X_PAD) * num_icons - E_WEEK_VIEW_ICON_X_PAD + E_WEEK_VIEW_ICON_R_PAD; /* The y position and height are the same for both event types. */ text_y = span_y + E_WEEK_VIEW_EVENT_BORDER_HEIGHT + E_WEEK_VIEW_EVENT_TEXT_Y_PAD; text_h = PANGO_PIXELS (pango_font_metrics_get_ascent (font_metrics)) + PANGO_PIXELS (pango_font_metrics_get_descent (font_metrics)); if (one_day_event) { /* Note that 1-day events don't have a border. Although we * still use the border height to position the events * vertically so they still line up neatly (see above), * we don't use the border width or edge padding at all. */ text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD; switch (week_view->time_format) { case E_WEEK_VIEW_TIME_BOTH_SMALL_MIN: case E_WEEK_VIEW_TIME_BOTH: /* These have 2 time strings with a small space between * them and some space before the EText item. */ text_x += time_width * 2 + E_WEEK_VIEW_EVENT_TIME_SPACING + E_WEEK_VIEW_EVENT_TIME_X_PAD; break; case E_WEEK_VIEW_TIME_START_SMALL_MIN: case E_WEEK_VIEW_TIME_START: /* These have just 1 time string with some space * before the EText item. */ text_x += time_width + E_WEEK_VIEW_EVENT_TIME_X_PAD; break; case E_WEEK_VIEW_TIME_NONE: break; } /* The icons_width includes space on the right of the icons. */ text_x += icons_width; /* The width of the EText item extends right to the edge of the * event, just inside the border. */ text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD - text_x; } else { if (use_max_width) { /* When we are editing the event we use all the * available width. */ text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD + E_WEEK_VIEW_EVENT_BORDER_WIDTH + E_WEEK_VIEW_EVENT_EDGE_X_PAD; text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD - E_WEEK_VIEW_EVENT_BORDER_WIDTH - E_WEEK_VIEW_EVENT_EDGE_X_PAD - text_x; } else { text = NULL; /* Get the width of the text of the event. This is a * bit of a hack. It would be better if EText could * tell us this. */ g_object_get (span->text_item, "text", &text, NULL); text_width = 0; if (text) { /* It should only have one line of text in it. * I'm not sure we need this any more. */ end_of_line = strchr (text, '\n'); if (end_of_line) line_len = end_of_line - text; else line_len = strlen (text); pango_layout_set_text (layout, text, line_len); pango_layout_get_pixel_size (layout, &text_width, NULL); g_free (text); } /* Add on the width of the icons and find the default * position, which centers the icons + text. */ width = text_width + icons_width; text_x = span_x + (span_w - width) / 2; /* Now calculate the left-most valid position, and make * sure we don't go to the left of that. */ min_text_x = span_x + E_WEEK_VIEW_EVENT_L_PAD + E_WEEK_VIEW_EVENT_BORDER_WIDTH + E_WEEK_VIEW_EVENT_EDGE_X_PAD; /* See if we will want to display the start time, and * if so take that into account. */ if (event->start > week_view->day_starts[span->start_day]) min_text_x += time_width + E_WEEK_VIEW_EVENT_TIME_X_PAD; /* Now make sure we don't go to the left of the minimum * position. */ text_x = MAX (text_x, min_text_x); /* Now calculate the largest valid width, using the * calculated x position, and make sure we don't * exceed that. */ max_text_w = span_x + span_w - E_WEEK_VIEW_EVENT_R_PAD - E_WEEK_VIEW_EVENT_BORDER_WIDTH - E_WEEK_VIEW_EVENT_EDGE_X_PAD - text_x; if (event->end < week_view->day_starts[span->start_day + span->num_days]) max_text_w -= time_width + E_WEEK_VIEW_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; } } /* Make sure we don't try to use a negative width. */ text_w = MAX (text_w, 0); gnome_canvas_item_set ( span->text_item, "clip_width", (gdouble) text_w, "clip_height", (gdouble) text_h, NULL); e_canvas_item_move_absolute (span->text_item, text_x, text_y); g_object_unref (comp); g_object_unref (layout); pango_font_metrics_unref (font_metrics); } gboolean e_week_view_start_editing_event (EWeekView *week_view, gint event_num, gint span_num, gchar *initial_text) { EWeekViewEvent *event; EWeekViewEventSpan *span; ETextEventProcessor *event_processor = NULL; ETextEventProcessorCommand command; ECalModelComponent *comp_data; /* If we are already editing the event, just return. */ if (event_num == week_view->editing_event_num && span_num == week_view->editing_span_num) return TRUE; if (!is_array_index_in_bounds (week_view->events, event_num)) return FALSE; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) return FALSE; if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) return FALSE; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); if (e_client_is_readonly (E_CLIENT (event->comp_data->client))) return FALSE; /* If the event is not shown, don't try to edit it. */ if (!span->text_item) return FALSE; if (week_view->editing_event_num >= 0) { EWeekViewEvent *editing; if (!is_array_index_in_bounds (week_view->events, week_view->editing_event_num)) return FALSE; editing = &g_array_index (week_view->events, EWeekViewEvent, week_view->editing_event_num); /* do not change to other part of same component - the event is spread into more days */ if (editing && event && editing->comp_data == event->comp_data) return FALSE; } gnome_canvas_item_set ( span->text_item, "text", initial_text ? initial_text : icalcomponent_get_summary (event->comp_data->icalcomp), NULL); /* Save the comp_data value because we use that as our invariant */ comp_data = event->comp_data; e_canvas_item_grab_focus (span->text_item, TRUE); /* If the above focus caused things to redraw, then find the * the event and the span again */ if (event_num < week_view->events->len) event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (event_num >= week_view->events->len || event->comp_data != comp_data) { /* When got in because of other comp_data, then be sure we go through all events */ event_num = week_view->events->len; /* Unfocussing can cause a removal but not a new * addition so just run backwards through the * events */ for (event_num--; event_num >= 0; event_num--) { event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (event->comp_data == comp_data) break; } g_return_val_if_fail (event_num >= 0, FALSE); } if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) return FALSE; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); /* Try to move the cursor to the end of the text. */ g_object_get (span->text_item, "event_processor", &event_processor, NULL); if (event_processor) { command.action = E_TEP_MOVE; command.position = E_TEP_END_OF_BUFFER; g_signal_emit_by_name ( event_processor, "command", &command); } return TRUE; } /* This stops any current edit. */ void e_week_view_stop_editing_event (EWeekView *week_view) { GtkWidget *toplevel; /* Check we are editing an event. */ if (week_view->editing_event_num == -1) return; /* Set focus to the toplevel so the item loses focus. */ toplevel = gtk_widget_get_toplevel (GTK_WIDGET (week_view)); if (toplevel && GTK_IS_WINDOW (toplevel)) gtk_window_set_focus (GTK_WINDOW (toplevel), NULL); } /* Cancels the current edition by resetting the appointment's text to its original value */ static void cancel_editing (EWeekView *week_view) { gint event_num, span_num; EWeekViewEvent *event; EWeekViewEventSpan *span; const gchar *summary; event_num = week_view->editing_event_num; span_num = week_view->editing_span_num; g_return_if_fail (event_num != -1); if (!is_array_index_in_bounds (week_view->events, event_num)) return; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) return; if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) return; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); /* Reset the text to what was in the component */ summary = icalcomponent_get_summary (event->comp_data->icalcomp); g_object_set (span->text_item, "text", summary ? summary : "", NULL); /* Stop editing */ e_week_view_stop_editing_event (week_view); } static gboolean e_week_view_on_text_item_event (GnomeCanvasItem *item, GdkEvent *gdk_event, EWeekView *week_view) { EWeekViewEvent *event; gint event_num, span_num; gint nevent = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "event-num")); EWeekViewEvent *pevent; guint event_button = 0; guint event_keyval = 0; gdouble event_x_root = 0; gdouble event_y_root = 0; pevent = tooltip_get_view_event (week_view, -1, nevent); switch (gdk_event->type) { case GDK_KEY_PRESS: tooltip_destroy (week_view, item); gdk_event_get_keyval (gdk_event, &event_keyval); if (!E_TEXT (item)->preedit_len && event_keyval == GDK_KEY_Return) { /* We set the keyboard focus to the EDayView, so the * EText item loses it and stops the edit. */ gtk_widget_grab_focus (GTK_WIDGET (week_view)); /* Stop the signal last or we will also stop any * other events getting to the EText item. */ g_signal_stop_emission_by_name (item, "event"); return TRUE; } else if (event_keyval == GDK_KEY_Escape) { cancel_editing (week_view); g_signal_stop_emission_by_name (item, "event"); /* focus should go to week view when stop editing */ gtk_widget_grab_focus (GTK_WIDGET (week_view)); return TRUE; } break; case GDK_2BUTTON_PRESS: if (!e_week_view_find_event_from_item (week_view, item, &event_num, &span_num)) return FALSE; if (!is_array_index_in_bounds (week_view->events, event_num)) return FALSE; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) return FALSE; /* if we started to editing new item on the canvas, then do not open editing dialog until it's saved, * because the save of the event recalculates event numbers and you can edit different one */ if (!is_icalcomp_on_the_server (event->comp_data->icalcomp, event->comp_data->client)) return TRUE; e_calendar_view_edit_appointment ( E_CALENDAR_VIEW (week_view), event->comp_data->client, event->comp_data->icalcomp, EDIT_EVENT_AUTODETECT); g_signal_stop_emission_by_name (item, "event"); return TRUE; case GDK_BUTTON_PRESS: tooltip_destroy (week_view, item); if (!e_week_view_find_event_from_item (week_view, item, &event_num, &span_num)) return FALSE; gdk_event_get_button (gdk_event, &event_button); if (event_button == 3) { EWeekViewEvent *e; if (E_TEXT (item)->editing) { e_week_view_stop_editing_event (week_view); gtk_widget_grab_focus (GTK_WIDGET (week_view)); return FALSE; } if (!is_array_index_in_bounds (week_view->events, event_num)) return FALSE; e = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!gtk_widget_has_focus (GTK_WIDGET (week_view))) gtk_widget_grab_focus (GTK_WIDGET (week_view)); e_week_view_set_selected_time_range_visible (week_view, e->start, e->end); e_week_view_show_popup_menu ( week_view, gdk_event, event_num); g_signal_stop_emission_by_name ( item->canvas, "button_press_event"); return TRUE; } if (event_button != 3) { week_view->pressed_event_num = event_num; week_view->pressed_span_num = span_num; } /* Only let the EText handle the event while editing. */ if (!E_TEXT (item)->editing) { gdouble event_x_win = 0; gdouble event_y_win = 0; g_signal_stop_emission_by_name (item, "event"); gdk_event_get_coords ( gdk_event, &event_x_win, &event_y_win); week_view->drag_event_x = (gint) event_x_win; week_view->drag_event_y = (gint) event_y_win; /* FIXME: Remember the day offset from the start of * the event, for DnD. */ return TRUE; } break; case GDK_BUTTON_RELEASE: if (!E_TEXT (item)->editing) { /* This shouldn't ever happen. */ if (!e_week_view_find_event_from_item (week_view, item, &event_num, &span_num)) return FALSE; if (week_view->pressed_event_num != -1 && week_view->pressed_event_num == event_num && week_view->pressed_span_num == span_num) { e_week_view_start_editing_event ( week_view, event_num, span_num, NULL); week_view->pressed_event_num = -1; } /* Stop the signal last or we will also stop any * other events getting to the EText item. */ g_signal_stop_emission_by_name (item, "event"); return TRUE; } week_view->pressed_event_num = -1; break; case GDK_ENTER_NOTIFY: { ECalendarViewEventData *data; gint nspan; if (week_view->editing_event_num != -1 || !e_week_view_find_event_from_item (week_view, item, &nevent, &nspan)) return FALSE; g_object_set_data ((GObject *) item, "event-num", GINT_TO_POINTER (nevent)); pevent = tooltip_get_view_event (week_view, -1, nevent); data = g_malloc (sizeof (ECalendarViewEventData)); gdk_event_get_root_coords ( gdk_event, &event_x_root, &event_y_root); pevent->x = (gint) event_x_root; pevent->y = (gint) event_y_root; pevent->tooltip = NULL; data->cal_view = (ECalendarView *) week_view; data->day = -1; data->event_num = nevent; data->get_view_event = (ECalendarViewEvent * (*)(ECalendarView *, int, gint)) tooltip_get_view_event; pevent->timeout = g_timeout_add_full ( G_PRIORITY_DEFAULT, 500, (GSourceFunc) e_calendar_view_get_tooltips, data, (GDestroyNotify) g_free); g_object_set_data ((GObject *) week_view, "tooltip-timeout", GUINT_TO_POINTER (pevent->timeout)); return TRUE; } case GDK_LEAVE_NOTIFY: tooltip_destroy (week_view, item); return FALSE; case GDK_MOTION_NOTIFY: gdk_event_get_root_coords ( gdk_event, &event_x_root, &event_y_root); pevent->x = (gint) event_x_root; pevent->y = (gint) event_y_root; pevent->tooltip = (GtkWidget *) g_object_get_data (G_OBJECT (week_view), "tooltip-window"); if (pevent->tooltip) { e_calendar_view_move_tip (pevent->tooltip, pevent->x + 16, pevent->y + 16); } return TRUE; case GDK_FOCUS_CHANGE: if (gdk_event->focus_change.in) { e_week_view_on_editing_started (week_view, item); } else { e_week_view_on_editing_stopped (week_view, item); } return FALSE; default: break; } return FALSE; } static gboolean e_week_view_event_move (ECalendarView *cal_view, ECalViewMoveDirection direction) { EWeekViewEvent *event; gint event_num, adjust_days, current_start_day, current_end_day; time_t start_dt, end_dt; struct icaltimetype start_time,end_time; EWeekView *week_view = E_WEEK_VIEW (cal_view); gboolean is_all_day = FALSE; event_num = week_view->editing_event_num; adjust_days = 0; /* If no item is being edited, just return. */ if (event_num == -1) return FALSE; if (!is_array_index_in_bounds (week_view->events, event_num)) return FALSE; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) return FALSE; end_dt = event->end; start_time = icalcomponent_get_dtstart (event->comp_data->icalcomp); end_time = icalcomponent_get_dtend (event->comp_data->icalcomp); if (start_time.is_date && end_time.is_date) is_all_day = TRUE; current_end_day = e_week_view_get_day_offset_of_event (week_view,end_dt); switch (direction) { case E_CAL_VIEW_MOVE_UP: adjust_days = e_week_view_get_adjust_days_for_move_up (week_view,current_end_day); break; case E_CAL_VIEW_MOVE_DOWN: adjust_days = e_week_view_get_adjust_days_for_move_down (week_view,current_end_day); break; case E_CAL_VIEW_MOVE_LEFT: adjust_days = e_week_view_get_adjust_days_for_move_left (week_view,current_end_day); break; case E_CAL_VIEW_MOVE_RIGHT: adjust_days = e_week_view_get_adjust_days_for_move_right (week_view,current_end_day); break; default: break; } icaltime_adjust (&start_time ,adjust_days,0,0,0); icaltime_adjust (&end_time ,adjust_days,0,0,0); start_dt = icaltime_as_timet_with_zone ( start_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); end_dt = icaltime_as_timet_with_zone ( end_time, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); current_start_day = e_week_view_get_day_offset_of_event (week_view,start_dt); current_end_day = e_week_view_get_day_offset_of_event (week_view,end_dt); if (is_all_day) current_end_day--; if (current_start_day < 0) return TRUE; if (current_end_day >= e_week_view_get_weeks_shown (week_view) * 7) return TRUE; e_week_view_change_event_time (week_view, start_dt, end_dt, is_all_day); return TRUE; } static gint e_week_view_get_day_offset_of_event (EWeekView *week_view, time_t event_time) { time_t first_day = week_view->day_starts[0]; if (event_time - first_day < 0) return -1; else return (event_time - first_day) / (24 * 60 * 60); } void e_week_view_scroll_a_step (EWeekView *week_view, ECalViewMoveDirection direction) { GtkAdjustment *adjustment; GtkRange *range; gdouble step_increment; gdouble page_size; gdouble new_value; gdouble lower; gdouble upper; gdouble value; range = GTK_RANGE (week_view->vscrollbar); adjustment = gtk_range_get_adjustment (range); step_increment = gtk_adjustment_get_step_increment (adjustment); page_size = gtk_adjustment_get_page_size (adjustment); lower = gtk_adjustment_get_lower (adjustment); upper = gtk_adjustment_get_upper (adjustment); value = gtk_adjustment_get_value (adjustment); switch (direction) { case E_CAL_VIEW_MOVE_UP: new_value = value - step_increment; break; case E_CAL_VIEW_MOVE_DOWN: new_value = value + step_increment; break; case E_CAL_VIEW_MOVE_PAGE_UP: new_value = value - page_size; break; case E_CAL_VIEW_MOVE_PAGE_DOWN: new_value = value + page_size; break; default: return; } new_value = CLAMP (new_value, lower, upper - page_size); gtk_adjustment_set_value (adjustment, new_value); } static void e_week_view_change_event_time (EWeekView *week_view, time_t start_dt, time_t end_dt, gboolean is_all_day) { EWeekViewEvent *event; gint event_num; ECalComponent *comp; ECalComponentDateTime date; struct icaltimetype itt; ECalClient *client; CalObjModType mod = CALOBJ_MOD_ALL; GtkWindow *toplevel; event_num = week_view->editing_event_num; /* If no item is being edited, just return. */ if (event_num == -1) return; if (!is_array_index_in_bounds (week_view->events, event_num)) return; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) return; client = event->comp_data->client; /* We use a temporary shallow copy of the ico since we don't want to * change the original ico here. Otherwise we would not detect that * the event's time had changed in the "update_event" callback. */ comp = e_cal_component_new (); e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp)); date.value = &itt; /* FIXME: Should probably keep the timezone of the original start * and end times. */ date.tzid = icaltimezone_get_tzid (e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); *date.value = icaltime_from_timet_with_zone (start_dt, is_all_day, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); cal_comp_set_dtstart_with_oldzone (client, comp, &date); *date.value = icaltime_from_timet_with_zone (end_dt, is_all_day, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); cal_comp_set_dtend_with_oldzone (client, comp, &date); e_cal_component_commit_sequence (comp); if (week_view->last_edited_comp_string != NULL) { g_free (week_view->last_edited_comp_string); week_view->last_edited_comp_string = NULL; } week_view->last_edited_comp_string = e_cal_component_get_as_string (comp); if (e_cal_component_has_recurrences (comp)) { if (!recur_component_dialog (client, comp, &mod, NULL, FALSE)) { gtk_widget_queue_draw (week_view->main_canvas); goto out; } if (mod == CALOBJ_MOD_ALL) comp_util_sanitize_recurrence_master (comp, client); if (mod == CALOBJ_MOD_THIS) { e_cal_component_set_rdate_list (comp, NULL); e_cal_component_set_rrule_list (comp, NULL); e_cal_component_set_exdate_list (comp, NULL); e_cal_component_set_exrule_list (comp, NULL); } } else if (e_cal_component_is_instance (comp)) mod = CALOBJ_MOD_THIS; toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (week_view))); e_cal_component_commit_sequence (comp); e_calendar_view_modify_and_send ( E_CALENDAR_VIEW (week_view), comp, client, mod, toplevel, TRUE); out: g_object_unref (comp); } static void e_week_view_on_editing_started (EWeekView *week_view, GnomeCanvasItem *item) { gint event_num, span_num; if (!e_week_view_find_event_from_item (week_view, item, &event_num, &span_num)) return; week_view->editing_event_num = event_num; week_view->editing_span_num = span_num; /* We need to reshape long events so the whole width is used while * editing. */ if (!e_week_view_is_one_day_event (week_view, event_num)) { e_week_view_reshape_event_span ( week_view, event_num, span_num); } g_signal_emit_by_name (week_view, "selection_changed"); g_object_notify (G_OBJECT (week_view), "is-editing"); } static void e_week_view_on_editing_stopped (EWeekView *week_view, GnomeCanvasItem *item) { gint event_num, span_num; EWeekViewEvent *event; EWeekViewEventSpan *span; gchar *text = NULL; ECalComponent *comp; ECalComponentText summary; ECalClient *client; const gchar *uid; gboolean on_server; /* 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. */ event_num = week_view->editing_event_num; span_num = week_view->editing_span_num; /* If no item is being edited, just return. */ if (event_num == -1) return; if (!is_array_index_in_bounds (week_view->events, event_num)) return; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) return; if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) return; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); /* Reset the edit fields. */ week_view->editing_event_num = -1; /* Check that the event is still valid. */ uid = icalcomponent_get_uid (event->comp_data->icalcomp); if (!uid) { g_object_notify (G_OBJECT (week_view), "is-editing"); return; } text = NULL; g_object_set (span->text_item, "handle_popup", FALSE, NULL); g_object_get (span->text_item, "text", &text, NULL); comp = e_cal_component_new (); e_cal_component_set_icalcomponent (comp, icalcomponent_new_clone (event->comp_data->icalcomp)); client = event->comp_data->client; on_server = cal_comp_is_on_server (comp, client); if (string_is_empty (text) && !on_server) { e_cal_component_get_uid (comp, &uid); g_signal_handlers_disconnect_by_func (item, e_week_view_on_text_item_event, week_view); e_week_view_foreach_event_with_uid (week_view, uid, e_week_view_remove_event_cb, NULL); week_view->event_destroyed = TRUE; gtk_widget_queue_draw (week_view->main_canvas); e_week_view_check_layout (week_view); goto out; } /* Only update the summary if necessary. */ e_cal_component_get_summary (comp, &summary); if (summary.value && !strcmp (text, summary.value)) { gboolean free_text = FALSE; const gchar *summary; summary = get_comp_summary (event->comp_data->client, event->comp_data->icalcomp, &free_text); g_object_set (span->text_item, "text", summary ? summary : "", NULL); if (free_text) g_free ((gchar *) summary); if (!e_week_view_is_one_day_event (week_view, event_num)) e_week_view_reshape_event_span (week_view, event_num, span_num); } else if (summary.value || !string_is_empty (text)) { icalcomponent *icalcomp = e_cal_component_get_icalcomponent (comp); summary.value = text; summary.altrep = NULL; e_cal_component_set_summary (comp, &summary); e_cal_component_commit_sequence (comp); if (!on_server) { gchar *uid = NULL; GError *error = NULL; e_cal_client_create_object_sync ( client, icalcomp, &uid, NULL, &error); if (error != NULL) { g_warning ( G_STRLOC ": Could not create the object! %s", error->message); uid = NULL; } else { if (uid) icalcomponent_set_uid (icalcomp, uid); e_calendar_view_emit_user_created ( E_CALENDAR_VIEW (week_view), client); } if (uid) g_free (uid); if (error) g_error_free (error); /* we remove the object since we either got the update from the server or failed */ e_week_view_remove_event_cb (week_view, event_num, NULL); } else { CalObjModType mod = CALOBJ_MOD_ALL; GtkWindow *toplevel; if (e_cal_component_has_recurrences (comp)) { if (!recur_component_dialog (client, comp, &mod, NULL, FALSE)) { goto out; } if (mod == CALOBJ_MOD_ALL) comp_util_sanitize_recurrence_master (comp, client); if (mod == CALOBJ_MOD_THIS) { ECalComponentDateTime dt; struct icaltimetype tt; gchar *tzid; e_cal_component_get_dtstart (comp, &dt); if (dt.value->zone) { tt = icaltime_from_timet_with_zone ( event->comp_data->instance_start, dt.value->is_date, dt.value->zone); } else { tt = icaltime_from_timet_with_zone ( event->comp_data->instance_start, dt.value->is_date, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); } tzid = g_strdup (dt.tzid); e_cal_component_free_datetime (&dt); dt.value = &tt; dt.tzid = tzid; e_cal_component_set_dtstart (comp, &dt); g_free (tzid); e_cal_component_get_dtend (comp, &dt); if (dt.value->zone) { tt = icaltime_from_timet_with_zone ( event->comp_data->instance_end, dt.value->is_date, dt.value->zone); } else { tt = icaltime_from_timet_with_zone ( event->comp_data->instance_end, dt.value->is_date, e_calendar_view_get_timezone (E_CALENDAR_VIEW (week_view))); } tzid = g_strdup (dt.tzid); e_cal_component_free_datetime (&dt); dt.value = &tt; dt.tzid = tzid; e_cal_component_set_dtend (comp, &dt); g_free (tzid); e_cal_component_set_rdate_list (comp, NULL); e_cal_component_set_rrule_list (comp, NULL); e_cal_component_set_exdate_list (comp, NULL); e_cal_component_set_exrule_list (comp, NULL); e_cal_component_commit_sequence (comp); } } else if (e_cal_component_is_instance (comp)) mod = CALOBJ_MOD_THIS; /* FIXME When sending here, what exactly should we send? */ toplevel = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (week_view))); e_calendar_view_modify_and_send ( E_CALENDAR_VIEW (week_view), comp, client, mod, toplevel, FALSE); } } out: g_free (text); g_object_unref (comp); g_signal_emit_by_name (week_view, "selection_changed"); g_object_notify (G_OBJECT (week_view), "is-editing"); } gboolean e_week_view_find_event_from_item (EWeekView *week_view, GnomeCanvasItem *item, gint *event_num_return, gint *span_num_return) { EWeekViewEvent *event; EWeekViewEventSpan *span; gint event_num, span_num, num_events; num_events = week_view->events->len; for (event_num = 0; event_num < num_events; event_num++) { event = &g_array_index (week_view->events, EWeekViewEvent, event_num); for (span_num = 0; span_num < event->num_spans; span_num++) { if (!is_array_index_in_bounds (week_view->spans, event->spans_index + span_num)) continue; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index + span_num); if (span->text_item == item) { *event_num_return = event_num; *span_num_return = span_num; return TRUE; } } } return FALSE; } /* Finds the index of the event with the given uid. * Returns TRUE if an event with the uid was found. * Note that for recurring events there may be several EWeekViewEvents, one * for each instance, all with the same iCalObject and uid. So only use this * function if you know the event doesn't recur or you are just checking to * see if any events with the uid exist. */ static gboolean e_week_view_find_event_from_uid (EWeekView *week_view, ECalClient *client, const gchar *uid, const gchar *rid, gint *event_num_return) { EWeekViewEvent *event; gint event_num, num_events; *event_num_return = -1; if (!uid) return FALSE; num_events = week_view->events->len; for (event_num = 0; event_num < num_events; event_num++) { const gchar *u; gchar *r = NULL; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (!is_comp_data_valid (event)) continue; if (event->comp_data->client != client) continue; u = icalcomponent_get_uid (event->comp_data->icalcomp); if (u && !strcmp (uid, u)) { if (rid && *rid) { r = icaltime_as_ical_string_r (icalcomponent_get_recurrenceid (event->comp_data->icalcomp)); if (!r || !*r) continue; if (strcmp (rid, r) != 0) { g_free (r); continue; } g_free (r); } *event_num_return = event_num; return TRUE; } } return FALSE; } gboolean e_week_view_is_one_day_event (EWeekView *week_view, gint event_num) { EWeekViewEvent *event; EWeekViewEventSpan *span; if (!is_array_index_in_bounds (week_view->events, event_num)) return FALSE; event = &g_array_index (week_view->events, EWeekViewEvent, event_num); if (event->num_spans != 1) return FALSE; if (!is_array_index_in_bounds (week_view->spans, event->spans_index)) return FALSE; span = &g_array_index (week_view->spans, EWeekViewEventSpan, event->spans_index); if (event->start == week_view->day_starts[span->start_day] && event->end == week_view->day_starts[span->start_day + 1]) return FALSE; if (span->num_days == 1 && event->start >= week_view->day_starts[span->start_day] && event->end <= week_view->day_starts[span->start_day + 1]) return TRUE; return FALSE; } static void e_week_view_cursor_key_up (EWeekView *week_view) { EWeekViewClass *week_view_class; week_view_class = E_WEEK_VIEW_GET_CLASS (week_view); g_return_if_fail (week_view_class->cursor_key_up != NULL); week_view_class->cursor_key_up (week_view); } static void e_week_view_cursor_key_down (EWeekView *week_view) { EWeekViewClass *week_view_class; week_view_class = E_WEEK_VIEW_GET_CLASS (week_view); g_return_if_fail (week_view_class->cursor_key_down != NULL); week_view_class->cursor_key_down (week_view); } static void e_week_view_cursor_key_left (EWeekView *week_view) { EWeekViewClass *week_view_class; week_view_class = E_WEEK_VIEW_GET_CLASS (week_view); g_return_if_fail (week_view_class->cursor_key_left != NULL); week_view_class->cursor_key_left (week_view); } static void e_week_view_cursor_key_right (EWeekView *week_view) { EWeekViewClass *week_view_class; week_view_class = E_WEEK_VIEW_GET_CLASS (week_view); g_return_if_fail (week_view_class->cursor_key_right != NULL); week_view_class->cursor_key_right (week_view); } static gboolean e_week_view_do_key_press (GtkWidget *widget, GdkEventKey *event) { EWeekView *week_view; gchar *initial_text; guint keyval; gboolean stop_emission; gboolean ret_val; g_return_val_if_fail (widget != NULL, FALSE); g_return_val_if_fail (E_IS_WEEK_VIEW (widget), FALSE); g_return_val_if_fail (event != NULL, FALSE); week_view = E_WEEK_VIEW (widget); keyval = event->keyval; /* The Escape key aborts a resize operation. */ #if 0 if (week_view->resize_drag_pos != E_CALENDAR_VIEW_POS_NONE) { if (event->keyval == GDK_KEY_Escape) { e_week_view_abort_resize (week_view, event->time); } return FALSE; } #endif /* Handle the cursor keys for moving the selection */ stop_emission = FALSE; if (!(event->state & GDK_SHIFT_MASK) && !(event->state & GDK_MOD1_MASK)) { stop_emission = TRUE; switch (keyval) { case GDK_KEY_Page_Up: if (!e_week_view_get_multi_week_view (week_view)) e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_UP); else e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_PAGE_UP); break; case GDK_KEY_Page_Down: if (!e_week_view_get_multi_week_view (week_view)) e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_DOWN); else e_week_view_scroll_a_step (week_view, E_CAL_VIEW_MOVE_PAGE_DOWN); break; case GDK_KEY_Up: e_week_view_cursor_key_up (week_view); break; case GDK_KEY_Down: e_week_view_cursor_key_down (week_view); break; case GDK_KEY_Left: e_week_view_cursor_key_left (week_view); break; case GDK_KEY_Right: e_week_view_cursor_key_right (week_view); break; default: stop_emission = FALSE; break; } } if (stop_emission) return TRUE; /*Navigation through days with arrow keys*/ if (((event->state & GDK_SHIFT_MASK) != GDK_SHIFT_MASK) &&((event->state & GDK_CONTROL_MASK) != GDK_CONTROL_MASK) &&((event->state & GDK_MOD1_MASK) == GDK_MOD1_MASK)) { if (keyval == GDK_KEY_Up || keyval == GDK_KEY_KP_Up) return e_week_view_event_move ((ECalendarView *) week_view, E_CAL_VIEW_MOVE_UP); else if (keyval == GDK_KEY_Down || keyval == GDK_KEY_KP_Down) return e_week_view_event_move ((ECalendarView *) week_view, E_CAL_VIEW_MOVE_DOWN); else if (keyval == GDK_KEY_Left || keyval == GDK_KEY_KP_Left) return e_week_view_event_move ((ECalendarView *) week_view, E_CAL_VIEW_MOVE_LEFT); else if (keyval == GDK_KEY_Right || keyval == GDK_KEY_KP_Right) return e_week_view_event_move ((ECalendarView *) week_view, E_CAL_VIEW_MOVE_RIGHT); } if (week_view->selection_start_day == -1) return FALSE; /* We only want to start an edit with a return key or a simple * character. */ if (event->keyval == GDK_KEY_Return) { initial_text = NULL; } else if (((event->keyval >= 0x20) && (event->keyval <= 0xFF) && (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))) || (event->length == 0) || (event->keyval == GDK_KEY_Tab)) { return FALSE; } else initial_text = e_utf8_from_gtk_event_key (widget, event->keyval, event->string); ret_val = e_week_view_add_new_event_in_selected_range (week_view, initial_text); if (initial_text) g_free (initial_text); return ret_val; } static gint e_week_view_get_adjust_days_for_move_up (EWeekView *week_view, gint current_day) { return e_week_view_get_multi_week_view (week_view) ? -7 : 0; } static gint e_week_view_get_adjust_days_for_move_down (EWeekView *week_view, gint current_day) { return e_week_view_get_multi_week_view (week_view) ? 7 : 0; } static gint e_week_view_get_adjust_days_for_move_left (EWeekView *week_view, gint current_day) { return -1; } static gint e_week_view_get_adjust_days_for_move_right (EWeekView *week_view, gint current_day) { return 1; } void e_week_view_show_popup_menu (EWeekView *week_view, GdkEvent *button_event, gint event_num) { week_view->popup_event_num = event_num; e_calendar_view_popup_event (E_CALENDAR_VIEW (week_view), button_event); } void e_week_view_jump_to_button_item (EWeekView *week_view, GnomeCanvasItem *item) { gint day; GnomeCalendar *calendar; for (day = 0; day < E_WEEK_VIEW_MAX_WEEKS * 7; ++day) { if (item == week_view->jump_buttons[day]) { calendar = e_calendar_view_get_calendar (E_CALENDAR_VIEW (week_view)); if (calendar) gnome_calendar_dayjump (calendar, week_view->day_starts[day]); else g_warning ("Calendar not set"); return; } } } static gboolean e_week_view_on_jump_button_event (GnomeCanvasItem *item, GdkEvent *event, EWeekView *week_view) { gint day; if (event->type == GDK_BUTTON_PRESS) { e_week_view_jump_to_button_item (week_view, item); return TRUE; } else if (event->type == GDK_KEY_PRESS) { /* return, if Tab, Control or Alt is pressed */ if ((event->key.keyval == GDK_KEY_Tab) || (event->key.state & (GDK_CONTROL_MASK | GDK_MOD1_MASK))) return FALSE; /* with a return key or a simple character (from 0x20 to 0xff), * jump to the day */ if ((event->key.keyval == GDK_KEY_Return) || ((event->key.keyval >= 0x20) && (event->key.keyval <= 0xFF))) { e_week_view_jump_to_button_item (week_view, item); return TRUE; } } else if (event->type == GDK_FOCUS_CHANGE) { GdkEventFocus *focus_event = (GdkEventFocus *) event; GdkPixbuf *pixbuf = NULL; for (day = 0; day < E_WEEK_VIEW_MAX_WEEKS * 7; day++) { if (item == week_view->jump_buttons[day]) break; } g_return_val_if_fail (day < E_WEEK_VIEW_MAX_WEEKS * 7, FALSE); if (focus_event->in) { week_view->focused_jump_button = day; pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) jump_xpm_focused); gnome_canvas_item_set ( week_view->jump_buttons[day], "GnomeCanvasPixbuf::pixbuf", pixbuf, NULL); } else { week_view->focused_jump_button = E_WEEK_VIEW_JUMP_BUTTON_NO_FOCUS; pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) jump_xpm); gnome_canvas_item_set ( week_view->jump_buttons[day], "GnomeCanvasPixbuf::pixbuf", pixbuf, NULL); } if (pixbuf) g_object_unref (pixbuf); } return FALSE; } /* Converts an hour from 0-23 to the preferred time format, and returns the * suffix to add and the width of it in the normal font. */ void e_week_view_convert_time_to_display (EWeekView *week_view, gint hour, gint *display_hour, const gchar **suffix, gint *suffix_width) { ECalModel *model; model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)); /* Calculate the actual hour number to display. For 12-hour * format we convert 0-23 to 12-11am/12-11pm. */ *display_hour = hour; if (e_cal_model_get_use_24_hour_format (model)) { *suffix = ""; *suffix_width = 0; } else { if (hour < 12) { *suffix = week_view->am_string; *suffix_width = week_view->am_string_width; } else { *display_hour -= 12; *suffix = week_view->pm_string; *suffix_width = week_view->pm_string_width; } /* 12-hour uses 12:00 rather than 0:00. */ if (*display_hour == 0) *display_hour = 12; } } gint e_week_view_get_time_string_width (EWeekView *week_view) { ECalModel *model; gint time_width; model = e_calendar_view_get_model (E_CALENDAR_VIEW (week_view)); if (week_view->use_small_font && week_view->small_font_desc) time_width = week_view->digit_width * 2 + week_view->small_digit_width * 2; else time_width = week_view->digit_width * 4 + week_view->colon_width; if (!e_cal_model_get_use_24_hour_format (model)) time_width += MAX (week_view->am_string_width, week_view->pm_string_width); return time_width; } /* Queues a layout, unless one is already queued. */ static void e_week_view_queue_layout (EWeekView *week_view) { if (week_view->layout_timeout_id == 0) { week_view->layout_timeout_id = g_timeout_add (E_WEEK_VIEW_LAYOUT_TIMEOUT, e_week_view_layout_timeout_cb, week_view); } } /* Removes any queued layout. */ static void e_week_view_cancel_layout (EWeekView *week_view) { if (week_view->layout_timeout_id != 0) { g_source_remove (week_view->layout_timeout_id); week_view->layout_timeout_id = 0; } } static gboolean e_week_view_layout_timeout_cb (gpointer data) { EWeekView *week_view = E_WEEK_VIEW (data); gtk_widget_queue_draw (week_view->main_canvas); e_week_view_check_layout (week_view); week_view->layout_timeout_id = 0; return FALSE; } /* Returns the number of selected events (0 or 1 at present). */ gint e_week_view_get_num_events_selected (EWeekView *week_view) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), 0); return (week_view->editing_event_num != -1) ? 1 : 0; } gboolean e_week_view_is_jump_button_visible (EWeekView *week_view, gint day) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE); if ((day >= 0) && (day < E_WEEK_VIEW_MAX_WEEKS * 7)) return week_view->jump_buttons[day]->flags & GNOME_CANVAS_ITEM_VISIBLE; return FALSE; } gboolean e_week_view_is_editing (EWeekView *week_view) { g_return_val_if_fail (E_IS_WEEK_VIEW (week_view), FALSE); return week_view->editing_event_num != -1; }