/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
/* e-itip-control.c
*
* Copyright (C) 2001 Ximian, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*
* Author: JP Rosevear
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <gtk/gtkmisc.h>
#include <libgnome/gnome-i18n.h>
#include <libgnome/gnome-util.h>
#include <libgnomeui/gnome-stock-icons.h>
#include <libgnomeui/gnome-uidefs.h>
#include <libgnomeui/gnome-dialog.h>
#include <libgnomeui/gnome-dialog-util.h>
#include <bonobo/bonobo-object.h>
#include <bonobo/bonobo-exception.h>
#include <gtkhtml/gtkhtml.h>
#include <gtkhtml/gtkhtml-embedded.h>
#include <gtkhtml/gtkhtml-stream.h>
#include <libedataserver/e-source-list.h>
#include <libedataserverui/e-source-option-menu.h>
#include <libical/ical.h>
#include <libecal/e-cal-component.h>
#include <libecal/e-cal-time-util.h>
#include <libecal/e-cal.h>
#include <libedataserver/e-time-utils.h>
#include <e-util/e-dialog-widgets.h>
#include <e-util/e-html-utils.h>
#include <e-util/e-icon-factory.h>
#include <e-util/e-util-private.h>
#include "dialogs/delete-error.h"
#include "calendar-config.h"
#include "itip-utils.h"
#include "e-itip-control.h"
#include "common/authentication.h"
struct _EItipControlPrivate {
GtkWidget *html;
ESourceList *source_lists[E_CAL_SOURCE_TYPE_LAST];
GHashTable *ecals[E_CAL_SOURCE_TYPE_LAST];
ECal *current_ecal;
ECalSourceType type;
char action;
gboolean rsvp;
GtkWidget *ok;
GtkWidget *hbox;
GtkWidget *vbox;
char *vcalendar;
ECalComponent *comp;
icalcomponent *main_comp;
icalcomponent *ical_comp;
icalcomponent *top_level;
icalcompiter iter;
icalproperty_method method;
int current;
int total;
gchar *calendar_uid;
EAccountList *accounts;
gchar *from_address;
gchar *delegator_address;
gchar *delegator_name;
gchar *my_address;
gint view_only;
};
#define ACTION_DATA "EItipControl:Action"
/* HTML Strings */
#define HTML_BODY_START "<body bgcolor=\"#ffffff\" text=\"#000000\" link=\"#336699\">"
#define HTML_SEP "<hr color=#336699 align=\"left\" width=450>"
#define HTML_BODY_END "</body>"
#define HTML_FOOTER "</html>"
static void e_itip_control_destroy (GtkObject *obj);
static void find_my_address (EItipControl *itip, icalcomponent *ical_comp, icalparameter_partstat *status);
static void url_requested_cb (GtkHTML *html, const gchar *url, GtkHTMLStream *handle, gpointer data);
static gboolean object_requested_cb (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data);
static void ok_clicked_cb (GtkWidget *widget, gpointer data);
G_DEFINE_TYPE (EItipControl, e_itip_control, GTK_TYPE_VBOX);
static void
e_itip_control_class_init (EItipControlClass *klass)
{
GObjectClass *object_class;
GtkObjectClass *gtkobject_class;
object_class = G_OBJECT_CLASS (klass);
gtkobject_class = GTK_OBJECT_CLASS (klass);
gtkobject_class->destroy = e_itip_control_destroy;
}
static void
set_ok_sens (EItipControl *itip)
{
EItipControlPrivate *priv;
gboolean read_only = TRUE;
priv = itip->priv;
if (!priv->ok)
return;
if (priv->current_ecal)
e_cal_is_read_only (priv->current_ecal, &read_only, NULL);
gtk_widget_set_sensitive (priv->ok, priv->current_ecal != NULL && !read_only);
}
static void
cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data)
{
EItipControl *itip = data;
EItipControlPrivate *priv;
ESource *source;
ECalSourceType source_type;
priv = itip->priv;
source_type = e_cal_get_source_type (ecal);
source = e_cal_get_source (ecal);
g_signal_handlers_disconnect_matched (ecal, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, cal_opened_cb, NULL);
if (status != E_CALENDAR_STATUS_OK) {
g_hash_table_remove (priv->ecals[source_type], e_source_peek_uid (source));
return;
}
priv->current_ecal = ecal;
set_ok_sens (itip);
}
typedef void (* EItipControlOpenFunc) (ECal *ecal, ECalendarStatus status, gpointer data);
static ECal *
start_calendar_server (EItipControl *itip, ESource *source, ECalSourceType type, EItipControlOpenFunc func, gpointer data)
{
EItipControlPrivate *priv;
ECal *ecal;
icaltimezone *zone;
priv = itip->priv;
ecal = g_hash_table_lookup (priv->ecals[type], e_source_peek_uid (source));
if (ecal) {
priv->current_ecal = ecal;
set_ok_sens (itip);
return ecal;
}
ecal = auth_new_cal_from_source (source, type);
zone = calendar_config_get_icaltimezone ();
e_cal_set_default_timezone (ecal, zone, NULL);
g_signal_connect (G_OBJECT (ecal), "cal_opened", G_CALLBACK (func), data);
g_hash_table_insert (priv->ecals[type], g_strdup (e_source_peek_uid (source)), ecal);
e_cal_open_async (ecal, TRUE);
return ecal;
}
static ECal *
start_calendar_server_by_uid (EItipControl *itip, const char *uid, ECalSourceType type)
{
EItipControlPrivate *priv;
int i;
priv = itip->priv;
for (i = 0; i < E_CAL_SOURCE_TYPE_LAST; i++) {
ESource *source;
source = e_source_list_peek_source_by_uid (priv->source_lists[i], uid);
if (source)
return start_calendar_server (itip, source, type, cal_opened_cb, itip);
}
return NULL;
}
typedef struct {
EItipControl *itip;
char *uid;
int count;
gboolean show_selector;
} EItipControlFindData;
static void
source_selected_cb (ESourceOptionMenu *esom, ESource *source, gpointer data)
{
EItipControl *itip = data;
EItipControlPrivate *priv;
priv = itip->priv;
if (priv->ok)
gtk_widget_set_sensitive (priv->ok, FALSE);
start_calendar_server (itip, source, priv->type, cal_opened_cb, itip);
}
static void
find_cal_opened_cb (ECal *ecal, ECalendarStatus status, gpointer data)
{
EItipControlFindData *fd = data;
EItipControlPrivate *priv;
ESource *source;
ECalSourceType source_type;
icalcomponent *icalcomp;
source_type = e_cal_get_source_type (ecal);
source = e_cal_get_source (ecal);
priv = fd->itip->priv;
fd->count--;
g_signal_handlers_disconnect_matched (ecal, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, find_cal_opened_cb, NULL);
if (status != E_CALENDAR_STATUS_OK) {
g_hash_table_remove (priv->ecals[source_type], e_source_peek_uid (source));
goto cleanup;
}
if (e_cal_get_object (ecal, fd->uid, NULL, &icalcomp, NULL)) {
icalcomponent_free (icalcomp);
priv->current_ecal = ecal;
set_ok_sens (fd->itip);
}
cleanup:
if (fd->count == 0) {
if (fd->show_selector && !priv->current_ecal && priv->vbox) {
GtkWidget *esom;
ESource *source = NULL;
char *uid;
switch (priv->type) {
case E_CAL_SOURCE_TYPE_EVENT:
uid = calendar_config_get_primary_calendar ();
break;
case E_CAL_SOURCE_TYPE_TODO:
uid = calendar_config_get_primary_tasks ();
break;
default:
uid = NULL;
g_assert_not_reached ();
}
if (uid) {
source = e_source_list_peek_source_by_uid (priv->source_lists[priv->type], uid);
g_free (uid);
}
/* Try to create a default if there isn't one */
if (!source)
source = e_source_list_peek_source_any (priv->source_lists[priv->type]);
esom = e_source_option_menu_new (priv->source_lists[priv->type]);
g_signal_connect_object (esom, "source_selected",
G_CALLBACK (source_selected_cb),
fd->itip, 0);
gtk_box_pack_start (GTK_BOX (priv->vbox), esom, FALSE, TRUE, 0);
gtk_widget_show (esom);
/* FIXME What if there is no source? */
if (source)
e_source_option_menu_select (E_SOURCE_OPTION_MENU (esom), source);
} else {
/* FIXME Display error message to user */
}
g_free (fd->uid);
g_free (fd);
}
}
static void
find_server (EItipControl *itip, ECalComponent *comp, gboolean show_selector)
{
EItipControlPrivate *priv;
EItipControlFindData *fd = NULL;
GSList *groups, *l;
const char *uid;
priv = itip->priv;
e_cal_component_get_uid (comp, &uid);
groups = e_source_list_peek_groups (priv->source_lists[priv->type]);
for (l = groups; l; l = l->next) {
ESourceGroup *group;
GSList *sources, *m;
group = l->data;
sources = e_source_group_peek_sources (group);
for (m = sources; m; m = m->next) {
ESource *source;
ECal *ecal;
source = m->data;
if (!fd) {
fd = g_new0 (EItipControlFindData, 1);
fd->itip = itip;
fd->uid = g_strdup (uid);
fd->show_selector = show_selector;
}
fd->count++;
ecal = start_calendar_server (itip, source, priv->type, find_cal_opened_cb, fd);
}
}
}
static void
cleanup_ecal (void *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);
g_object_unref (ecal);
}
static void
html_destroyed (gpointer data)
{
EItipControl *itip = data;
EItipControlPrivate *priv;
priv = itip->priv;
priv->html = NULL;
}
static void
e_itip_control_init (EItipControl *itip)
{
EItipControlPrivate *priv;
GtkWidget *scrolled_window;
int i;
priv = g_new0 (EItipControlPrivate, 1);
itip->priv = priv;
/* Addresses */
priv->accounts = itip_addresses_get ();
/* Source Lists */
for (i = 0; i < E_CAL_SOURCE_TYPE_LAST; i++)
priv->source_lists[i] = NULL;
priv->source_lists[E_CAL_SOURCE_TYPE_EVENT] = e_source_list_new_for_gconf_default ("/apps/evolution/calendar/sources");
priv->source_lists[E_CAL_SOURCE_TYPE_TODO] = e_source_list_new_for_gconf_default ("/apps/evolution/tasks/sources");
/* Initialize the ecal hashes */
for (i = 0; i < E_CAL_SOURCE_TYPE_LAST; i++)
priv->ecals[i] = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, cleanup_ecal);
priv->current_ecal = NULL;
/* Other fields to init */
priv->calendar_uid = NULL;
priv->from_address = NULL;
priv->delegator_address = NULL;
priv->delegator_name = NULL;
priv->my_address = NULL;
priv->view_only = 0;
/* Html Widget */
priv->html = gtk_html_new ();
gtk_html_set_default_content_type (GTK_HTML (priv->html),
"text/html; charset=utf-8");
gtk_html_load_from_string (GTK_HTML (priv->html), " ", 1);
gtk_widget_show (priv->html);
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_AUTOMATIC);
gtk_widget_show (scrolled_window);
gtk_container_add (GTK_CONTAINER (scrolled_window), priv->html);
g_object_weak_ref (G_OBJECT (priv->html), (GWeakNotify)html_destroyed, itip);
gtk_widget_set_usize (scrolled_window, 600, 400);
gtk_box_pack_start (GTK_BOX (itip), scrolled_window, FALSE, FALSE, 6);
g_signal_connect (priv->html, "url_requested", G_CALLBACK (url_requested_cb), itip);
g_signal_connect (priv->html, "object_requested", G_CALLBACK (object_requested_cb), itip);
g_signal_connect (priv->html, "submit", G_CALLBACK (ok_clicked_cb), itip);
}
static void
clean_up (EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
if (!priv)
return;
g_free (priv->vcalendar);
priv->vcalendar = NULL;
if (priv->comp) {
g_object_unref (priv->comp);
priv->comp = NULL;
}
if (priv->top_level) {
icalcomponent_free (priv->top_level);
priv->top_level = NULL;
}
if (priv->main_comp) {
icalcomponent_free (priv->main_comp);
priv->main_comp = NULL;
}
priv->ical_comp = NULL;
priv->current = 0;
priv->total = 0;
g_free (priv->calendar_uid);
priv->calendar_uid = NULL;
g_free (priv->from_address);
priv->from_address = NULL;
g_free (priv->delegator_address);
priv->delegator_address = NULL;
g_free (priv->delegator_name);
priv->delegator_name = NULL;
g_free (priv->my_address);
priv->my_address = NULL;
}
static void
e_itip_control_destroy (GtkObject *obj)
{
EItipControl *itip = E_ITIP_CONTROL (obj);
EItipControlPrivate *priv;
priv = itip->priv;
if (priv) {
int i;
clean_up (itip);
priv->accounts = NULL;
for (i = 0; i < E_CAL_SOURCE_TYPE_LAST; i++) {
if (priv->ecals[i]) {
g_hash_table_destroy (priv->ecals[i]);
priv->ecals[i] = NULL;
}
}
if (priv->html) {
g_signal_handlers_disconnect_matched (priv->html, G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, itip);
g_object_weak_unref (G_OBJECT (priv->html), (GWeakNotify)html_destroyed, itip);
}
g_free (priv);
itip->priv = NULL;
}
(* GTK_OBJECT_CLASS (e_itip_control_parent_class)->destroy) (obj);
}
GtkWidget *
e_itip_control_new (void)
{
return g_object_new (E_TYPE_ITIP_CONTROL, NULL);
}
static void
find_my_address (EItipControl *itip, icalcomponent *ical_comp, icalparameter_partstat *status)
{
EItipControlPrivate *priv;
icalproperty *prop;
char *my_alt_address = NULL;
priv = itip->priv;
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 (priv->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)) {
priv->my_address = g_strdup (itip_strip_mailto (priv->delegator_address));
priv->my_address = g_strstrip (priv->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 *)priv->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)) {
priv->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);
}
priv->my_address = my_alt_address;
if (status)
*status = ICAL_PARTSTAT_NEEDSACTION;
}
static icalproperty *
find_attendee (icalcomponent *ical_comp, const char *address)
{
icalproperty *prop;
if (address == NULL)
return 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;
const char *attendee;
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_ascii_strcasecmp (address, text)) {
g_free (text);
break;
}
g_free (text);
}
return prop;
}
static void
write_label_piece (EItipControl *itip, ECalComponentDateTime *dt,
char *buffer, int size,
const char *stext, const char *etext,
gboolean just_date)
{
EItipControlPrivate *priv;
struct tm tmp_tm;
char time_buf[64];
icaltimezone *zone = NULL;
char *display_name;
priv = itip->priv;
/* UTC times get converted to the current timezone. This is done for
the COMPLETED property, which is always in UTC, and also because
Outlook sends simple events as UTC times. */
if (dt->value->is_utc) {
zone = calendar_config_get_icaltimezone ();
icaltimezone_convert_time (dt->value, icaltimezone_get_utc_timezone (), zone);
}
tmp_tm = icaltimetype_to_tm (dt->value);
if (just_date)
tmp_tm.tm_hour = tmp_tm.tm_min = tmp_tm.tm_sec = 0;
if (stext != NULL)
strcat (buffer, stext);
e_time_format_date_and_time (&tmp_tm,
calendar_config_get_24_hour_format (),
FALSE, FALSE,
time_buf, sizeof (time_buf));
strcat (buffer, time_buf);
if (!dt->value->is_utc && dt->tzid) {
zone = icalcomponent_get_timezone (priv->top_level, dt->tzid);
}
/* Output timezone after time, e.g. " America/New_York". */
if (zone && !just_date) {
/* Note that this returns UTF-8, since all iCalendar data is
UTF-8. But it probably is not translated. */
display_name = icaltimezone_get_display_name (zone);
if (display_name && *display_name) {
strcat (buffer, " <font size=-1>[");
/* We check if it is one of our builtin timezone names,
in which case we call gettext to translate it. */
if (icaltimezone_get_builtin_timezone (display_name)) {
strcat (buffer, _(display_name));
} else {
strcat (buffer, display_name);
}
strcat (buffer, "]</font>");
}
}
if (etext != NULL)
strcat (buffer, etext);
}
static const char *
nth (int n)
{
if (n == -1)
return "last";
else if (n < 1 || n > 31)
return "?";
else
return e_cal_recur_nth[n];
}
static const char *dayname[] = {
N_("Sunday"),
N_("Monday"),
N_("Tuesday"),
N_("Wednesday"),
N_("Thursday"),
N_("Friday"),
N_("Saturday")
};
static inline char *
get_dayname (struct icalrecurrencetype *r, int i)
{
enum icalrecurrencetype_weekday day;
day = icalrecurrencetype_day_day_of_week (r->by_day[i]);
g_return_val_if_fail (day > 0 && day < 8, "?");
return _(dayname[day - 1]);
}
static void
write_recurrence_piece (EItipControl *itip, ECalComponent *comp,
char *buffer, int size)
{
GSList *rrules;
struct icalrecurrencetype *r;
int len, i;
strcpy (buffer, "<b>Recurring:</b> ");
len = strlen (buffer);
buffer += len;
size -= len;
if (!e_cal_component_has_simple_recurrence (comp)) {
strcpy (buffer, _("Yes. (Complex Recurrence)"));
return;
}
e_cal_component_get_rrule_list (comp, &rrules);
g_return_if_fail (rrules && !rrules->next);
r = rrules->data;
switch (r->freq) {
case ICAL_DAILY_RECURRENCE:
sprintf (buffer, ngettext("Every day", "Every %d days", r->interval), r->interval);
break;
case ICAL_WEEKLY_RECURRENCE:
if (r->by_day[0] == ICAL_RECURRENCE_ARRAY_MAX) {
sprintf (buffer, ngettext("Every week", "Every %d weeks", r->interval), r->interval);
} else {
sprintf (buffer, ngettext("Every week on ", "Every %d weeks on ", r->interval), r->interval);
for (i = 1; i < 8 && r->by_day[i] != ICAL_RECURRENCE_ARRAY_MAX; i++) {
if (i > 1)
strcat (buffer, ", ");
strcat (buffer, get_dayname (r, i - 1));
}
if (i > 1)
strcat (buffer, _(" and "));
strcat (buffer, get_dayname (r, i - 1));
}
break;
case ICAL_MONTHLY_RECURRENCE:
if (r->by_month_day[0] != ICAL_RECURRENCE_ARRAY_MAX) {
sprintf (buffer, _("The %s day of "),
nth (r->by_month_day[0]));
} else {
int pos;
/* Outlook 2000 uses BYDAY=TU;BYSETPOS=2, and will not
accept BYDAY=2TU. So we now use the same as Outlook
by default. */
pos = icalrecurrencetype_day_position (r->by_day[0]);
if (pos == 0)
pos = r->by_set_pos[0];
sprintf (buffer, _("The %s %s of "),
nth (pos), get_dayname (r, 0));
}
len = strlen (buffer);
buffer += len;
size -= len;
sprintf (buffer, ngettext("every month","every %d months", r->interval), r->interval);
break;
case ICAL_YEARLY_RECURRENCE:
sprintf (buffer, ngettext("Every year", "Every %d years", r->interval), r->interval);
break;
default:
g_assert_not_reached ();
}
len = strlen (buffer);
buffer += len;
size -= len;
if (r->count) {
sprintf (buffer, ngettext("a total of %d time", " a total of %d times", r->count), r->count);
} else if (!icaltime_is_null_time (r->until)) {
ECalComponentDateTime dt;
/* FIXME This should get the tzid id, not the whole zone */
dt.value = &r->until;
dt.tzid = icaltimezone_get_tzid ((icaltimezone *)r->until.zone);
write_label_piece (itip, &dt, buffer, size,
_(", ending on "), NULL, TRUE);
}
strcat (buffer, "<br>");
}
static void
set_date_label (EItipControl *itip, GtkHTML *html, GtkHTMLStream *html_stream,
ECalComponent *comp)
{
EItipControlPrivate *priv;
ECalComponentDateTime datetime;
static char buffer[1024];
gchar *str;
gboolean wrote = FALSE, task_completed = FALSE;
ECalComponentVType type;
priv = itip->priv;
type = e_cal_component_get_vtype (comp);
buffer[0] = '\0';
e_cal_component_get_dtstart (comp, &datetime);
if (datetime.value) {
str = g_strdup_printf ("<b>%s:</b>", _("Starts"));
write_label_piece (itip, &datetime, buffer, 1024,
str,
"<br>", FALSE);
gtk_html_write (html, html_stream, buffer, strlen(buffer));
wrote = TRUE;
g_free (str);
}
e_cal_component_free_datetime (&datetime);
buffer[0] = '\0';
e_cal_component_get_dtend (comp, &datetime);
if (datetime.value){
str = g_strdup_printf ("<b>%s:</b>", _("Ends"));
write_label_piece (itip, &datetime, buffer, 1024, str, "<br>", FALSE);
gtk_html_write (html, html_stream, buffer, strlen (buffer));
wrote = TRUE;
g_free (str);
}
e_cal_component_free_datetime (&datetime);
buffer[0] = '\0';
if (e_cal_component_has_recurrences (comp)) {
write_recurrence_piece (itip, comp, buffer, 1024);
gtk_html_write (html, html_stream, buffer, strlen (buffer));
wrote = TRUE;
}
buffer[0] = '\0';
datetime.tzid = NULL;
e_cal_component_get_completed (comp, &datetime.value);
if (type == E_CAL_COMPONENT_TODO && datetime.value) {
/* Pass TRUE as is_utc, so it gets converted to the current
timezone. */
str = g_strdup_printf ("<b>%s:</b>", _("Completed"));
datetime.value->is_utc = TRUE;
write_label_piece (itip, &datetime, buffer, 1024, str, "<br>", FALSE);
gtk_html_write (html, html_stream, buffer, strlen (buffer));
wrote = TRUE;
task_completed = TRUE;
g_free (str);
}
e_cal_component_free_datetime (&datetime);
buffer[0] = '\0';
e_cal_component_get_due (comp, &datetime);
if (type == E_CAL_COMPONENT_TODO && !task_completed && datetime.value) {
str = g_strdup_printf ("<b>%s:</b>", _("Due"));
write_label_piece (itip, &datetime, buffer, 1024, str, "<br>", FALSE);
gtk_html_write (html, html_stream, buffer, strlen (buffer));
wrote = TRUE;
g_free (str);
}
e_cal_component_free_datetime (&datetime);
if (wrote)
gtk_html_stream_printf (html_stream, "<br>");
}
static void
set_message (GtkHTML *html, GtkHTMLStream *html_stream, const gchar *message, gboolean err)
{
if (message == NULL)
return;
if (err) {
gtk_html_stream_printf (html_stream, "<b><font color=\"#ff0000\">%s</font></b><br><br>", message);
} else {
gtk_html_stream_printf (html_stream, "<b>%s</b><br><br>", message);
}
}
static void
write_error_html (EItipControl *itip, const gchar *itip_err)
{
EItipControlPrivate *priv;
GtkHTMLStream *html_stream;
gchar *filename;
priv = itip->priv;
/* Html widget */
html_stream = gtk_html_begin (GTK_HTML (priv->html));
gtk_html_stream_printf (html_stream,
"<html><head><title>%s</title></head>",
_("iCalendar Information"));
gtk_html_write (GTK_HTML (priv->html), html_stream,
HTML_BODY_START, strlen(HTML_BODY_START));
/* The table */
gtk_html_stream_printf (html_stream, "<table width=450 cellspacing=\"0\" cellpadding=\"4\" border=\"0\">");
/* The column for the image */
gtk_html_stream_printf (html_stream, "<tr><td width=48 align=\"center\" valign=\"top\" rowspan=\"8\">");
/* The image */
filename = e_icon_factory_get_icon_filename ("stock_new-meeting", E_ICON_SIZE_DIALOG);
gtk_html_stream_printf (html_stream, "<img src=\"%s\"></td>", filename);
g_free (filename);
gtk_html_stream_printf (html_stream, "<td align=\"left\" valign=\"top\">");
/* Title */
set_message (GTK_HTML (priv->html), html_stream, _("iCalendar Error"), TRUE);
/* Error */
gtk_html_write (GTK_HTML (priv->html), html_stream, itip_err, strlen(itip_err));
/* Clean up */
gtk_html_stream_printf (html_stream, "</td></tr></table>");
gtk_html_write (GTK_HTML (priv->html), html_stream,
HTML_BODY_END, strlen(HTML_BODY_END));
gtk_html_write (GTK_HTML (priv->html), html_stream,
HTML_FOOTER, strlen(HTML_FOOTER));
gtk_html_end (GTK_HTML (priv->html), html_stream, GTK_HTML_STREAM_OK);
}
static void
write_html (EItipControl *itip, const gchar *itip_desc, const gchar *itip_title, const gchar *options)
{
EItipControlPrivate *priv;
GtkHTMLStream *html_stream;
ECalComponentText text;
ECalComponentOrganizer organizer;
ECalComponentAttendee *attendee;
GSList *attendees, *l = NULL;
const char *string;
gchar *html;
const gchar *const_html;
gchar *filename;
gchar *str;
priv = itip->priv;
if (priv->html == NULL)
return;
/* Html widget */
html_stream = gtk_html_begin (GTK_HTML (priv->html));
gtk_html_stream_printf (html_stream,
"<html><head><title>%s</title></head>",
_("iCalendar Information"));
gtk_html_write (GTK_HTML (priv->html), html_stream,
HTML_BODY_START, strlen(HTML_BODY_START));
/* The table */
const_html = "<table width=450 cellspacing=\"0\" cellpadding=\"4\" border=\"0\">";
gtk_html_write (GTK_HTML (priv->html), html_stream, const_html, strlen(const_html));
/* The column for the image */
const_html = "<tr><td width=48 align=\"center\" valign=\"top\" rowspan=\"8\">";
gtk_html_write (GTK_HTML (priv->html), html_stream, const_html, strlen(const_html));
/* The image */
filename = e_icon_factory_get_icon_filename ("stock_new-meeting", E_ICON_SIZE_DIALOG);
gtk_html_stream_printf (html_stream, "<img src=\"%s\"></td>", filename);
g_free (filename);
const_html = "<td align=\"left\" valign=\"top\">";
gtk_html_write (GTK_HTML (priv->html), html_stream, const_html, strlen(const_html));
switch (priv->method) {
case ICAL_METHOD_REFRESH:
case ICAL_METHOD_REPLY:
/* An attendee sent this */
e_cal_component_get_attendee_list (priv->comp, &attendees);
if (attendees != NULL) {
attendee = attendees->data;
html = g_strdup_printf (itip_desc,
attendee->cn ?
attendee->cn :
itip_strip_mailto (attendee->value));
} else {
html = g_strdup_printf (itip_desc, _("An unknown person"));
}
break;
case ICAL_METHOD_REQUEST:
/* The organizer sent this */
e_cal_component_get_organizer (priv->comp, &organizer);
if (priv->delegator_address != NULL) {
if (organizer.value != NULL)
html = g_strdup_printf (itip_desc,
organizer.cn ?
organizer.cn :
itip_strip_mailto (organizer.value),
priv->delegator_name ?
priv->delegator_name :
priv->delegator_address);
else
html = g_strdup_printf (itip_desc, _("An unknown person"),
priv->delegator_name ?
priv->delegator_name :
priv->delegator_address);
} else {
if (organizer.value != NULL)
html = g_strdup_printf (itip_desc,
organizer.cn ?
organizer.cn :
itip_strip_mailto (organizer.value));
else
html = g_strdup_printf (itip_desc, _("An unknown person"));
}
break;
case ICAL_METHOD_PUBLISH:
case ICAL_METHOD_ADD:
case ICAL_METHOD_CANCEL:
default:
/* The organizer sent this */
e_cal_component_get_organizer (priv->comp, &organizer);
if (organizer.value != NULL)
html = g_strdup_printf (itip_desc,
organizer.cn ?
organizer.cn :
itip_strip_mailto (organizer.value));
else
html = g_strdup_printf (itip_desc, _("An unknown person"));
break;
}
gtk_html_write (GTK_HTML (priv->html), html_stream, html, strlen(html));
g_free (html);
/* Describe what the user can do */
const_html = _("<br> Please review the following information, "
"and then select an action from the menu below.");
gtk_html_write (GTK_HTML (priv->html), html_stream, const_html, strlen(const_html));
/* Separator */
gtk_html_write (GTK_HTML (priv->html), html_stream, HTML_SEP, strlen (HTML_SEP));
/* Title */
set_message (GTK_HTML (priv->html), html_stream, itip_title, FALSE);
/* Date information */
set_date_label (itip, GTK_HTML (priv->html), html_stream, priv->comp);
/* Summary */
e_cal_component_get_summary (priv->comp, &text);
str = g_strdup_printf ("<i>%s:</i>", _("None"));
html = text.value ? e_text_to_html_full (text.value, E_TEXT_TO_HTML_CONVERT_NL, 0) : str;
gtk_html_stream_printf (html_stream, "<b>%s</b><br>%s<br><br>",
_("Summary:"), html);
g_free (str);
if (text.value)
g_free (html);
/* Location */
e_cal_component_get_location (priv->comp, &string);
if (string != NULL) {
html = e_text_to_html_full (string, E_TEXT_TO_HTML_CONVERT_NL, 0);
gtk_html_stream_printf (html_stream, "<b>%s</b><br>%s<br><br>",
_("Location:"), html);
g_free (html);
}
/* Status */
if (priv->method == ICAL_METHOD_REPLY) {
GSList *alist;
e_cal_component_get_attendee_list (priv->comp, &alist);
if (alist != NULL) {
ECalComponentAttendee *a = alist->data;
gtk_html_stream_printf (html_stream, "<b>%s</b><br>",
_("Status:"));
switch (a->status) {
case ICAL_PARTSTAT_ACCEPTED:
gtk_html_stream_printf (html_stream, "%s<br><br>",
_("Accepted"));
break;
case ICAL_PARTSTAT_TENTATIVE:
gtk_html_stream_printf (html_stream, "%s<br><br>",
_("Tentatively Accepted"));
break;
case ICAL_PARTSTAT_DECLINED:
gtk_html_stream_printf (html_stream, "%s<br><br>",
_("Declined"));
break;
default:
gtk_html_stream_printf (html_stream, "%s<br><br>",
_("Unknown"));
}
}
e_cal_component_free_attendee_list (alist);
}
/* Description */
e_cal_component_get_description_list (priv->comp, &l);
if (l)
text = *((ECalComponentText *)l->data);
if (l && text.value) {
html = e_text_to_html_full (text.value, E_TEXT_TO_HTML_CONVERT_NL, 0);
gtk_html_stream_printf (html_stream, "<b>%s</b><br>%s",
_("Description:"), html);
g_free (html);
}
e_cal_component_free_text_list (l);
/* Separator */
gtk_html_write (GTK_HTML (priv->html), html_stream, HTML_SEP, strlen (HTML_SEP));
/* Options */
if (!e_itip_control_get_view_only (itip)) {
if (options != NULL) {
const_html = "</td></tr><tr><td valign=\"center\">";
gtk_html_write (GTK_HTML (priv->html), html_stream, const_html, strlen (const_html));
gtk_html_write (GTK_HTML (priv->html), html_stream, options, strlen (options));
}
}
const_html = "</td></tr></table>";
gtk_html_write (GTK_HTML (priv->html), html_stream, const_html, strlen(const_html));
gtk_html_write (GTK_HTML (priv->html), html_stream,
HTML_BODY_END, strlen(HTML_BODY_END));
gtk_html_write (GTK_HTML (priv->html), html_stream,
HTML_FOOTER, strlen(HTML_FOOTER));
gtk_html_end (GTK_HTML (priv->html), html_stream, GTK_HTML_STREAM_OK);
}
static char*
get_publish_options (void)
{
return g_strdup_printf ("<object classid=\"itip:publish_options\"></object>");
}
static char*
get_request_options (void)
{
return g_strdup_printf ("<object classid=\"itip:request_options\"></object>");
}
static char*
get_request_fb_options ()
{
return g_strdup_printf ("<object classid=\"itip:freebusy_options\"></object>");
}
static char*
get_reply_options ()
{
return g_strdup_printf ("<object classid=\"itip:reply_options\"></object>");
}
static char*
get_refresh_options ()
{
return g_strdup_printf ("<object classid=\"itip:refresh_options\"></object>");
}
static char*
get_cancel_options (gboolean found, icalcomponent_kind kind)
{
if (!found) {
switch (kind) {
case ICAL_VEVENT_COMPONENT:
return g_strdup_printf ("<i>%s</i>", _("The meeting has been cancelled, however it could not be found in your calendars"));
case ICAL_VTODO_COMPONENT:
return g_strdup_printf ("<i>%s</i>", _("The task has been cancelled, however it could not be found in your task lists"));
default:
g_assert_not_reached ();
return NULL;
}
}
return g_strdup_printf ("<object classid=\"itip:cancel_options\"></object>");
}
static ECalComponent *
get_real_item (EItipControl *itip)
{
EItipControlPrivate *priv;
ECalComponent *comp;
icalcomponent *icalcomp;
gboolean found = FALSE;
const char *uid;
priv = itip->priv;
e_cal_component_get_uid (priv->comp, &uid);
found = e_cal_get_object (priv->current_ecal, uid, NULL, &icalcomp, NULL);
if (!found)
return NULL;
comp = e_cal_component_new ();
if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
g_object_unref (comp);
icalcomponent_free (icalcomp);
return NULL;
}
return comp;
}
static void
adjust_item (EItipControl *itip, ECalComponent *comp)
{
ECalComponent *real_comp;
real_comp = get_real_item (itip);
if (real_comp != NULL) {
ECalComponentText text;
const char *string;
GSList *l;
e_cal_component_get_summary (real_comp, &text);
e_cal_component_set_summary (comp, &text);
e_cal_component_get_location (real_comp, &string);
e_cal_component_set_location (comp, string);
e_cal_component_get_description_list (real_comp, &l);
e_cal_component_set_description_list (comp, l);
e_cal_component_free_text_list (l);
g_object_unref (real_comp);
} else {
ECalComponentText text = {_("Unknown"), NULL};
e_cal_component_set_summary (comp, &text);
}
}
static void
show_current_event (EItipControl *itip)
{
EItipControlPrivate *priv;
const gchar *itip_title, *itip_desc;
char *options;
gboolean show_selector = FALSE;
priv = itip->priv;
priv->type = E_CAL_SOURCE_TYPE_EVENT;
switch (priv->method) {
case ICAL_METHOD_PUBLISH:
itip_desc = _("<b>%s</b> has published meeting information.");
itip_title = _("Meeting Information");
options = get_publish_options ();
show_selector = TRUE;
break;
case ICAL_METHOD_REQUEST:
if (priv->delegator_address != NULL)
itip_desc = _("<b>%s</b> requests the presence of %s at a meeting.");
else
itip_desc = _("<b>%s</b> requests your presence at a meeting.");
itip_title = _("Meeting Proposal");
options = get_request_options ();
show_selector = TRUE;
break;
case ICAL_METHOD_ADD:
/* FIXME Whats going on here? */
itip_desc = _("<b>%s</b> wishes to add to an existing meeting.");
itip_title = _("Meeting Update");
options = get_publish_options ();
break;
case ICAL_METHOD_REFRESH:
itip_desc = _("<b>%s</b> wishes to receive the latest meeting information.");
itip_title = _("Meeting Update Request");
options = get_refresh_options ();
/* Provide extra info, since its not in the component */
adjust_item (itip, priv->comp);
break;
case ICAL_METHOD_REPLY:
itip_desc = _("<b>%s</b> has replied to a meeting request.");
itip_title = _("Meeting Reply");
options = get_reply_options ();
/* Provide extra info, since might not be in the component */
adjust_item (itip, priv->comp);
break;
case ICAL_METHOD_CANCEL:
itip_desc = _("<b>%s</b> has cancelled a meeting.");
itip_title = _("Meeting Cancellation");
/* FIXME priv->current_ecal will always be NULL so the
* user won't see an error message, the OK button will
* just be de-sensitized */
options = get_cancel_options (TRUE, ICAL_VEVENT_COMPONENT);
/* Provide extra info, since might not be in the component */
adjust_item (itip, priv->comp);
break;
default:
itip_desc = _("<b>%s</b> has sent an unintelligible message.");
itip_title = _("Bad Meeting Message");
options = NULL;
}
write_html (itip, itip_desc, itip_title, options);
g_free (options);
if (priv->calendar_uid)
priv->current_ecal = start_calendar_server_by_uid (itip, priv->calendar_uid, priv->type);
else
find_server (itip, priv->comp, show_selector);
}
static void
show_current_todo (EItipControl *itip)
{
EItipControlPrivate *priv;
const gchar *itip_title, *itip_desc;
char *options;
gboolean show_selector = FALSE;
priv = itip->priv;
priv->type = E_CAL_SOURCE_TYPE_TODO;
switch (priv->method) {
case ICAL_METHOD_PUBLISH:
itip_desc = _("<b>%s</b> has published task information.");
itip_title = _("Task Information");
options = get_publish_options ();
show_selector = TRUE;
break;
case ICAL_METHOD_REQUEST:
/* FIXME Does this need to handle like events above? */
if (priv->delegator_address != NULL)
itip_desc = _("<b>%s</b> requests %s to perform a task.");
else
itip_desc = _("<b>%s</b> requests you perform a task.");
itip_title = _("Task Proposal");
options = get_request_options ();
show_selector = TRUE;
break;
case ICAL_METHOD_ADD:
/* FIXME Whats going on here? */
itip_desc = _("<b>%s</b> wishes to add to an existing task.");
itip_title = _("Task Update");
options = get_publish_options ();
break;
case ICAL_METHOD_REFRESH:
itip_desc = _("<b>%s</b> wishes to receive the latest task information.");
itip_title = _("Task Update Request");
options = get_refresh_options ();
/* Provide extra info, since its not in the component */
adjust_item (itip, priv->comp);
break;
case ICAL_METHOD_REPLY:
itip_desc = _("<b>%s</b> has replied to a task assignment.");
itip_title = _("Task Reply");
options = get_reply_options ();
/* Provide extra info, since might not be in the component */
adjust_item (itip, priv->comp);
break;
case ICAL_METHOD_CANCEL:
itip_desc = _("<b>%s</b> has cancelled a task.");
itip_title = _("Task Cancellation");
/* FIXME priv->current_ecal will always be NULL so the
* user won't see an error message, the OK button will
* just be de-sensitized */
options = get_cancel_options (TRUE, ICAL_VTODO_COMPONENT);
/* Provide extra info, since might not be in the component */
adjust_item (itip, priv->comp);
break;
default:
itip_desc = _("<b>%s</b> has sent an unintelligible message.");
itip_title = _("Bad Task Message");
options = NULL;
}
write_html (itip, itip_desc, itip_title, options);
g_free (options);
if (priv->calendar_uid)
priv->current_ecal = start_calendar_server_by_uid (itip, priv->calendar_uid, priv->type);
else
find_server (itip, priv->comp, show_selector);
}
static void
show_current_freebusy (EItipControl *itip)
{
EItipControlPrivate *priv;
const gchar *itip_title, *itip_desc;
char *options;
priv = itip->priv;
switch (priv->method) {
case ICAL_METHOD_PUBLISH:
itip_desc = _("<b>%s</b> has published free/busy information.");
itip_title = _("Free/Busy Information");
options = NULL;
break;
case ICAL_METHOD_REQUEST:
itip_desc = _("<b>%s</b> requests your free/busy information.");
itip_title = _("Free/Busy Request");
options = get_request_fb_options ();
break;
case ICAL_METHOD_REPLY:
itip_desc = _("<b>%s</b> has replied to a free/busy request.");
itip_title = _("Free/Busy Reply");
options = NULL;
break;
default:
itip_desc = _("<b>%s</b> has sent an unintelligible message.");
itip_title = _("Bad Free/Busy Message");
options = NULL;
}
write_html (itip, itip_desc, itip_title, options);
g_free (options);
}
static icalcomponent *
get_next (icalcompiter *iter)
{
icalcomponent *ret = NULL;
icalcomponent_kind kind;
do {
icalcompiter_next (iter);
ret = icalcompiter_deref (iter);
if (ret == NULL)
break;
kind = icalcomponent_isa (ret);
} while (ret != NULL
&& kind != ICAL_VEVENT_COMPONENT
&& kind != ICAL_VTODO_COMPONENT
&& kind != ICAL_VFREEBUSY_COMPONENT);
return ret;
}
static void
show_current (EItipControl *itip)
{
EItipControlPrivate *priv;
ECalComponentVType type;
icalcomponent *alarm_comp;
icalcompiter alarm_iter;
icalproperty *prop;
priv = itip->priv;
g_object_ref (itip);
if (priv->comp)
g_object_unref (priv->comp);
priv->current_ecal = NULL;
/* Determine any delegate sections */
prop = icalcomponent_get_first_property (priv->ical_comp, ICAL_X_PROPERTY);
while (prop) {
const char *x_name, *x_val;
x_name = icalproperty_get_x_name (prop);
x_val = icalproperty_get_x (prop);
if (!strcmp (x_name, "X-EVOLUTION-DELEGATOR-CALENDAR-UID"))
e_itip_control_set_calendar_uid (itip, x_val);
else if (!strcmp (x_name, "X-EVOLUTION-DELEGATOR-CALENDAR-URI"))
g_warning (G_STRLOC ": X-EVOLUTION-DELEGATOR-CALENDAR-URI used");
else if (!strcmp (x_name, "X-EVOLUTION-DELEGATOR-ADDRESS"))
e_itip_control_set_delegator_address (itip, x_val);
else if (!strcmp (x_name, "X-EVOLUTION-DELEGATOR-NAME"))
e_itip_control_set_delegator_name (itip, x_val);
prop = icalcomponent_get_next_property (priv->ical_comp, ICAL_X_PROPERTY);
}
/* Strip out alarms for security purposes */
alarm_iter = icalcomponent_begin_component (priv->ical_comp, ICAL_VALARM_COMPONENT);
while ((alarm_comp = icalcompiter_deref (&alarm_iter)) != NULL) {
icalcomponent_remove_component (priv->ical_comp, alarm_comp);
icalcompiter_next (&alarm_iter);
}
priv->comp = e_cal_component_new ();
if (!e_cal_component_set_icalcomponent (priv->comp, priv->ical_comp)) {
write_error_html (itip, _("The message does not appear to be properly formed"));
g_object_unref (priv->comp);
priv->comp = NULL;
g_object_unref (itip);
return;
};
/* Add default reminder if the config says so */
if (calendar_config_get_use_default_reminder ()) {
ECalComponentAlarm *acomp;
int interval;
CalUnits units;
ECalComponentAlarmTrigger trigger;
interval = calendar_config_get_default_reminder_interval ();
units = calendar_config_get_default_reminder_units ();
acomp = e_cal_component_alarm_new ();
e_cal_component_alarm_set_action (acomp, E_CAL_COMPONENT_ALARM_DISPLAY);
trigger.type = E_CAL_COMPONENT_ALARM_TRIGGER_RELATIVE_START;
memset (&trigger.u.rel_duration, 0, sizeof (trigger.u.rel_duration));
trigger.u.rel_duration.is_neg = TRUE;
switch (units) {
case CAL_MINUTES:
trigger.u.rel_duration.minutes = interval;
break;
case CAL_HOURS:
trigger.u.rel_duration.hours = interval;
break;
case CAL_DAYS:
trigger.u.rel_duration.days = interval;
break;
default:
g_assert_not_reached ();
}
e_cal_component_alarm_set_trigger (acomp, trigger);
e_cal_component_add_alarm (priv->comp, acomp);
e_cal_component_alarm_free (acomp);
}
type = e_cal_component_get_vtype (priv->comp);
switch (type) {
case E_CAL_COMPONENT_EVENT:
show_current_event (itip);
break;
case E_CAL_COMPONENT_TODO:
show_current_todo (itip);
break;
case E_CAL_COMPONENT_FREEBUSY:
show_current_freebusy (itip);
break;
default:
write_error_html (itip, _("The message contains only unsupported requests."));
}
find_my_address (itip, priv->ical_comp, NULL);
g_object_unref (itip);
}
void
e_itip_control_set_data (EItipControl *itip, const gchar *text)
{
EItipControlPrivate *priv;
icalproperty *prop;
icalcomponent_kind kind = ICAL_NO_COMPONENT;
icalcomponent *tz_comp;
icalcompiter tz_iter;
priv = itip->priv;
if (priv == NULL)
return;
clean_up (itip);
if (text == NULL || *text == '\0') {
gtk_html_load_from_string (GTK_HTML (priv->html), " ", 1);
return;
}
priv->vcalendar = g_strdup (text);
priv->top_level = e_cal_util_new_top_level ();
priv->main_comp = icalparser_parse_string (priv->vcalendar);
if (priv->main_comp == NULL) {
write_error_html (itip, _("The attachment does not contain a valid calendar message"));
return;
}
prop = icalcomponent_get_first_property (priv->main_comp, ICAL_METHOD_PROPERTY);
if (prop == NULL) {
priv->method = ICAL_METHOD_PUBLISH;
} else {
priv->method = icalproperty_get_method (prop);
}
tz_iter = icalcomponent_begin_component (priv->main_comp, ICAL_VTIMEZONE_COMPONENT);
while ((tz_comp = icalcompiter_deref (&tz_iter)) != NULL) {
icalcomponent *clone;
clone = icalcomponent_new_clone (tz_comp);
icalcomponent_add_component (priv->top_level, clone);
icalcompiter_next (&tz_iter);
}
priv->iter = icalcomponent_begin_component (priv->main_comp, ICAL_ANY_COMPONENT);
priv->ical_comp = icalcompiter_deref (&priv->iter);
if (priv->ical_comp != NULL) {
kind = icalcomponent_isa (priv->ical_comp);
if (kind != ICAL_VEVENT_COMPONENT
&& kind != ICAL_VTODO_COMPONENT
&& kind != ICAL_VFREEBUSY_COMPONENT)
priv->ical_comp = get_next (&priv->iter);
}
if (priv->ical_comp == NULL) {
write_error_html (itip, _("The attachment has no viewable calendar items"));
return;
}
priv->total = icalcomponent_count_components (priv->main_comp, ICAL_VEVENT_COMPONENT);
priv->total += icalcomponent_count_components (priv->main_comp, ICAL_VTODO_COMPONENT);
priv->total += icalcomponent_count_components (priv->main_comp, ICAL_VFREEBUSY_COMPONENT);
if (priv->total > 0)
priv->current = 1;
else
priv->current = 0;
show_current (itip);
}
gchar *
e_itip_control_get_data (EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
return g_strdup (priv->vcalendar);
}
gint
e_itip_control_get_data_size (EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
if (priv->vcalendar == NULL)
return 0;
return strlen (priv->vcalendar);
}
void
e_itip_control_set_from_address (EItipControl *itip, const gchar *address)
{
EItipControlPrivate *priv;
priv = itip->priv;
if (priv->from_address)
g_free (priv->from_address);
priv->from_address = g_strdup (address);
}
const gchar *
e_itip_control_get_from_address (EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
return priv->from_address;
}
void
e_itip_control_set_view_only (EItipControl *itip, gint view_only)
{
EItipControlPrivate *priv;
priv = itip->priv;
priv->view_only = view_only;
}
gint
e_itip_control_get_view_only (EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
return priv->view_only;
}
void
e_itip_control_set_delegator_address (EItipControl *itip, const gchar *address)
{
EItipControlPrivate *priv;
priv = itip->priv;
if (priv->delegator_address)
g_free (priv->delegator_address);
priv->delegator_address = g_strdup (address);
}
const gchar *
e_itip_control_get_delegator_address (EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
return priv->delegator_address;
}
void
e_itip_control_set_delegator_name (EItipControl *itip, const gchar *name)
{
EItipControlPrivate *priv;
priv = itip->priv;
if (priv->delegator_name)
g_free (priv->delegator_name);
priv->delegator_name = g_strdup (name);
}
const gchar *
e_itip_control_get_delegator_name (EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
return priv->delegator_name;
}
void
e_itip_control_set_calendar_uid (EItipControl *itip, const gchar *uri)
{
EItipControlPrivate *priv;
priv = itip->priv;
if (priv->calendar_uid)
g_free (priv->calendar_uid);
priv->calendar_uid = g_strdup (uri);
}
const gchar *
e_itip_control_get_calendar_uid (EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
return priv->calendar_uid;
}
static gboolean
change_status (icalcomponent *ical_comp, const char *address, icalparameter_partstat status)
{
icalproperty *prop;
prop = find_attendee (ical_comp, address);
if (prop) {
icalparameter *param;
icalproperty_remove_parameter (prop, ICAL_PARTSTAT_PARAMETER);
param = icalparameter_new_partstat (status);
icalproperty_add_parameter (prop, param);
} else {
icalparameter *param;
if (address != NULL) {
prop = icalproperty_new_attendee (address);
icalcomponent_add_property (ical_comp, prop);
param = icalparameter_new_role (ICAL_ROLE_OPTPARTICIPANT);
icalproperty_add_parameter (prop, param);
param = icalparameter_new_partstat (status);
icalproperty_add_parameter (prop, param);
} else {
EAccount *a;
a = itip_addresses_get_default ();
prop = icalproperty_new_attendee (a->id->address);
icalcomponent_add_property (ical_comp, prop);
param = icalparameter_new_cn (a->id->name);
icalproperty_add_parameter (prop, param);
param = icalparameter_new_role (ICAL_ROLE_REQPARTICIPANT);
icalproperty_add_parameter (prop, param);
param = icalparameter_new_partstat (status);
icalproperty_add_parameter (prop, param);
}
}
return TRUE;
}
static void
update_item (EItipControl *itip)
{
EItipControlPrivate *priv;
struct icaltimetype stamp;
icalproperty *prop;
icalcomponent *clone;
GtkWidget *dialog;
GError *error = NULL;
priv = itip->priv;
/* Set X-MICROSOFT-CDO-REPLYTIME to record the time at which
* the user accepted/declined the request. (Outlook ignores
* SEQUENCE in REPLY reponses and instead requires that each
* updated response have a later REPLYTIME than the previous
* one.) This also ends up getting saved in our own copy of
* the meeting, though there's currently no way to see that
* information (unless it's being saved to an Exchange folder
* and you then look at it in Outlook).
*/
stamp = icaltime_current_time_with_zone (icaltimezone_get_utc_timezone ());
prop = icalproperty_new_x (icaltime_as_ical_string (stamp));
icalproperty_set_x_name (prop, "X-MICROSOFT-CDO-REPLYTIME");
icalcomponent_add_property (priv->ical_comp, prop);
clone = icalcomponent_new_clone (priv->ical_comp);
icalcomponent_add_component (priv->top_level, clone);
icalcomponent_set_method (priv->top_level, priv->method);
if (!e_cal_receive_objects (priv->current_ecal, priv->top_level, &error)) {
dialog = gnome_warning_dialog (error->message);
g_error_free (error);
} else {
dialog = gnome_ok_dialog (_("Update complete\n"));
}
gnome_dialog_run_and_close (GNOME_DIALOG (dialog));
icalcomponent_remove_component (priv->top_level, clone);
}
static void
update_attendee_status (EItipControl *itip)
{
EItipControlPrivate *priv;
ECalComponent *comp = NULL;
icalcomponent *icalcomp = NULL;
const char *uid;
GtkWidget *dialog;
GError *error;
priv = itip->priv;
/* Obtain our version */
e_cal_component_get_uid (priv->comp, &uid);
if (e_cal_get_object (priv->current_ecal, uid, NULL, &icalcomp, NULL)) {
GSList *attendees;
comp = e_cal_component_new ();
if (!e_cal_component_set_icalcomponent (comp, icalcomp)) {
icalcomponent_free (icalcomp);
dialog = gnome_warning_dialog (_("Object is invalid and cannot be updated\n"));
} else {
e_cal_component_get_attendee_list (priv->comp, &attendees);
if (attendees != NULL) {
ECalComponentAttendee *a = attendees->data;
icalproperty *prop;
prop = find_attendee (icalcomp, itip_strip_mailto (a->value));
if (prop == NULL) {
dialog = gnome_question_dialog_modal (_("This response is not from a current "
"attendee. Add as an attendee?"),
NULL, NULL);
if (gnome_dialog_run_and_close (GNOME_DIALOG (dialog)) == GNOME_YES) {
change_status (icalcomp,
itip_strip_mailto (a->value),
a->status);
e_cal_component_rescan (comp);
} else {
goto cleanup;
}
} else if (a->status == ICAL_PARTSTAT_NONE || a->status == ICAL_PARTSTAT_X) {
dialog = gnome_warning_dialog (_("Attendee status could "
"not be updated because "
"of an invalid status!\n"));
goto cleanup;
} else {
change_status (icalcomp,
itip_strip_mailto (a->value),
a->status);
e_cal_component_rescan (comp);
}
}
}
if (!e_cal_modify_object (priv->current_ecal, icalcomp, CALOBJ_MOD_ALL, &error)) {
dialog = gnome_warning_dialog (error->message);
g_error_free (error);
} else {
dialog = gnome_ok_dialog (_("Attendee status updated\n"));
}
} else {
dialog = gnome_warning_dialog (_("Attendee status can not be updated "
"because the item no longer exists"));
}
cleanup:
if (comp != NULL)
g_object_unref (comp);
gnome_dialog_run_and_close (GNOME_DIALOG (dialog));
}
static void
remove_item (EItipControl *itip)
{
EItipControlPrivate *priv;
const char *uid;
GtkWidget *dialog;
GError *error = NULL;
priv = itip->priv;
/* FIXME Is this check necessary? */
if (!priv->current_ecal)
return;
e_cal_component_get_uid (priv->comp, &uid);
if (e_cal_component_has_recurrences (priv->comp))
e_cal_remove_object_with_mod (priv->current_ecal, uid, NULL, CALOBJ_MOD_ALL, &error);
else
e_cal_remove_object (priv->current_ecal, uid, &error);
if (!error || error->code == E_CALENDAR_STATUS_OBJECT_NOT_FOUND) {
dialog = gnome_ok_dialog (_("Removal Complete"));
gnome_dialog_run_and_close (GNOME_DIALOG (dialog));
} else {
delete_error_dialog (error, e_cal_component_get_vtype (priv->comp));
}
g_clear_error (&error);
}
static void
send_item (EItipControl *itip)
{
EItipControlPrivate *priv;
ECalComponent *comp;
GtkWidget *dialog;
priv = itip->priv;
comp = get_real_item (itip);
if (comp != NULL) {
itip_send_comp (E_CAL_COMPONENT_METHOD_REQUEST, comp, priv->current_ecal, NULL, NULL);
g_object_unref (comp);
dialog = gnome_ok_dialog (_("Item sent!\n"));
} else {
dialog = gnome_warning_dialog (_("The item could not be sent!\n"));
}
gnome_dialog_run_and_close (GNOME_DIALOG (dialog));
}
static void
send_freebusy (EItipControl *itip)
{
EItipControlPrivate *priv;
ECalComponentDateTime datetime;
time_t start, end;
GtkWidget *dialog;
GList *comp_list = NULL;
icaltimezone *zone;
priv = itip->priv;
e_cal_component_get_dtstart (priv->comp, &datetime);
if (datetime.tzid) {
zone = icalcomponent_get_timezone (priv->top_level,
datetime.tzid);
} else {
zone = NULL;
}
start = icaltime_as_timet_with_zone (*datetime.value, zone);
e_cal_component_free_datetime (&datetime);
e_cal_component_get_dtend (priv->comp, &datetime);
if (datetime.tzid) {
zone = icalcomponent_get_timezone (priv->top_level,
datetime.tzid);
} else {
zone = NULL;
}
end = icaltime_as_timet_with_zone (*datetime.value, zone);
e_cal_component_free_datetime (&datetime);
if (e_cal_get_free_busy (priv->current_ecal, NULL, start, end, &comp_list, NULL)) {
GList *l;
for (l = comp_list; l; l = l->next) {
ECalComponent *comp = E_CAL_COMPONENT (l->data);
itip_send_comp (E_CAL_COMPONENT_METHOD_REPLY, comp, priv->current_ecal, NULL, NULL);
g_object_unref (comp);
}
dialog = gnome_ok_dialog (_("Item sent!\n"));
g_list_free (comp_list);
} else {
dialog = gnome_warning_dialog (_("The item could not be sent!\n"));
}
gnome_dialog_run_and_close (GNOME_DIALOG (dialog));
}
static void
url_requested_cb (GtkHTML *html, const gchar *url, GtkHTMLStream *handle, gpointer data)
{ unsigned char buffer[4096];
int len, fd;
if ((fd = g_open (url, O_RDONLY|O_BINARY, 0)) == -1) {
g_warning ("%s", g_strerror (errno));
return;
}
while ((len = read (fd, buffer, 4096)) > 0) {
gtk_html_write (html, handle, buffer, len);
}
if (len < 0) {
/* check to see if we stopped because of an error */
gtk_html_end (html, handle, GTK_HTML_STREAM_ERROR);
g_warning ("%s", g_strerror (errno));
return;
}
/* done with no errors */
gtk_html_end (html, handle, GTK_HTML_STREAM_OK);
close (fd);
}
static void
option_activated_cb (GtkWidget *widget, gpointer data)
{
EItipControl *itip = E_ITIP_CONTROL (data);
EItipControlPrivate *priv;
priv = itip->priv;
priv->action = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), ACTION_DATA));
}
static void
add_option (EItipControl *itip, GtkWidget *menu, const char *text, char action)
{
GtkWidget *item;
item = gtk_menu_item_new_with_label (text);
g_signal_connect (item, "activate", G_CALLBACK (option_activated_cb), itip);
g_object_set_data (G_OBJECT (item), ACTION_DATA, GINT_TO_POINTER ((int)action));
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
gtk_widget_show (item);
}
static void
insert_boxes (GtkHTMLEmbedded *eb, EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
priv->vbox = gtk_vbox_new (FALSE, 12);
g_object_add_weak_pointer (G_OBJECT (priv->vbox), ((gpointer *)&priv->vbox));
gtk_container_add (GTK_CONTAINER (eb), priv->vbox);
gtk_widget_show (priv->vbox);
priv->hbox = gtk_hbox_new (FALSE, 6);
g_object_add_weak_pointer (G_OBJECT (priv->hbox), ((gpointer *)&priv->hbox));
gtk_box_pack_start (GTK_BOX (priv->vbox), priv->hbox, FALSE, TRUE, 0);
gtk_widget_show (priv->hbox);
}
static void
insert_label (GtkWidget *hbox)
{
GtkWidget *label;
char *text;
text = g_strdup_printf ("<b>%s</b>", _("Choose an action:"));
label = gtk_label_new (NULL);
gtk_label_set_markup (GTK_LABEL (label), text);
g_free (text);
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
gtk_widget_show (label);
}
static void
rsvp_clicked_cb (GtkWidget *widget, gpointer data)
{
EItipControl *itip = E_ITIP_CONTROL (data);
EItipControlPrivate *priv;
priv = itip->priv;
priv->rsvp = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget));
}
static void
insert_rsvp (GtkWidget *hbox, EItipControl *itip)
{
EItipControlPrivate *priv;
GtkWidget *btn;
priv = itip->priv;
btn = gtk_check_button_new_with_label ("RSVP");
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (btn), TRUE);
priv->rsvp = TRUE;
g_signal_connect (btn, "clicked", G_CALLBACK (rsvp_clicked_cb), itip);
gtk_box_pack_start (GTK_BOX (hbox), btn, FALSE, TRUE, 0);
gtk_widget_show (btn);
}
static void
insert_ok (GtkWidget *hbox, EItipControl *itip)
{
EItipControlPrivate *priv;
priv = itip->priv;
priv->ok = gtk_button_new_from_stock (GTK_STOCK_OK);
g_object_add_weak_pointer (G_OBJECT (priv->ok), ((gpointer *)&priv->ok));
g_signal_connect (priv->ok, "clicked", G_CALLBACK (ok_clicked_cb), itip);
set_ok_sens (itip);
gtk_box_pack_start (GTK_BOX (hbox), priv->ok, FALSE, TRUE, 0);
gtk_widget_show (priv->ok);
}
static gboolean
publish_options_object (EItipControl *itip, GtkHTML *html, GtkHTMLEmbedded *eb)
{
EItipControlPrivate *priv;
GtkWidget *option, *menu;
priv = itip->priv;
insert_boxes (eb, itip);
insert_label (priv->hbox);
option = gtk_option_menu_new ();
menu = gtk_menu_new ();
add_option (itip, menu, _("Update"), 'U');
priv->action = 'U';
gtk_option_menu_set_menu (GTK_OPTION_MENU (option), menu);
gtk_box_pack_start (GTK_BOX (priv->hbox), option, FALSE, TRUE, 0);
gtk_widget_show (option);
insert_ok (priv->hbox, itip);
return TRUE;
}
static gboolean
request_options_object (EItipControl *itip, GtkHTML *html, GtkHTMLEmbedded *eb)
{
EItipControlPrivate *priv;
GtkWidget *option, *menu;
priv = itip->priv;
insert_boxes (eb, itip);
insert_label (priv->hbox);
option = gtk_option_menu_new ();
menu = gtk_menu_new ();
add_option (itip, menu, _("Accept"), 'A');
add_option (itip, menu, _("Tentatively accept"), 'T');
add_option (itip, menu, _("Decline"), 'D');
priv->action = 'A';
gtk_option_menu_set_menu (GTK_OPTION_MENU (option), menu);
gtk_box_pack_start (GTK_BOX (priv->hbox), option, FALSE, TRUE, 0);
gtk_widget_show (option);
insert_rsvp (priv->hbox, itip);
insert_ok (priv->hbox, itip);
return TRUE;
}
static gboolean
freebusy_options_object (EItipControl *itip, GtkHTML *html, GtkHTMLEmbedded *eb)
{
EItipControlPrivate *priv;
GtkWidget *option, *menu;
priv = itip->priv;
insert_boxes (eb, itip);
insert_label (priv->hbox);
option = gtk_option_menu_new ();
menu = gtk_menu_new ();
add_option (itip, menu, _("Send Free/Busy Information"), 'F');
priv->action = 'F';
gtk_option_menu_set_menu (GTK_OPTION_MENU (option), menu);
gtk_container_add (GTK_CONTAINER (priv->hbox), option);
gtk_widget_show (option);
insert_ok (priv->hbox, itip);
return TRUE;
}
static gboolean
reply_options_object (EItipControl *itip, GtkHTML *html, GtkHTMLEmbedded *eb)
{
EItipControlPrivate *priv;
GtkWidget *option, *menu;
priv = itip->priv;
insert_boxes (eb, itip);
insert_label (priv->hbox);
option = gtk_option_menu_new ();
menu = gtk_menu_new ();
add_option (itip, menu, _("Update respondent status"), 'R');
priv->action = 'R';
gtk_option_menu_set_menu (GTK_OPTION_MENU (option), menu);
gtk_container_add (GTK_CONTAINER (priv->hbox), option);
gtk_widget_show (option);
insert_ok (priv->hbox, itip);
return TRUE;
}
static gboolean
refresh_options_object (EItipControl *itip, GtkHTML *html, GtkHTMLEmbedded *eb)
{
EItipControlPrivate *priv;
GtkWidget *option, *menu;
priv = itip->priv;
insert_boxes (eb, itip);
insert_label (priv->hbox);
option = gtk_option_menu_new ();
menu = gtk_menu_new ();
add_option (itip, menu, _("Send Latest Information"), 'S');
priv->action = 'R';
gtk_option_menu_set_menu (GTK_OPTION_MENU (option), menu);
gtk_container_add (GTK_CONTAINER (priv->hbox), option);
gtk_widget_show (option);
insert_ok (priv->hbox, itip);
return TRUE;
}
static gboolean
cancel_options_object (EItipControl *itip, GtkHTML *html, GtkHTMLEmbedded *eb)
{
EItipControlPrivate *priv;
GtkWidget *option, *menu;
priv = itip->priv;
insert_boxes (eb, itip);
insert_label (priv->hbox);
option = gtk_option_menu_new ();
menu = gtk_menu_new ();
add_option (itip, menu, _("Cancel"), 'C');
priv->action = 'C';
gtk_option_menu_set_menu (GTK_OPTION_MENU (option), menu);
gtk_container_add (GTK_CONTAINER (priv->hbox), option);
gtk_widget_show (option);
insert_ok (priv->hbox, itip);
return TRUE;
}
static gboolean
object_requested_cb (GtkHTML *html, GtkHTMLEmbedded *eb, gpointer data)
{
EItipControl *itip = E_ITIP_CONTROL (data);
if (!strcmp (eb->classid, "itip:publish_options"))
return publish_options_object (itip, html, eb);
else if (!strcmp (eb->classid, "itip:request_options"))
return request_options_object (itip, html, eb);
else if (!strcmp (eb->classid, "itip:freebusy_options"))
return freebusy_options_object (itip, html, eb);
else if (!strcmp (eb->classid, "itip:reply_options"))
return reply_options_object (itip, html, eb);
else if (!strcmp (eb->classid, "itip:refresh_options"))
return refresh_options_object (itip, html, eb);
else if (!strcmp (eb->classid, "itip:cancel_options"))
return cancel_options_object (itip, html, eb);
return FALSE;
}
static void
ok_clicked_cb (GtkWidget *widget, gpointer data)
{
EItipControl *itip = E_ITIP_CONTROL (data);
EItipControlPrivate *priv;
gboolean status = FALSE;
priv = itip->priv;
if (!priv->my_address && priv->current_ecal != NULL)
e_cal_get_cal_address (priv->current_ecal, &priv->my_address, NULL);
switch (priv->action) {
case 'U':
update_item (itip);
break;
case 'A':
status = change_status (priv->ical_comp, priv->my_address,
ICAL_PARTSTAT_ACCEPTED);
if (status) {
e_cal_component_rescan (priv->comp);
update_item (itip);
}
break;
case 'T':
status = change_status (priv->ical_comp, priv->my_address,
ICAL_PARTSTAT_TENTATIVE);
if (status) {
e_cal_component_rescan (priv->comp);
update_item (itip);
}
break;
case 'D':
status = change_status (priv->ical_comp, priv->my_address,
ICAL_PARTSTAT_DECLINED);
if (status) {
e_cal_component_rescan (priv->comp);
update_item (itip);
}
break;
case 'F':
send_freebusy (itip);
break;
case 'R':
update_attendee_status (itip);
break;
case 'S':
send_item (itip);
break;
case 'C':
update_item (itip);
break;
}
if (e_cal_get_save_schedules (priv->current_ecal))
return;
if (priv->rsvp && 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 (priv->comp);
if (comp == NULL)
return;
vtype = e_cal_component_get_vtype (comp);
if (priv->my_address == NULL)
find_my_address (itip, priv->ical_comp, NULL);
g_assert (priv->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_ascii_strcasecmp (priv->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, priv->current_ecal, priv->top_level, NULL);
g_object_unref (comp);
}
}