diff options
-rw-r--r-- | plugins/itip-formatter/ChangeLog | 50 | ||||
-rw-r--r-- | plugins/itip-formatter/itip-formatter.c | 382 | ||||
-rw-r--r-- | plugins/itip-formatter/itip-view.c | 401 | ||||
-rw-r--r-- | plugins/itip-formatter/itip-view.h | 30 |
4 files changed, 749 insertions, 114 deletions
diff --git a/plugins/itip-formatter/ChangeLog b/plugins/itip-formatter/ChangeLog index 82154dfc23..d4b47dc96a 100644 --- a/plugins/itip-formatter/ChangeLog +++ b/plugins/itip-formatter/ChangeLog @@ -1,3 +1,53 @@ +2005-01-03 JP Rosevear <jpr@novell.com> + + * itip-view.h: new protos, signal + + * itip-view.c (set_info_items): be more generic so both upper and + lower setting can use it + (set_upper_info_items): set the upper info items + (set_lower_info_items): ditto for lower items + (itip_view_destroy): clear both sets of info items + (itip_view_class_init): add source selected signalo + (itip_view_init): add separate upper and lower info item areas and + a detail area + (itip_view_add_upper_info_item): add upper info item + (itip_view_remove_upper_info_item): remove a singal upper area + info item + (itip_view_clear_upper_info_items): clear them all + (itip_view_add_lower_info_item): as above + (itip_view_remove_lower_info_item): ditto + (itip_view_clear_lower_info_items): ditto + (source_selected_cb): emit the source selected signal when the + source in the option menu changes + (itip_view_set_source_list): take a source list and create an + e-source-option-menu if its non-null + (itip_view_get_source_list): get source list + (itip_view_set_source): set a specific source in the source option + menu + (itip_view_get_source): obtain that source + (itip_view_set_rsvp): get the rsvp status + (itip_view_get_rsvp): set it + (itip_view_set_show_rsvp): set visibility of rsvp check box + (itip_view_get_show_rsvp): get the visibility of rsvp check box + (itip_view_set_buttons_sensitive): set button sensitivity + (itip_view_get_buttons_sensitive): get button sensitivity + + * itip-formatter.c (find_my_address): find the user's address in + the list of attendees + (set_buttons_sensitive): set the action buttons sensitivity + appropriately + (cal_opened_cb): use above + (start_calendar_server): ditto + (start_calendar_server_by_uid): de-sensitize buttons to start + (source_selected_cb): ditto + (find_cal_opened_cb): check for conflicting appointments; set + informative info area items + (find_server): create the sexp for determining conflicts + (update_item): oset informative info area items + (view_response_cb): implement some of the responses, start on + implementing rsvp + (format_itip_object): load the source lists properly + 2004-12-29 JP Rosevear <jpr@novell.com> * itip-view.h: new protos diff --git a/plugins/itip-formatter/itip-formatter.c b/plugins/itip-formatter/itip-formatter.c index 7c8a7ed9ab..871c8cb3bf 100644 --- a/plugins/itip-formatter/itip-formatter.c +++ b/plugins/itip-formatter/itip-formatter.c @@ -43,6 +43,7 @@ #include <mail/em-format-html.h> #include <e-util/e-account-list.h> #include <e-util/e-icon-factory.h> +#include <calendar/gui/calendar-config.h> #include <calendar/gui/itip-utils.h> #include <calendar/common/authentication.h> #include "itip-view.h" @@ -52,12 +53,6 @@ void format_itip (EPlugin *ep, EMFormatHookTarget *target); GtkWidget *itip_formatter_page_factory (EPlugin *ep, EConfigHookItemFactoryData *hook_data); -/* FIXME We should include these properly */ -icaltimezone *calendar_config_get_icaltimezone (void); -void calendar_config_init (void); -char *calendar_config_get_primary_calendar (void); -char *calendar_config_get_primary_tasks (void); - typedef struct { EMFormatHTMLPObject pobject; @@ -69,8 +64,6 @@ typedef struct { ECal *current_ecal; ECalSourceType type; - gboolean rsvp; - char *vcalendar; ECalComponent *comp; icalcomponent *main_comp; @@ -78,7 +71,9 @@ typedef struct { icalcomponent *top_level; icalcompiter iter; icalproperty_method method; - + time_t start_time; + time_t end_time; + int current; int total; @@ -91,19 +86,130 @@ typedef struct { gchar *delegator_name; gchar *my_address; gint view_only; + + guint progress_info_id; } FormatItipPObject; typedef struct { FormatItipPObject *pitip; char *uid; - int count; + + char *sexp; + gboolean show_selector; + + int count; } EItipControlFindData; typedef void (* FormatItipOpenFunc) (ECal *ecal, ECalendarStatus status, gpointer data); static void +find_my_address (FormatItipPObject *pitip, icalcomponent *ical_comp, icalparameter_partstat *status) +{ + icalproperty *prop; + char *my_alt_address = NULL; + + for (prop = icalcomponent_get_first_property (ical_comp, ICAL_ATTENDEE_PROPERTY); + prop != NULL; + prop = icalcomponent_get_next_property (ical_comp, ICAL_ATTENDEE_PROPERTY)) { + icalvalue *value; + icalparameter *param; + const char *attendee, *name; + char *attendee_clean, *name_clean; + EIterator *it; + + value = icalproperty_get_value (prop); + if (value != NULL) { + attendee = icalvalue_get_string (value); + attendee_clean = g_strdup (itip_strip_mailto (attendee)); + attendee_clean = g_strstrip (attendee_clean); + } else { + attendee = NULL; + attendee_clean = NULL; + } + + param = icalproperty_get_first_parameter (prop, ICAL_CN_PARAMETER); + if (param != NULL) { + name = icalparameter_get_cn (param); + name_clean = g_strdup (name); + name_clean = g_strstrip (name_clean); + } else { + name = NULL; + name_clean = NULL; + } + + if (pitip->delegator_address) { + char *delegator_clean; + + delegator_clean = g_strdup (itip_strip_mailto (attendee)); + delegator_clean = g_strstrip (delegator_clean); + + /* If the mailer told us the address to use, use that */ + if (delegator_clean != NULL + && !g_ascii_strcasecmp (attendee_clean, delegator_clean)) { + pitip->my_address = g_strdup (itip_strip_mailto (pitip->delegator_address)); + pitip->my_address = g_strstrip (pitip->my_address); + + if (status) { + param = icalproperty_get_first_parameter (prop, ICAL_PARTSTAT_PARAMETER); + *status = param ? icalparameter_get_partstat (param) : ICAL_PARTSTAT_NEEDSACTION; + } + } + + g_free (delegator_clean); + } else { + it = e_list_get_iterator((EList *)pitip->accounts); + while (e_iterator_is_valid(it)) { + const EAccount *account = e_iterator_get(it); + + /* Check for a matching address */ + if (attendee_clean != NULL + && !g_ascii_strcasecmp (account->id->address, attendee_clean)) { + pitip->my_address = g_strdup (account->id->address); + if (status) { + param = icalproperty_get_first_parameter (prop, ICAL_PARTSTAT_PARAMETER); + *status = param ? icalparameter_get_partstat (param) : ICAL_PARTSTAT_NEEDSACTION; + } + g_free (attendee_clean); + g_free (name_clean); + g_free (my_alt_address); + g_object_unref(it); + return; + } + + /* Check for a matching cname to fall back on */ + if (name_clean != NULL + && !g_ascii_strcasecmp (account->id->name, name_clean)) + my_alt_address = g_strdup (attendee_clean); + + e_iterator_next(it); + } + g_object_unref(it); + } + + g_free (attendee_clean); + g_free (name_clean); + } + + pitip->my_address = my_alt_address; + if (status) + *status = ICAL_PARTSTAT_NEEDSACTION; +} + +static void +set_buttons_sensitive (FormatItipPObject *pitip) +{ + gboolean read_only = TRUE; + + if (pitip->current_ecal) + e_cal_is_read_only (pitip->current_ecal, &read_only, NULL); + + itip_view_set_buttons_sensitive (ITIP_VIEW (pitip->view), pitip->current_ecal != NULL && !read_only); +} + + +static void cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data) { FormatItipPObject *pitip = data; @@ -117,7 +223,11 @@ cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data) g_signal_handlers_disconnect_matched (ecal, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, cal_opened_cb, NULL); if (status != E_CALENDAR_STATUS_OK) { - itip_view_set_progress (ITIP_VIEW (pitip->view), "Failed to load at least one calendar"); + char *warn; + + warn = g_strdup_printf ("Failed to load the calendar '%s'", e_source_peek_name (source)); + itip_view_add_lower_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_WARNING, warn); + g_free (warn); g_hash_table_remove (pitip->ecals[source_type], e_source_peek_uid (source)); @@ -129,7 +239,7 @@ cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data) pitip->current_ecal = ecal; -// set_ok_sens (itip); + set_buttons_sensitive (pitip); } static ECal * @@ -141,8 +251,11 @@ start_calendar_server (FormatItipPObject *pitip, ESource *source, ECalSourceType if (ecal) { pitip->current_ecal = ecal; - itip_view_set_progress (ITIP_VIEW (pitip->view), NULL); -// set_ok_sens (itip); + itip_view_remove_lower_info_item (ITIP_VIEW (pitip->view), pitip->progress_info_id); + pitip->progress_info_id = 0; + + set_buttons_sensitive (pitip); + return ecal; } @@ -161,6 +274,8 @@ start_calendar_server_by_uid (FormatItipPObject *pitip, const char *uid, ECalSou { int i; + itip_view_set_buttons_sensitive (ITIP_VIEW (pitip->view), FALSE); + for (i = 0; i < E_CAL_SOURCE_TYPE_LAST; i++) { ESource *source; @@ -173,13 +288,11 @@ start_calendar_server_by_uid (FormatItipPObject *pitip, const char *uid, ECalSou } static void -source_selected_cb (ESourceOptionMenu *esom, ESource *source, gpointer data) +source_selected_cb (ItipView *view, ESource *source, gpointer data) { FormatItipPObject *pitip = data; - g_message ("Source selected"); - - /* FIXME turn off buttons while we check the calendar for being open? */ + itip_view_set_buttons_sensitive (ITIP_VIEW (pitip->view), FALSE); start_calendar_server (pitip, source, pitip->type, cal_opened_cb, pitip); } @@ -193,7 +306,8 @@ find_cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data) ECalSourceType source_type; icalcomponent *icalcomp; icaltimezone *zone; - + GList *objects = NULL; + source_type = e_cal_get_source_type (ecal); source = e_cal_get_source (ecal); @@ -202,18 +316,54 @@ find_cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data) g_signal_handlers_disconnect_matched (ecal, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, find_cal_opened_cb, NULL); if (status != E_CALENDAR_STATUS_OK) { + char *warn; + + /* FIXME Do we really want to warn here? If we fail + * to find the item, this won't be cleared but the + * selector might be shown */ + warn = g_strdup_printf ("Failed to load the calendar '%s'", e_source_peek_name (source)); + itip_view_add_lower_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_WARNING, warn); + g_free (warn); + g_hash_table_remove (pitip->ecals[source_type], e_source_peek_uid (source)); goto cleanup; } + /* Check for conflicts */ + /* FIXME Limit the calendars checked for conflicts? */ + /* FIXME What happens for recurring conflicts? */ + if (e_cal_get_object_list (ecal, fd->sexp, &objects, NULL) && g_list_length (objects) > 0) { + char *info; + + g_message ("Conflicting item found"); + info = g_strdup_printf ("An appointment in the calendar '%s' conflicts with this meeting", e_source_peek_name (source)); + itip_view_add_upper_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_WARNING, info); + g_free (info); + + e_cal_free_object_list (objects); + } else { + g_message ("Query failed"); + } + if (e_cal_get_object (ecal, fd->uid, NULL, &icalcomp, NULL)) { + char *info; + icalcomponent_free (icalcomp); pitip->current_ecal = ecal; - itip_view_set_progress (ITIP_VIEW (pitip->view), NULL); -// set_ok_sens (fd->itip); + /* We clear everything because we don't really care + * about any other info/warnings now we found an + * existing versions */ + itip_view_clear_lower_info_items (ITIP_VIEW (pitip->view)); + pitip->progress_info_id = 0; + + info = g_strdup_printf ("Found the appointment in the calendar '%s'", e_source_peek_name (source)); + itip_view_add_lower_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_INFO, info); + g_free (info); + + set_buttons_sensitive (pitip); } zone = calendar_config_get_icaltimezone (); @@ -221,12 +371,11 @@ find_cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data) cleanup: if (fd->count == 0) { - /* FIXME the box check is to see if the buttons are displayed i think */ - if (fd->show_selector && !pitip->current_ecal /*&& pitip->vbox*/) { - GtkWidget *esom; + if (fd->show_selector && !pitip->current_ecal) { ESource *source = NULL; char *uid; + /* FIXME Should we take into account any sources we failed to open? */ switch (pitip->type) { case E_CAL_SOURCE_TYPE_EVENT: uid = calendar_config_get_primary_calendar (); @@ -248,18 +397,23 @@ find_cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data) if (!source) source = e_source_list_peek_source_any (pitip->source_lists[pitip->type]); - g_message ("Picking any source"); - esom = e_source_option_menu_new (pitip->source_lists[pitip->type]); - /* FIXME used to force the data to be kept alive, still do this? */ - g_signal_connect (esom, "source_selected", G_CALLBACK (source_selected_cb), fd->pitip); + itip_view_set_source_list (ITIP_VIEW (pitip->view), pitip->source_lists[pitip->type]); + g_signal_connect (pitip->view, "source_selected", G_CALLBACK (source_selected_cb), pitip); - //gtk_box_pack_start (GTK_BOX (pitip->vbox), esom, FALSE, TRUE, 0); - gtk_widget_show (esom); + /* The only method that RSVP makes sense for is REQUEST */ + /* FIXME Default to the suggestion for RSVP for my attendee */ + itip_view_set_show_rsvp (ITIP_VIEW (pitip->view), pitip->method == ICAL_METHOD_REQUEST ? TRUE : FALSE ); + + itip_view_remove_lower_info_item (ITIP_VIEW (pitip->view), pitip->progress_info_id); + pitip->progress_info_id = 0; - /* FIXME What if there is no source? */ if (source) { - e_source_option_menu_select (E_SOURCE_OPTION_MENU (esom), source); - itip_view_set_progress (ITIP_VIEW (pitip->view), NULL); + itip_view_set_source (ITIP_VIEW (pitip->view), source); + + /* FIXME Shouldn't the buttons be sensitized here? */ + } else { + itip_view_add_lower_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_ERROR, "Unable to find any calendars"); + itip_view_set_buttons_sensitive (ITIP_VIEW (pitip->view), FALSE); } } else { /* FIXME Display error message to user */ @@ -279,7 +433,10 @@ find_server (FormatItipPObject *pitip, ECalComponent *comp, gboolean show_select e_cal_component_get_uid (comp, &uid); - itip_view_set_progress (ITIP_VIEW (pitip->view), "Searching for an existing version of this appointment"); + pitip->progress_info_id = itip_view_add_lower_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_PROGRESS, + "Searching for an existing version of this appointment"); + + itip_view_set_buttons_sensitive (ITIP_VIEW (pitip->view), FALSE); groups = e_source_list_peek_groups (pitip->source_lists[pitip->type]); for (l = groups; l; l = l->next) { @@ -296,10 +453,23 @@ find_server (FormatItipPObject *pitip, ECalComponent *comp, gboolean show_select source = m->data; if (!fd) { + char *start, *end; + fd = g_new0 (EItipControlFindData, 1); fd->pitip = pitip; fd->uid = g_strdup (uid); fd->show_selector = show_selector; + + if (pitip->start_time && pitip->end_time) { + start = isodate_from_time_t (pitip->start_time); + end = isodate_from_time_t (pitip->end_time); + + fd->sexp = g_strdup_printf ("(occur-in-time-range? (make-time \"%s\") (make-time \"%s\"))", start, end); + g_message ("Sexp is %s", fd->sexp); + } + + g_free (start); + g_free (end); } fd->count++; @@ -309,8 +479,10 @@ find_server (FormatItipPObject *pitip, ECalComponent *comp, gboolean show_select } static void -cleanup_ecal (ECal *ecal) +cleanup_ecal (gpointer data) { + ECal *ecal = data; + /* Clean up any signals */ g_signal_handlers_disconnect_matched (ecal, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, cal_opened_cb, NULL); g_signal_handlers_disconnect_matched (ecal, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, find_cal_opened_cb, NULL); @@ -398,12 +570,11 @@ change_status (icalcomponent *ical_comp, const char *address, icalparameter_part } static void -update_item (FormatItipPObject *pitip) +update_item (FormatItipPObject *pitip, ItipViewResponse response) { struct icaltimetype stamp; icalproperty *prop; icalcomponent *clone; -// GtkWidget *dialog; GError *error = NULL; /* Set X-MICROSOFT-CDO-REPLYTIME to record the time at which @@ -425,14 +596,40 @@ update_item (FormatItipPObject *pitip) icalcomponent_set_method (pitip->top_level, pitip->method); if (!e_cal_receive_objects (pitip->current_ecal, pitip->top_level, &error)) { - /* FIXME e-error */ -// dialog = gnome_warning_dialog (error->message); + /* FIXME Really use e_error? or should it just be an info item */ +// e_error_run (NULL, "org.gnome.itip-formatter:meeting-update-failed", error->message, NULL); g_error_free (error); } else { - /* FIXME I think we should do nothing */ -// dialog = gnome_ok_dialog (_("Update complete\n")); + ESource *source; + char *info; + + /* FIXME This makes the UI look ugly */ + itip_view_set_source_list (ITIP_VIEW (pitip->view), NULL); + + itip_view_clear_lower_info_items (ITIP_VIEW (pitip->view)); + source = e_cal_get_source (pitip->current_ecal); + + switch (response) { + case ITIP_VIEW_RESPONSE_ACCEPT: + info = g_strdup_printf ("Sent to calendar '%s' as accepted", e_source_peek_name (source)); + break; + case ITIP_VIEW_RESPONSE_TENTATIVE: + info = g_strdup_printf ("Sent to calendar '%s' as tentative", e_source_peek_name (source)); + break; + case ITIP_VIEW_RESPONSE_DECLINE: + /* FIXME some calendars just might not save it at all, is this accurate? */ + info = g_strdup_printf ("Sent to calendar '%s' as declined", e_source_peek_name (source)); + break; + default: + g_assert_not_reached (); + break; + } + + itip_view_add_lower_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_INFO, info); + g_free (info); + + /* FIXME Should we hide or desensitize the buttons now? */ } -// gnome_dialog_run_and_close (GNOME_DIALOG (dialog)); icalcomponent_remove_component (pitip->top_level, clone); } @@ -467,18 +664,12 @@ extract_itip_data (FormatItipPObject *pitip) icalcomponent *tz_comp; icalcompiter tz_iter; -/* FIXME try and match sender with organizer/attendee/sentby? - pd->from_address = camel_address_encode ((CamelAddress *)from); - g_message ("Detected from address %s", pd->from_address); -*/ - content = camel_medium_get_content_object ((CamelMedium *) pitip->pobject.part); mem = camel_stream_mem_new (); camel_data_wrapper_write_to_stream (content, mem); pitip->vcalendar = g_strndup (((CamelStreamMem *) mem)->buffer->data, ((CamelStreamMem *) mem)->buffer->len); - /* FIXME unref the content object as well? */ camel_object_unref (mem); pitip->top_level = e_cal_util_new_top_level (); @@ -553,7 +744,7 @@ view_response_cb (GtkWidget *widget, ItipViewResponse response, gpointer data) ICAL_PARTSTAT_ACCEPTED); if (status) { e_cal_component_rescan (pitip->comp); - update_item (pitip); + update_item (pitip, response); } break; case ITIP_VIEW_RESPONSE_TENTATIVE: @@ -561,7 +752,7 @@ view_response_cb (GtkWidget *widget, ItipViewResponse response, gpointer data) ICAL_PARTSTAT_TENTATIVE); if (status) { e_cal_component_rescan (pitip->comp); - update_item (pitip); + update_item (pitip, response); } break; case ITIP_VIEW_RESPONSE_DECLINE: @@ -569,12 +760,69 @@ view_response_cb (GtkWidget *widget, ItipViewResponse response, gpointer data) ICAL_PARTSTAT_DECLINED); if (status) { e_cal_component_rescan (pitip->comp); - update_item (pitip); + update_item (pitip, response); } break; default: break; } + + if (e_cal_get_save_schedules (pitip->current_ecal)) + return; + + if (itip_view_get_rsvp (ITIP_VIEW (pitip->view)) && status) { + ECalComponent *comp = NULL; + ECalComponentVType vtype; + icalcomponent *ical_comp; + icalproperty *prop; + icalvalue *value; + const char *attendee; + GSList *l, *list = NULL; + + comp = e_cal_component_clone (pitip->comp); + if (comp == NULL) + return; + + vtype = e_cal_component_get_vtype (comp); + + if (pitip->my_address == NULL) + find_my_address (pitip, pitip->ical_comp, NULL); + g_assert (pitip->my_address != NULL); + + ical_comp = e_cal_component_get_icalcomponent (comp); + + for (prop = icalcomponent_get_first_property (ical_comp, ICAL_ATTENDEE_PROPERTY); + prop != NULL; + prop = icalcomponent_get_next_property (ical_comp, ICAL_ATTENDEE_PROPERTY)) + { + char *text; + + value = icalproperty_get_value (prop); + if (!value) + continue; + + attendee = icalvalue_get_string (value); + + text = g_strdup (itip_strip_mailto (attendee)); + text = g_strstrip (text); + if (g_strcasecmp (pitip->my_address, text)) + list = g_slist_prepend (list, prop); + g_free (text); + } + + for (l = list; l; l = l->next) { + prop = l->data; + icalcomponent_remove_property (ical_comp, prop); + icalproperty_free (prop); + } + g_slist_free (list); + + e_cal_component_rescan (comp); + itip_send_comp (E_CAL_COMPONENT_METHOD_REPLY, comp, pitip->current_ecal, pitip->top_level); + + g_object_unref (comp); + + } } static gboolean @@ -592,7 +840,7 @@ format_itip_object (EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject /* Source Lists and open ecal clients */ for (i = 0; i < E_CAL_SOURCE_TYPE_LAST; i++) { - if (!e_cal_get_sources (&pitip->source_lists[E_CAL_SOURCE_TYPE_EVENT], E_CAL_SOURCE_TYPE_EVENT, NULL)) + if (!e_cal_get_sources (&pitip->source_lists[i], i, NULL)) /* FIXME More error handling? */ pitip->source_lists[i] = NULL; @@ -642,6 +890,11 @@ format_itip_object (EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject /* FIXME, do i need to strip the sentby somehow? Maybe with camel? */ itip_view_set_sentby (ITIP_VIEW (pitip->view), organizer.sentby); +/* FIXME try and match sender with organizer/attendee/sentby? + pd->from_address = camel_address_encode ((CamelAddress *)from); + g_message ("Detected from address %s", pd->from_address); +*/ + e_cal_component_get_summary (pitip->comp, &text); itip_view_set_summary (ITIP_VIEW (pitip->view), text.value ? text.value : _("None")); @@ -668,10 +921,12 @@ format_itip_object (EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject to_zone = calendar_config_get_icaltimezone (); e_cal_component_get_dtstart (pitip->comp, &datetime); + pitip->start_time = 0; if (datetime.value) { struct tm start_tm; - /* FIXME Handle tzid that is not in the component - treat as "local" time */ + /* If the timezone is not in the component, guess the local time */ + /* Should we guess if the timezone is an olsen name somehow? */ if (!datetime.value->is_utc && datetime.tzid) from_zone = icalcomponent_get_timezone (pitip->top_level, datetime.tzid); else @@ -680,28 +935,34 @@ format_itip_object (EMFormatHTML *efh, GtkHTMLEmbedded *eb, EMFormatHTMLPObject start_tm = icaltimetype_to_tm_with_zone (datetime.value, from_zone, to_zone); itip_view_set_start (ITIP_VIEW (pitip->view), &start_tm); + pitip->start_time = icaltime_as_timet_with_zone (*datetime.value, from_zone); } e_cal_component_free_datetime (&datetime); e_cal_component_get_dtend (pitip->comp, &datetime); + pitip->end_time = 0; if (datetime.value) { struct tm end_tm; - - /* FIXME Handle tzid that is not in the component - treat as "local" time */ + + /* If the timezone is not in the component, guess the local time */ + /* Should we guess if the timezone is an olsen name somehow? */ if (!datetime.value->is_utc && datetime.tzid) from_zone = icalcomponent_get_timezone (pitip->top_level, datetime.tzid); else from_zone = NULL; end_tm = icaltimetype_to_tm_with_zone (datetime.value, from_zone, to_zone); - + itip_view_set_end (ITIP_VIEW (pitip->view), &end_tm); + pitip->end_time = icaltime_as_timet_with_zone (*datetime.value, from_zone); } e_cal_component_free_datetime (&datetime); - /* Info area items */ - itip_view_add_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_INFO, "This meeting occurs weekly indefinitely"); - itip_view_add_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_WARNING, "An appointment in the calendar conflicts with this meeting"); + /* Recurrence info */ + /* FIXME Better recurring description */ + if (e_cal_component_has_recurrences (pitip->comp)) { + itip_view_add_upper_info_item (ITIP_VIEW (pitip->view), ITIP_VIEW_INFO_ITEM_TYPE_INFO, "This meeting recurs"); + } gtk_container_add (GTK_CONTAINER (eb), pitip->view); gtk_widget_set_usize (pitip->view, 640, -1); @@ -719,11 +980,10 @@ format_itip (EPlugin *ep, EMFormatHookTarget *target) { FormatItipPObject *pitip; - calendar_config_init (); - pitip = (FormatItipPObject *) em_format_html_add_pobject ((EMFormatHTML *) target->format, sizeof (FormatItipPObject), CLASSID, target->part, format_itip_object); // FIXME set the free function // pitip->object.free = pitip_free; + camel_stream_printf (target->stream, "<table border=0 width=\"100%%\" cellpadding=3><tr>"); camel_stream_printf (target->stream, "<td valign=top><object classid=\"%s\"></object></td><td width=100%% valign=top>", CLASSID); camel_stream_printf (target->stream, "</td></tr></table>"); diff --git a/plugins/itip-formatter/itip-view.c b/plugins/itip-formatter/itip-view.c index a1a1e8c12a..4b9f161e30 100644 --- a/plugins/itip-formatter/itip-view.c +++ b/plugins/itip-formatter/itip-view.c @@ -33,6 +33,8 @@ #include <camel/camel-stream-mem.h> #include <camel/camel-medium.h> #include <camel/camel-mime-message.h> +#include <libedataserver/e-time-utils.h> +#include <libedataserverui/e-source-option-menu.h> #include <libecal/e-cal.h> #include <gtkhtml/gtkhtml-embedded.h> #include <mail/em-format-hook.h> @@ -50,8 +52,9 @@ G_DEFINE_TYPE (ItipView, itip_view, GTK_TYPE_HBOX); typedef struct { ItipViewInfoItemType type; - char *message; + + guint id; } ItipViewInfoItem; struct _ItipViewPrivate { @@ -78,21 +81,33 @@ struct _ItipViewPrivate { GtkWidget *end_label; struct tm *end_tm; - GtkWidget *info_box; - GSList *info_items; - + GtkWidget *upper_info_box; + GSList *upper_info_items; + + GtkWidget *lower_info_box; + GSList *lower_info_items; + + guint next_info_item_id; + GtkWidget *description_label; char *description; - GtkWidget *progress_box; - GtkWidget *progress_label; - char *progress; + GtkWidget *details_box; + + GtkWidget *esom; + GtkWidget *esom_header; + ESourceList *source_list; + GtkWidget *rsvp_check; + gboolean rsvp_show; + GtkWidget *button_box; + gboolean buttons_sensitive; }; /* Signal IDs */ enum { + SOURCE_SELECTED, RESPONSE, LAST_SIGNAL }; @@ -402,16 +417,13 @@ set_end_text (ItipView *view) } static void -set_info_items (ItipView *view) +set_info_items (GtkWidget *info_box, GSList *info_items) { - ItipViewPrivate *priv; GSList *l; - priv = view->priv; - - gtk_container_foreach (GTK_CONTAINER (priv->info_box), (GtkCallback) gtk_widget_destroy, NULL); + gtk_container_foreach (GTK_CONTAINER (info_box), (GtkCallback) gtk_widget_destroy, NULL); - for (l = priv->info_items; l; l = l->next) { + for (l = info_items; l; l = l->next) { ItipViewInfoItem *item = l->data; GtkWidget *hbox, *image, *label; @@ -427,6 +439,9 @@ set_info_items (ItipView *view) case ITIP_VIEW_INFO_ITEM_TYPE_ERROR: image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_SMALL_TOOLBAR); break; + case ITIP_VIEW_INFO_ITEM_TYPE_PROGRESS: + image = e_icon_factory_get_image ("stock_animation", E_ICON_SIZE_BUTTON); + break; case ITIP_VIEW_INFO_ITEM_TYPE_NONE: default: image = NULL; @@ -442,21 +457,28 @@ set_info_items (ItipView *view) gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6); gtk_widget_show (hbox); - gtk_box_pack_start (GTK_BOX (priv->info_box), hbox, FALSE, FALSE, 6); + gtk_box_pack_start (GTK_BOX (info_box), hbox, FALSE, FALSE, 6); } } static void -set_progress_text (ItipView *view) +set_upper_info_items (ItipView *view) { ItipViewPrivate *priv; - + priv = view->priv; - g_message ("Setting progress to: %s", priv->progress); - gtk_label_set_text (GTK_LABEL (priv->progress_label), priv->progress); + set_info_items (priv->upper_info_box, priv->upper_info_items); +} - priv->progress ? gtk_widget_show (priv->progress_box) : gtk_widget_hide (priv->progress_box); +static void +set_lower_info_items (ItipView *view) +{ + ItipViewPrivate *priv; + + priv = view->priv; + + set_info_items (priv->lower_info_box, priv->lower_info_items); } #define DATA_RESPONSE_KEY "ItipView::button_response" @@ -483,7 +505,7 @@ set_one_button (ItipView *view, char *label, char *stock_id, ItipViewResponse re button = e_gtk_button_new_with_icon (label, stock_id); g_object_set_data (G_OBJECT (button), DATA_RESPONSE_KEY, GINT_TO_POINTER (response)); gtk_widget_show (button); - gtk_container_add (GTK_CONTAINER (priv->button_box), button); + gtk_container_add (GTK_CONTAINER (priv->button_box), button); g_signal_connect (button, "clicked", G_CALLBACK (button_clicked_cb), view); } @@ -549,7 +571,8 @@ itip_view_destroy (GtkObject *object) g_free (priv->start_tm); g_free (priv->end_tm); - itip_view_clear_info_items (view); + itip_view_clear_upper_info_items (view); + itip_view_clear_lower_info_items (view); g_free (priv); view->priv = NULL; @@ -569,6 +592,15 @@ itip_view_class_init (ItipViewClass *klass) gtkobject_class->destroy = itip_view_destroy; + signals[SOURCE_SELECTED] = + g_signal_new ("source_selected", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ItipViewClass, source_selected), + NULL, NULL, + gtk_marshal_NONE__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + signals[RESPONSE] = g_signal_new ("response", G_TYPE_FROM_CLASS (klass), @@ -583,7 +615,7 @@ static void itip_view_init (ItipView *view) { ItipViewPrivate *priv; - GtkWidget *icon, *vbox, *separator, *table, *image; + GtkWidget *icon, *vbox, *separator, *table; priv = g_new0 (ItipViewPrivate, 1); view->priv = priv; @@ -651,10 +683,10 @@ itip_view_init (ItipView *view) gtk_table_attach (GTK_TABLE (table), priv->end_header, 0, 1, 3, 4, GTK_FILL, 0, 0, 0); gtk_table_attach (GTK_TABLE (table), priv->end_label, 1, 2, 3, 4, GTK_FILL, 0, 0, 0); - /* Info items */ - priv->info_box = gtk_vbox_new (FALSE, 0); - gtk_widget_show (priv->info_box); - gtk_box_pack_start (GTK_BOX (vbox), priv->info_box, FALSE, FALSE, 6); + /* Upper Info items */ + priv->upper_info_box = gtk_vbox_new (FALSE, 0); + gtk_widget_show (priv->upper_info_box); + gtk_box_pack_start (GTK_BOX (vbox), priv->upper_info_box, FALSE, FALSE, 6); /* Description */ priv->description_label = gtk_label_new (NULL); @@ -666,25 +698,27 @@ itip_view_init (ItipView *view) gtk_widget_show (separator); gtk_box_pack_start (GTK_BOX (vbox), separator, FALSE, FALSE, 6); - /* Progress */ - priv->progress_box = gtk_hbox_new (FALSE, 0); - gtk_box_pack_start (GTK_BOX (vbox), priv->progress_box, FALSE, FALSE, 6); - - image = e_icon_factory_get_image ("stock_animation", E_ICON_SIZE_BUTTON); - gtk_widget_show (image); - gtk_box_pack_start (GTK_BOX (priv->progress_box), image, FALSE, FALSE, 6); + /* Lower Info items */ + priv->lower_info_box = gtk_vbox_new (FALSE, 0); + gtk_widget_show (priv->lower_info_box); + gtk_box_pack_start (GTK_BOX (vbox), priv->lower_info_box, FALSE, FALSE, 6); - priv->progress_label = gtk_label_new (NULL); - gtk_widget_show (priv->progress_label); - gtk_misc_set_alignment (GTK_MISC (priv->progress_label), 0, 0.5); - gtk_box_pack_start (GTK_BOX (priv->progress_box), priv->progress_label, FALSE, FALSE, 6); + /* Detail area */ + priv->details_box = gtk_hbox_new (FALSE, 0); + gtk_widget_show (priv->details_box); + gtk_box_pack_start (GTK_BOX (vbox), priv->details_box, FALSE, FALSE, 6); + priv->rsvp_check = gtk_check_button_new_with_mnemonic ("Send _reply to sender"); + gtk_box_pack_end (GTK_BOX (priv->details_box), priv->rsvp_check, FALSE, FALSE, 6); + /* The buttons for actions */ priv->button_box = gtk_hbutton_box_new (); gtk_button_box_set_layout (GTK_BUTTON_BOX (priv->button_box), GTK_BUTTONBOX_END); gtk_box_set_spacing (GTK_BOX (priv->button_box), 12); gtk_widget_show (priv->button_box); gtk_box_pack_start (GTK_BOX (vbox), priv->button_box, FALSE, FALSE, 6); + + priv->buttons_sensitive = TRUE; } GtkWidget * @@ -984,29 +1018,107 @@ itip_view_get_end (ItipView *view) return priv->end_tm; } -void -itip_view_add_info_item (ItipView *view, ItipViewInfoItemType type, const char *message) +guint +itip_view_add_upper_info_item (ItipView *view, ItipViewInfoItemType type, const char *message) { ItipViewPrivate *priv; ItipViewInfoItem *item; + g_return_val_if_fail (view != NULL, 0); + g_return_val_if_fail (ITIP_IS_VIEW (view), 0); + + priv = view->priv; + + item = g_new0 (ItipViewInfoItem, 1); + + item->type = type; + item->message = g_strdup (message); + item->id = priv->next_info_item_id++; + + priv->upper_info_items = g_slist_append (priv->upper_info_items, item); + + set_upper_info_items (view); + + return item->id; +} + +void +itip_view_remove_upper_info_item (ItipView *view, guint id) +{ + ItipViewPrivate *priv; + GSList *l; + + g_return_if_fail (view != NULL); + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + for (l = priv->upper_info_items; l; l = l->next) { + ItipViewInfoItem *item = l->data; + + if (item->id == id) { + priv->upper_info_items = g_slist_remove (priv->upper_info_items, item); + + g_free (item->message); + g_free (item); + + set_upper_info_items (view); + + return; + } + } +} + +void +itip_view_clear_upper_info_items (ItipView *view) +{ + ItipViewPrivate *priv; + GSList *l; + g_return_if_fail (view != NULL); g_return_if_fail (ITIP_IS_VIEW (view)); priv = view->priv; + gtk_container_foreach (GTK_CONTAINER (priv->upper_info_box), (GtkCallback) gtk_widget_destroy, NULL); + + for (l = priv->upper_info_items; l; l = l->next) { + ItipViewInfoItem *item = l->data; + + g_free (item->message); + g_free (item); + } + + g_slist_free (priv->upper_info_items); + priv->upper_info_items = NULL; +} + +guint +itip_view_add_lower_info_item (ItipView *view, ItipViewInfoItemType type, const char *message) +{ + ItipViewPrivate *priv; + ItipViewInfoItem *item; + + g_return_val_if_fail (view != NULL, 0); + g_return_val_if_fail (ITIP_IS_VIEW (view), 0); + + priv = view->priv; + item = g_new0 (ItipViewInfoItem, 1); item->type = type; item->message = g_strdup (message); + item->id = priv->next_info_item_id++; - priv->info_items = g_slist_append (priv->info_items, item); + priv->lower_info_items = g_slist_append (priv->lower_info_items, item); - set_info_items (view); + set_lower_info_items (view); + + return item->id; } void -itip_view_clear_info_items (ItipView *view) +itip_view_remove_lower_info_item (ItipView *view, guint id) { ItipViewPrivate *priv; GSList *l; @@ -1016,18 +1128,144 @@ itip_view_clear_info_items (ItipView *view) priv = view->priv; - gtk_container_foreach (GTK_CONTAINER (priv->info_box), (GtkCallback) gtk_widget_destroy, NULL); + for (l = priv->lower_info_items; l; l = l->next) { + ItipViewInfoItem *item = l->data; + + if (item->id == id) { + priv->lower_info_items = g_slist_remove (priv->lower_info_items, item); + + g_free (item->message); + g_free (item); + + set_lower_info_items (view); + + return; + } + } +} - for (l = priv->info_items; l; l = l->next) { +void +itip_view_clear_lower_info_items (ItipView *view) +{ + ItipViewPrivate *priv; + GSList *l; + + g_return_if_fail (view != NULL); + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + gtk_container_foreach (GTK_CONTAINER (priv->lower_info_box), (GtkCallback) gtk_widget_destroy, NULL); + + for (l = priv->lower_info_items; l; l = l->next) { ItipViewInfoItem *item = l->data; g_free (item->message); g_free (item); } + + g_slist_free (priv->lower_info_items); + priv->lower_info_items = NULL; +} + +static void +source_selected_cb (ESourceOptionMenu *esom, ESource *source, gpointer data) +{ + ItipView *view = data; + + g_signal_emit (view, signals[SOURCE_SELECTED], 0, source); +} + +void +itip_view_set_source_list (ItipView *view, ESourceList *source_list) +{ + ItipViewPrivate *priv; + + g_return_if_fail (view != NULL); + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + if (priv->source_list) + g_object_unref (priv->source_list); + + if (priv->esom) + gtk_widget_destroy (priv->esom); + + if (!source_list) { + if (priv->esom_header) + gtk_widget_destroy (priv->esom_header); + + priv->source_list = NULL; + priv->esom = NULL; + priv->esom_header = NULL; + + return; + } + + priv->source_list = g_object_ref (source_list); + + priv->esom = e_source_option_menu_new (source_list); + gtk_widget_show (priv->esom); + g_signal_connect (priv->esom, "source_selected", G_CALLBACK (source_selected_cb), view); + + if (!priv->esom_header) { + priv->esom_header = gtk_label_new_with_mnemonic (_("_Calendar:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (priv->esom_header), priv->esom); + gtk_widget_show (priv->esom_header); + } + + gtk_box_pack_start (GTK_BOX (priv->details_box), priv->esom_header, FALSE, TRUE, 6); + gtk_box_pack_start (GTK_BOX (priv->details_box), priv->esom, FALSE, TRUE, 0); +} + +ESourceList * +itip_view_get_source_list (ItipView *view) +{ + ItipViewPrivate *priv; + + g_return_val_if_fail (view != NULL, FALSE); + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + priv = view->priv; + + return priv->source_list; +} + +void +itip_view_set_source (ItipView *view, ESource *source) +{ + ItipViewPrivate *priv; + + g_return_if_fail (view != NULL); + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + if (!priv->esom) + return; + + e_source_option_menu_select (E_SOURCE_OPTION_MENU (priv->esom), source); +} + +ESource * +itip_view_get_source (ItipView *view) +{ + ItipViewPrivate *priv; + + g_return_val_if_fail (view != NULL, FALSE); + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + priv = view->priv; + + if (!priv->esom) + return NULL; + + return e_source_option_menu_peek_selected (E_SOURCE_OPTION_MENU (priv->esom)); } void -itip_view_set_progress (ItipView *view, const char *message) +itip_view_set_rsvp (ItipView *view, gboolean rsvp) { ItipViewPrivate *priv; @@ -1035,12 +1273,77 @@ itip_view_set_progress (ItipView *view, const char *message) g_return_if_fail (ITIP_IS_VIEW (view)); priv = view->priv; + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->rsvp_check), rsvp); +} - if (priv->progress) - g_free (priv->progress); +gboolean +itip_view_get_rsvp (ItipView *view) +{ + ItipViewPrivate *priv; + + g_return_val_if_fail (view != NULL, FALSE); + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + priv = view->priv; + + return gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->rsvp_check)); +} - priv->progress = message ? g_strstrip (g_strdup (message)) : NULL; +void +itip_view_set_show_rsvp (ItipView *view, gboolean rsvp) +{ + ItipViewPrivate *priv; - set_progress_text (view); + g_return_if_fail (view != NULL); + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + priv->rsvp_show = rsvp; + + priv->rsvp_show ? gtk_widget_show (priv->rsvp_check) : gtk_widget_hide (priv->rsvp_check); } +gboolean +itip_view_get_show_rsvp (ItipView *view) +{ + ItipViewPrivate *priv; + + g_return_val_if_fail (view != NULL, FALSE); + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + priv = view->priv; + + return priv->rsvp_show; +} + +void +itip_view_set_buttons_sensitive (ItipView *view, gboolean sensitive) +{ + ItipViewPrivate *priv; + + g_return_if_fail (view != NULL); + g_return_if_fail (ITIP_IS_VIEW (view)); + + priv = view->priv; + + priv->buttons_sensitive = sensitive; + + gtk_widget_set_sensitive (priv->button_box, priv->buttons_sensitive); +} + +gboolean +itip_view_get_buttons_sensitive (ItipView *view) +{ + ItipViewPrivate *priv; + + g_return_val_if_fail (view != NULL, FALSE); + g_return_val_if_fail (ITIP_IS_VIEW (view), FALSE); + + priv = view->priv; + + return priv->buttons_sensitive; +} + + diff --git a/plugins/itip-formatter/itip-view.h b/plugins/itip-formatter/itip-view.h index 915b3286cb..c42a931b31 100644 --- a/plugins/itip-formatter/itip-view.h +++ b/plugins/itip-formatter/itip-view.h @@ -25,6 +25,7 @@ #include <glib-object.h> #include <gtk/gtkhbox.h> +#include <libedataserver/e-source-list.h> G_BEGIN_DECLS @@ -73,13 +74,16 @@ typedef enum { struct _ItipView { GtkHBox parent_instance; - + ItipViewPrivate *priv; + + GtkWidget *action_vbox; }; struct _ItipViewClass { GtkHBoxClass parent_class; + void (* source_selected) (ItipView *view, ESource *selected_source); void (* response) (ItipView *view, int response); }; @@ -113,10 +117,28 @@ const struct tm *itip_view_get_start (ItipView *view); void itip_view_set_end (ItipView *view, struct tm *end); const struct tm *itip_view_get_end (ItipView *view); -void itip_view_add_info_item (ItipView *view, ItipViewInfoItemType, const char *message); -void itip_view_clear_info_items (ItipView *view); +guint itip_view_add_upper_info_item (ItipView *view, ItipViewInfoItemType, const char *message); +void itip_view_remove_upper_info_item (ItipView *view, guint id); +void itip_view_clear_upper_info_items (ItipView *view); + +guint itip_view_add_lower_info_item (ItipView *view, ItipViewInfoItemType, const char *message); +void itip_view_remove_lower_info_item (ItipView *view, guint id); +void itip_view_clear_lower_info_items (ItipView *view); + +void itip_view_set_source_list (ItipView *view, ESourceList *source_list); +ESourceList *itip_view_get_source_list (ItipView *view); + +void itip_view_set_source (ItipView *view, ESource *source); +ESource *itip_view_get_source (ItipView *view); + +void itip_view_set_rsvp (ItipView *view, gboolean rsvp); +gboolean itip_view_get_rsvp (ItipView *view); + +void itip_view_set_show_rsvp (ItipView *view, gboolean rsvp); +gboolean itip_view_get_show_rsvp (ItipView *view); -void itip_view_set_progress (ItipView *view, const char *message); +void itip_view_set_buttons_sensitive (ItipView *view, gboolean sensitive); +gboolean itip_view_get_buttons_sensitive (ItipView *view); G_END_DECLS |