/* Evolution calendar - Meeting editor dialog * * Copyright (C) 2000 Helix Code, Inc. * * Authors: Jesse Pavel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ #include #include #include #include #include #include "e-meeting-edit.h" #define E_MEETING_GLADE_XML "e-meeting-dialog.glade" #define E_MEETING_DEBUG typedef struct _EMeetingEditorPrivate EMeetingEditorPrivate; struct _EMeetingEditorPrivate { /* These are the widgets to be used in the GUI. */ GladeXML *xml; GtkWidget *meeting_window; GtkWidget *attendee_list; GtkWidget *address_entry; GtkWidget *edit_dialog; GtkWidget *organizer_entry; GtkWidget *role_entry; GtkWidget *rsvp_check; gint changed_signal_id; /* Various pieces of information. */ gint selected_row; CalComponent *comp; CalClient *client; icalcomponent *icalcomp, *vevent; gint numentries; /* How many attendees are there? */ gboolean dirty; /* Has anything changed? */ }; #define NUM_COLUMNS 4 /* The number of columns in our attendee list. */ enum column_names {ADDRESS_COL, ROLE_COL, RSVP_COL, STATUS_COL}; static gchar *partstat_values[] = { "Needs action", "Accepted", "Declined", "Tentative", "Delegated", "Completed", "In Progress", "Unknown" }; static gchar *role_values[] = { "Chair", "Required Participant", "Optional Participant", "Non-Participant", "Other" }; /* Note that I have to iterate and check myself because ical_property_get_xxx_parameter doesn't take into account the kind of parameter for which you wish to search! */ static icalparameter * get_icalparam_by_type (icalproperty *prop, icalparameter_kind kind) { icalparameter *param; for (param = icalproperty_get_first_parameter (prop, ICAL_ANY_PARAMETER); param != NULL && icalparameter_isa (param) != kind; param = icalproperty_get_next_parameter (prop, ICAL_ANY_PARAMETER) ); return param; } static gboolean window_delete_cb (GtkWidget *widget, GdkEvent *event, gpointer data) { EMeetingEditorPrivate *priv; gchar *text; priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; #ifdef E_MEETING_DEBUG g_printerr ("e-meeting-edit.c: The main window received a delete event.\n"); #endif if (priv->dirty == TRUE) { /* FIXME: notify the event editor that our data has changed. For now, I'll just display a dialog box. */ { GtkWidget *dialog; icalproperty *prop; icalvalue *value; /* Save the organizer into the iCAL object. */ prop = icalcomponent_get_first_property (priv->vevent, ICAL_ORGANIZER_PROPERTY); text = gtk_entry_get_text (GTK_ENTRY (priv->organizer_entry)); #ifdef E_MEETING_DEBUG g_print ("e-meeting-edit.c: The organizer entry is %s.\n", text); #endif if (strlen (text) > 0) { gchar buffer[200]; g_snprintf (buffer, 190, "MAILTO:%s", text); if (prop == NULL) { /* We need to add an ORGANIZER property. */ prop = icalproperty_new (ICAL_ORGANIZER_PROPERTY); icalcomponent_add_property (priv->vevent, prop); } value = icalvalue_new_text (buffer); icalproperty_set_value (prop, value); } dialog = gnome_warning_dialog_parented ("Note that the meeting has changed,\n" "and you should save this event.", GTK_WINDOW (priv->meeting_window)); gnome_dialog_run (GNOME_DIALOG(dialog)); } } gtk_entry_set_text (GTK_ENTRY (priv->organizer_entry), ""); return (FALSE); } static void window_destroy_cb (GtkWidget *widget, gpointer data) { EMeetingEditorPrivate *priv; #ifdef E_MEETING_DEBUG g_printerr ("e-meeting-edit.c: The main window received a destroy event.\n"); #endif priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; gtk_main_quit (); return; } /* put_property_in_list() synchronizes the display of row `rownum' in our attendee list to the values of `prop'. If rownum < 0, then put_property_in_list() will append a new row. If the property doesn't contain certain parameters that we deem necessary, it will add them. */ static void put_property_in_list (icalproperty *prop, gint rownum, gpointer data) { gchar *row_text[NUM_COLUMNS]; gchar *text, *new_text; icalparameter *param; icalvalue *value; gint enumval; gint cntr; EMeetingEditorPrivate *priv; priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; value = icalproperty_get_value (prop); if (value != NULL) { text = strdup (icalvalue_as_ical_string (value)); /* Here I strip off the "MAILTO:" if it is present. */ new_text = strchr (text, ':'); if (new_text != NULL) new_text++; else new_text = text; row_text[ADDRESS_COL] = g_strdup (new_text); g_free (text); } param = get_icalparam_by_type (prop, ICAL_ROLE_PARAMETER); if (param == NULL) { #ifdef E_MEETING_DEBUG g_print ("e-meeting-edit.c: within put_param...(), param is NULL.\n"); #endif param = icalparameter_new_role (ICAL_ROLE_REQPARTICIPANT); icalproperty_add_parameter (prop, param); } enumval = icalparameter_get_role (param); if (enumval < 0 || enumval > 4) enumval = 4; #ifdef E_MEETING_DEBUG g_print ("e-meeting-edit.c: the role value is %d.\n", enumval); #endif row_text[ROLE_COL] = role_values [enumval]; param = get_icalparam_by_type (prop, ICAL_RSVP_PARAMETER); if (param == NULL) { param = icalparameter_new_rsvp (TRUE); icalproperty_add_parameter (prop, param); } if (icalparameter_get_rsvp (param)) row_text[RSVP_COL] = "Y"; else row_text[RSVP_COL] = "N"; #ifdef E_MEETING_DEBUG g_print ("e-meeting-edit.c: the RSVP is %c.\n", row_text[RSVP_COL][0]); #endif param = get_icalparam_by_type (prop, ICAL_PARTSTAT_PARAMETER); if (param == NULL) { param = icalparameter_new_partstat (ICAL_PARTSTAT_NEEDSACTION); icalproperty_add_parameter (prop, param); } enumval = icalparameter_get_partstat (param); if (enumval < 0 || enumval > 7) { enumval = 7; } row_text[STATUS_COL] = partstat_values [enumval]; if (rownum < 0) { gtk_clist_append (GTK_CLIST (priv->attendee_list), row_text); gtk_clist_set_row_data (GTK_CLIST (priv->attendee_list), priv->numentries, prop); priv->numentries++; } else { for (cntr = 0; cntr < NUM_COLUMNS; cntr++) { gtk_clist_set_text (GTK_CLIST (priv->attendee_list), rownum, cntr, row_text[cntr]); } } g_free (row_text[ADDRESS_COL]); } /* edit_attendee() performs the GUI manipulation and interaction for editing `prop' and returns TRUE if the user indicated that he wants to save the new property information. Note that it is necessary that the property have parameters of the types RSVP, PARTSTAT, and ROLE already when passed into this function. */ static gboolean edit_attendee (icalproperty *prop, gpointer data) { EMeetingEditorPrivate *priv; gint button_num; gchar *new_text, *text; icalparameter *param; icalvalue *value; gchar buffer[200]; gint cntr; gint enumval; gboolean retval; priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; g_return_val_if_fail (prop != NULL, FALSE); if (priv->edit_dialog == NULL || priv->address_entry == NULL) { priv->edit_dialog = glade_xml_get_widget (priv->xml, "edit_dialog"); priv->address_entry = glade_xml_get_widget (priv->xml, "address_entry"); gnome_dialog_set_close (GNOME_DIALOG (priv->edit_dialog), TRUE); gnome_dialog_editable_enters (GNOME_DIALOG (priv->edit_dialog), GTK_EDITABLE (priv->address_entry)); gnome_dialog_close_hides (GNOME_DIALOG (priv->edit_dialog), TRUE); gnome_dialog_set_default (GNOME_DIALOG (priv->edit_dialog), 0); } g_return_val_if_fail (priv->edit_dialog != NULL, FALSE); g_return_val_if_fail (priv->address_entry != NULL, FALSE); gtk_widget_realize (priv->edit_dialog); value = icalproperty_get_value (prop); if (value != NULL) { text = strdup (icalvalue_as_ical_string (value)); /* Here I strip off the "MAILTO:" if it is present. */ new_text = strchr (text, ':'); if (new_text != NULL) new_text++; else new_text = text; gtk_entry_set_text (GTK_ENTRY (priv->address_entry), new_text); g_free (text); } else { gtk_entry_set_text (GTK_ENTRY (priv->address_entry), ""); } param = get_icalparam_by_type (prop, ICAL_ROLE_PARAMETER); enumval = icalparameter_get_role (param); if (enumval < 0 || enumval > 4) enumval = 4; text = role_values [enumval]; gtk_entry_set_text (GTK_ENTRY (priv->role_entry), text); param = get_icalparam_by_type (prop, ICAL_RSVP_PARAMETER); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->rsvp_check), icalparameter_get_rsvp (param)); gtk_widget_show (priv->edit_dialog); button_num = gnome_dialog_run (GNOME_DIALOG (priv->edit_dialog)); if (button_num == 0) { /* The user pressed the OK button. */ new_text = gtk_entry_get_text (GTK_ENTRY (priv->address_entry)); g_snprintf (buffer, 190, "MAILTO:%s", new_text); value = icalvalue_new_text (buffer); icalproperty_set_value (prop, value); /* Take care of the ROLE. */ icalproperty_remove_parameter (prop, ICAL_ROLE_PARAMETER); param = NULL; text = gtk_entry_get_text (GTK_ENTRY(priv->role_entry)); #ifdef E_MEETING_DEBUG g_print ("e-meeting-edit.c: the role entry text is %s.\n", text); #endif for (cntr = 0; cntr < 5; cntr++) { if (strncmp (text, role_values[cntr], 3) == 0) { param = icalparameter_new_role (cntr); break; } } if (param == NULL) { g_print ("e-meeting-edit.c: edit_attendee() the ROLE param was null.\n"); /* Use this as a default case, if none of the others match. */ param = icalparameter_new_role (ICAL_ROLE_REQPARTICIPANT); } icalproperty_add_parameter (prop, param); /* Now the RSVP. */ icalproperty_remove_parameter (prop, ICAL_RSVP_PARAMETER); param = icalparameter_new_rsvp (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->rsvp_check))); icalproperty_add_parameter (prop, param); retval = TRUE; } else /* The user didn't say OK. */ retval = FALSE; return retval; } static void add_button_clicked_cb (GtkWidget *widget, gpointer data) { EMeetingEditorPrivate *priv; icalproperty *prop; icalparameter *param; #ifdef E_MEETING_DEBUG g_printerr ("e-meeting-edit.c: the add button was clicked.\n"); #endif priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; prop = icalproperty_new (ICAL_ATTENDEE_PROPERTY); param = icalparameter_new_role (ICAL_ROLE_REQPARTICIPANT); icalproperty_add_parameter (prop, param); param = icalparameter_new_rsvp (TRUE); icalproperty_add_parameter (prop, param); param = icalparameter_new_partstat (ICAL_PARTSTAT_NEEDSACTION); icalproperty_add_parameter (prop, param); if (edit_attendee (prop, data) == TRUE) { #ifdef E_MEETING_DEBUG g_print ("e-meeting-edit.c: After edit_attendee()"); #endif /* Let's add this property to our component and to the CList. */ icalcomponent_add_property (priv->vevent, prop); /* The -1 indicates that we should add a new row. */ put_property_in_list (prop, -1, data); #ifdef E_MEETING_DEBUG g_print ("e-meeting-edit.c: After put_property_in_list()"); #endif priv->dirty = TRUE; } else { icalproperty_free (prop); } } static void delete_button_clicked_cb (GtkWidget *widget, gpointer data) { EMeetingEditorPrivate *priv; priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; if (priv->selected_row < 0) { GtkWidget *dialog; dialog = gnome_warning_dialog_parented ("You must select an entry to delete.", GTK_WINDOW (priv->meeting_window)); gnome_dialog_run (GNOME_DIALOG(dialog)); } else { /* Delete the associated property from the iCAL object. */ icalproperty *prop; prop = (icalproperty *)gtk_clist_get_row_data (GTK_CLIST (priv->attendee_list), priv->selected_row); icalcomponent_remove_property (priv->vevent, prop); icalproperty_free (prop); gtk_clist_remove (GTK_CLIST (priv->attendee_list), priv->selected_row); priv->selected_row = -1; priv->numentries--; priv->dirty = TRUE; } } static void edit_button_clicked_cb (GtkWidget *widget, gpointer data) { EMeetingEditorPrivate *priv; priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; if (priv->selected_row < 0) { GtkWidget *dialog; dialog = gnome_warning_dialog_parented ("You must select an entry to edit.", GTK_WINDOW (priv->meeting_window)); gnome_dialog_run (GNOME_DIALOG(dialog)); return; } else { icalproperty *prop, *new_prop; icalparameter *param, *new_param; icalvalue *value, *new_value; prop = (icalproperty *)gtk_clist_get_row_data (GTK_CLIST (priv->attendee_list), priv->selected_row); g_assert (prop != NULL); new_prop = icalproperty_new_clone (prop); if (edit_attendee (new_prop, data)) { /* The user hit Okay. */ /*We need to synchronize the old property with the newly edited one.*/ value = icalvalue_new_clone (icalproperty_get_value (new_prop)); icalproperty_set_value (prop, value); icalproperty_remove_parameter (prop, ICAL_ROLE_PARAMETER); icalproperty_remove_parameter (prop, ICAL_RSVP_PARAMETER); icalproperty_remove_parameter (prop, ICAL_PARTSTAT_PARAMETER); param = icalparameter_new_clone (get_icalparam_by_type (new_prop, ICAL_ROLE_PARAMETER)); g_assert (param != NULL); icalproperty_add_parameter (prop, param); param = icalparameter_new_clone (get_icalparam_by_type (new_prop, ICAL_RSVP_PARAMETER)); g_assert (param != NULL); icalproperty_add_parameter (prop, param); param = icalparameter_new_clone (get_icalparam_by_type (new_prop, ICAL_PARTSTAT_PARAMETER)); g_assert (param != NULL); icalproperty_add_parameter (prop, param); put_property_in_list (prop, priv->selected_row, data); priv->dirty = TRUE; } icalproperty_free (new_prop); } } static void list_row_select_cb (GtkWidget *widget, gint row, gint column, GdkEventButton *event, gpointer data) { EMeetingEditorPrivate *priv; priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; priv->selected_row = row; } static void organizer_changed_cb (GtkWidget *widget, gpointer data) { EMeetingEditorPrivate *priv; priv = (EMeetingEditorPrivate *) ((EMeetingEditor *)data)->priv; gtk_signal_disconnect (GTK_OBJECT (priv->organizer_entry), priv->changed_signal_id); priv->dirty = TRUE; } /* ------------------------------------------------------------ */ /* --------------------- Exported Functions ------------------- */ /* ------------------------------------------------------------ */ EMeetingEditor * e_meeting_editor_new (CalComponent *comp, CalClient *client) { EMeetingEditor *object; EMeetingEditorPrivate *priv; object = (EMeetingEditor *)g_new(EMeetingEditor, 1); priv = (EMeetingEditorPrivate *) g_new0(EMeetingEditorPrivate, 1); priv->selected_row = -1; priv->comp = comp; priv->client = client; priv->icalcomp = cal_component_get_icalcomponent (comp); object->priv = priv; return object; } void e_meeting_editor_free (EMeetingEditor *editor) { if (editor == NULL) return; if (editor->priv != NULL) g_free (editor->priv); g_free (editor); } void e_meeting_edit (EMeetingEditor *editor) { EMeetingEditorPrivate *priv; GtkWidget *add_button, *delete_button, *edit_button; icalproperty *prop; icalvalue *value; gchar *text; g_return_if_fail (editor != NULL); priv = (EMeetingEditorPrivate *)editor->priv; g_return_if_fail (priv != NULL); priv->xml = glade_xml_new (EVOLUTION_GLADEDIR "/" E_MEETING_GLADE_XML, NULL); priv->meeting_window = glade_xml_get_widget (priv->xml, "meeting_window"); priv->attendee_list = glade_xml_get_widget (priv->xml, "attendee_list"); priv->role_entry = glade_xml_get_widget (priv->xml, "role_entry"); priv->rsvp_check = glade_xml_get_widget (priv->xml, "rsvp_check"); gtk_clist_set_column_justification (GTK_CLIST (priv->attendee_list), ROLE_COL, GTK_JUSTIFY_CENTER); gtk_clist_set_column_justification (GTK_CLIST (priv->attendee_list), RSVP_COL, GTK_JUSTIFY_CENTER); gtk_clist_set_column_justification (GTK_CLIST (priv->attendee_list), STATUS_COL, GTK_JUSTIFY_CENTER); gtk_signal_connect (GTK_OBJECT (priv->meeting_window), "delete_event", GTK_SIGNAL_FUNC (window_delete_cb), editor); gtk_signal_connect_after (GTK_OBJECT (priv->meeting_window), "delete_event", GTK_SIGNAL_FUNC (window_destroy_cb), editor); gtk_signal_connect (GTK_OBJECT (priv->meeting_window), "destroy_event", GTK_SIGNAL_FUNC (window_destroy_cb), editor); gtk_signal_connect (GTK_OBJECT (priv->attendee_list), "select_row", GTK_SIGNAL_FUNC (list_row_select_cb), editor); add_button = glade_xml_get_widget (priv->xml, "add_button"); delete_button = glade_xml_get_widget (priv->xml, "delete_button"); edit_button = glade_xml_get_widget (priv->xml, "edit_button"); gtk_signal_connect (GTK_OBJECT (add_button), "clicked", GTK_SIGNAL_FUNC (add_button_clicked_cb), editor); gtk_signal_connect (GTK_OBJECT (delete_button), "clicked", GTK_SIGNAL_FUNC (delete_button_clicked_cb), editor); gtk_signal_connect (GTK_OBJECT (edit_button), "clicked", GTK_SIGNAL_FUNC (edit_button_clicked_cb), editor); priv->organizer_entry = glade_xml_get_widget (priv->xml, "organizer_entry"); if (icalcomponent_isa (priv->icalcomp) != ICAL_VEVENT_COMPONENT) priv->vevent = icalcomponent_get_first_component(priv->icalcomp,ICAL_VEVENT_COMPONENT); else priv->vevent = priv->icalcomp; g_assert (priv->vevent != NULL); /* Let's extract the organizer, if there is one. */ prop = icalcomponent_get_first_property (priv->vevent, ICAL_ORGANIZER_PROPERTY); if (prop != NULL) { gchar *buffer; #ifdef E_MEETING_DEBUG g_print ("e-meeting-edit.c: The organizer property is not null.\n"); #endif value = icalproperty_get_value (prop); buffer = g_strdup (icalvalue_as_ical_string (value)); if (buffer != NULL) { /* Strip off the MAILTO:, if it is present. */ text = strchr (buffer, ':'); if (text == NULL) text = buffer; else text++; gtk_entry_set_text (GTK_ENTRY (priv->organizer_entry), text); g_free (buffer); } } #ifdef E_MEETING_DEBUG else { g_print ("e-meeting-edit.c: the organizer property was NULL.\n"); } #endif priv->changed_signal_id = gtk_signal_connect (GTK_OBJECT (priv->organizer_entry), "changed", GTK_SIGNAL_FUNC (organizer_changed_cb), editor); /* Let's go through the iCAL object, and create a list entry for each ATTENDEE property. */ for (prop = icalcomponent_get_first_property (priv->vevent, ICAL_ATTENDEE_PROPERTY); prop != NULL; prop = icalcomponent_get_next_property (priv->vevent, ICAL_ATTENDEE_PROPERTY)) { put_property_in_list (prop, -1, editor); } gtk_widget_show (priv->meeting_window); gtk_main (); #ifdef E_MEETING_DEBUG g_printerr ("e-meeting-edit.c: We've terminated the subsidiary gtk_main().\n"); #endif if (priv->meeting_window != NULL) gtk_widget_destroy (priv->meeting_window); if (priv->edit_dialog != NULL) gtk_widget_destroy (priv->edit_dialog); gtk_object_unref (GTK_OBJECT (priv->xml)); }