/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ /* * Author : * Damon Chaplin * * Copyright 2000, Helix Code, Inc. * Copyright 2000, Ximian, Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA */ /* * TaskEditor - a GtkObject which handles a libglade-loaded dialog to edit * tasks. */ #include #include #include #include #include #include #include #include #include #include #include "delete-comp.h" #include "save-comp.h" #include "task-editor.h" #include "../calendar-config.h" #include "../widget-util.h" typedef struct { /* Glade XML data */ GladeXML *xml; /* Client to use */ CalClient *client; /* Calendar component we are editing; this is an internal copy and is * not one of the read-only objects from the parent calendar. */ CalComponent *comp; /* This is TRUE while we are setting the widget values. We just return from any signal handlers. */ gboolean ignore_callbacks; /* Widgets from the Glade file */ GtkWidget *app; GtkWidget *summary; GtkWidget *due_date; GtkWidget *start_date; GtkWidget *percent_complete; GtkWidget *status; GtkWidget *priority; GtkWidget *description; GtkWidget *classification_public; GtkWidget *classification_private; GtkWidget *classification_confidential; GtkWidget *contacts_btn; GtkWidget *contacts; GtkWidget *categories_btn; GtkWidget *categories; GtkWidget *completed_date; GtkWidget *url; /* Call task_editor_set_changed() to set this to TRUE when any field in the dialog is changed. When the user closes the dialog we will prompt to save changes. */ gboolean changed; } TaskEditorPrivate; /* Note that these two arrays must match. */ static const int status_map[] = { ICAL_STATUS_NEEDSACTION, ICAL_STATUS_INPROCESS, ICAL_STATUS_COMPLETED, ICAL_STATUS_CANCELLED, -1 }; typedef enum { PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_LOW, PRIORITY_UNDEFINED, } TaskEditorPriority; static const int priority_map[] = { PRIORITY_HIGH, PRIORITY_NORMAL, PRIORITY_LOW, PRIORITY_UNDEFINED, -1 }; static const int classification_map[] = { CAL_COMPONENT_CLASS_PUBLIC, CAL_COMPONENT_CLASS_PRIVATE, CAL_COMPONENT_CLASS_CONFIDENTIAL, -1 }; static void task_editor_class_init (TaskEditorClass *class); static void task_editor_init (TaskEditor *tedit); static void tedit_apply_event_cb (GtkWidget *widget, gint page_num, gpointer data); static gint tedit_close_event_cb (GtkWidget *widget, gpointer data); static gint tedit_delete_event_cb (GtkWidget *widget, GdkEvent *event, gpointer data); static void close_dialog (TaskEditor *tedit); static gboolean get_widgets (TaskEditor *tedit); static void init_widgets (TaskEditor *tedit); static void task_editor_destroy (GtkObject *object); static char * make_title_from_comp (CalComponent *comp); static void set_title_from_comp (TaskEditor *tedit, CalComponent *comp); static void clear_widgets (TaskEditor *tedit); static void fill_widgets (TaskEditor *tedit); static void save_todo_object (TaskEditor *tedit); static void dialog_to_comp_object (TaskEditor *tedit); static void obj_updated_cb (CalClient *client, const char *uid, gpointer data); static void obj_removed_cb (CalClient *client, const char *uid, gpointer data); static void raise_and_focus (GtkWidget *widget); static TaskEditorPriority priority_value_to_index (int priority_value); static int priority_index_to_value (TaskEditorPriority priority); static void completed_changed (EDateEdit *dedit, TaskEditor *tedit); static void status_changed (GtkMenu *menu, TaskEditor *tedit); static void percent_complete_changed (GtkAdjustment *adj, TaskEditor *tedit); static void field_changed (GtkWidget *widget, TaskEditor *tedit); static void task_editor_set_changed (TaskEditor *tedit, gboolean changed); static gboolean prompt_to_save_changes (TaskEditor *tedit); static CalComponentClassification classification_get (GtkWidget *widget); static void categories_clicked (GtkWidget *button, TaskEditor *editor); /* The function libglade calls to create the EDateEdit widgets in the GUI. */ GtkWidget * task_editor_create_date_edit (void); static GtkObjectClass *parent_class; E_MAKE_TYPE(task_editor, "TaskEditor", TaskEditor, task_editor_class_init, task_editor_init, GTK_TYPE_OBJECT) static void task_editor_class_init (TaskEditorClass *class) { GtkObjectClass *object_class; object_class = (GtkObjectClass *) class; parent_class = gtk_type_class (GTK_TYPE_OBJECT); object_class->destroy = task_editor_destroy; } static void task_editor_init (TaskEditor *tedit) { TaskEditorPrivate *priv; priv = g_new0 (TaskEditorPrivate, 1); tedit->priv = priv; priv->ignore_callbacks = FALSE; task_editor_set_changed (tedit, FALSE); } /** * task_editor_new: * @Returns: a new #TaskEditor. * * Creates a new #TaskEditor. **/ TaskEditor * task_editor_new (void) { TaskEditor *tedit; tedit = TASK_EDITOR (gtk_type_new (task_editor_get_type ())); return task_editor_construct (tedit); } /** * task_editor_construct: * @tedit: A #TaskEditor. * * Constructs a task editor by loading its Glade XML file. * * Return value: The same object as @tedit, or NULL if the widgets could not be * created. In the latter case, the task editor will automatically be * destroyed. **/ TaskEditor * task_editor_construct (TaskEditor *tedit) { TaskEditorPrivate *priv; g_return_val_if_fail (tedit != NULL, NULL); g_return_val_if_fail (IS_TASK_EDITOR (tedit), NULL); priv = tedit->priv; /* Load the content widgets */ priv->xml = glade_xml_new (EVOLUTION_GLADEDIR "/task-editor-dialog.glade", NULL); if (!priv->xml) { g_message ("task_editor_construct(): Could not load the Glade XML file!"); goto error; } if (!get_widgets (tedit)) { g_message ("task_editor_construct(): Could not find all widgets in the XML file!"); goto error; } init_widgets (tedit); /* Hook to destruction of the dialog */ gtk_signal_connect (GTK_OBJECT (priv->app), "apply", GTK_SIGNAL_FUNC (tedit_apply_event_cb), tedit); gtk_signal_connect (GTK_OBJECT (priv->app), "close", GTK_SIGNAL_FUNC (tedit_close_event_cb), tedit); gtk_signal_connect (GTK_OBJECT (priv->app), "delete_event", GTK_SIGNAL_FUNC (tedit_delete_event_cb), tedit); /* Add focus to the summary entry */ gtk_widget_grab_focus (GTK_WIDGET (priv->summary)); return tedit; error: gtk_object_unref (GTK_OBJECT (tedit)); return NULL; } /* Called by libglade to create our custom EDateEdit widgets. */ GtkWidget * task_editor_create_date_edit (void) { GtkWidget *dedit; dedit = date_edit_new (TRUE, TRUE); e_date_edit_set_allow_no_date_set (E_DATE_EDIT (dedit), TRUE); return dedit; } /* Callback used when the dialog box is destroyed */ static void tedit_apply_event_cb (GtkWidget *widget, gint page_num, gpointer data) { TaskEditor *tedit; g_return_if_fail (IS_TASK_EDITOR (data)); tedit = TASK_EDITOR (data); if (page_num != -1) return; save_todo_object (tedit); } /* Callback used when the dialog box is destroyed */ static gint tedit_close_event_cb (GtkWidget *widget, gpointer data) { TaskEditor *tedit; g_return_val_if_fail (IS_TASK_EDITOR (data), TRUE); tedit = TASK_EDITOR (data); if (prompt_to_save_changes (tedit)) close_dialog (tedit); return TRUE; } /* Callback used when the dialog box is destroyed */ static gint tedit_delete_event_cb (GtkWidget *widget, GdkEvent *event, gpointer data) { TaskEditor *tedit; g_return_val_if_fail (IS_TASK_EDITOR (data), TRUE); tedit = TASK_EDITOR (data); if (prompt_to_save_changes (tedit)) close_dialog (tedit); return TRUE; } /* Closes the dialog box and emits the appropriate signals */ static void close_dialog (TaskEditor *tedit) { TaskEditorPrivate *priv; priv = tedit->priv; g_assert (priv->app != NULL); gtk_object_destroy (GTK_OBJECT (tedit)); } /* Gets the widgets from the XML file and returns if they are all available. * For the widgets whose values can be simply set with e-dialog-utils, it does * that as well. */ static gboolean get_widgets (TaskEditor *tedit) { TaskEditorPrivate *priv; priv = tedit->priv; #define GW(name) glade_xml_get_widget (priv->xml, name) priv->app = GW ("task-editor-dialog"); priv->summary = GW ("summary"); priv->due_date = GW ("due-date"); priv->start_date = GW ("start-date"); priv->percent_complete = GW ("percent-complete"); priv->status = GW ("status"); priv->priority = GW ("priority"); priv->description = GW ("description"); priv->classification_public = GW ("classification-public"); priv->classification_private = GW ("classification-private"); priv->classification_confidential = GW ("classification-confidential"); priv->contacts_btn = GW ("contacts-button"); priv->contacts = GW ("contacts"); priv->categories_btn = GW ("categories-button"); priv->categories = GW ("categories"); priv->completed_date = GW ("completed-date"); priv->url = GW ("url"); #undef GW return (priv->app && priv->summary && priv->due_date && priv->start_date && priv->percent_complete && priv->status && priv->priority && priv->classification_public && priv->classification_private && priv->classification_confidential && priv->description && priv->contacts_btn && priv->contacts && priv->categories_btn && priv->categories && priv->completed_date && priv->url); } /* Hooks the widget signals */ static void init_widgets (TaskEditor *tedit) { TaskEditorPrivate *priv; priv = tedit->priv; /* Connect signals. The Status, Percent Complete & Date Completed properties are closely related so whenever one changes we may need to update the other 2. */ gtk_signal_connect (GTK_OBJECT (priv->completed_date), "changed", GTK_SIGNAL_FUNC (completed_changed), tedit); gtk_signal_connect (GTK_OBJECT (GTK_OPTION_MENU (priv->status)->menu), "deactivate", GTK_SIGNAL_FUNC (status_changed), tedit); gtk_signal_connect (GTK_OBJECT (GTK_SPIN_BUTTON (priv->percent_complete)->adjustment), "value_changed", GTK_SIGNAL_FUNC (percent_complete_changed), tedit); /* Classification */ gtk_signal_connect (GTK_OBJECT (priv->description), "changed", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->classification_public), "toggled", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->classification_private), "toggled", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->classification_confidential), "toggled", GTK_SIGNAL_FUNC (field_changed), tedit); /* Connect the default signal handler to use to make sure the "changed" field gets set whenever a field is changed. */ gtk_signal_connect (GTK_OBJECT (priv->summary), "changed", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->due_date), "changed", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->start_date), "changed", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (GTK_OPTION_MENU (priv->priority)->menu), "deactivate", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->description), "changed", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->contacts), "changed", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->categories), "changed", GTK_SIGNAL_FUNC (field_changed), tedit); gtk_signal_connect (GTK_OBJECT (priv->url), "changed", GTK_SIGNAL_FUNC (field_changed), tedit); /* Button clicks */ gtk_signal_connect (GTK_OBJECT (priv->categories_btn), "clicked", GTK_SIGNAL_FUNC (categories_clicked), tedit); /* FIXME: we do not support these fields yet, so we disable them */ gtk_widget_set_sensitive (priv->contacts_btn, FALSE); gtk_widget_set_sensitive (priv->contacts, FALSE); } static void task_editor_destroy (GtkObject *object) { TaskEditor *tedit; TaskEditorPrivate *priv; g_return_if_fail (object != NULL); g_return_if_fail (IS_TASK_EDITOR (object)); tedit = TASK_EDITOR (object); priv = tedit->priv; if (priv->app) { gtk_widget_destroy (priv->app); priv->app = NULL; } if (priv->comp) { gtk_object_unref (GTK_OBJECT (priv->comp)); priv->comp = NULL; } if (priv->client) { gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), tedit); gtk_object_unref (GTK_OBJECT (priv->client)); priv->client = NULL; } if (priv->xml) { gtk_object_unref (GTK_OBJECT (priv->xml)); priv->xml = NULL; } g_free (priv); tedit->priv = NULL; if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } void task_editor_set_cal_client (TaskEditor *tedit, CalClient *client) { TaskEditorPrivate *priv; g_return_if_fail (tedit != NULL); g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; if (client == priv->client) return; if (client) g_return_if_fail (IS_CAL_CLIENT (client)); if (client) g_return_if_fail (cal_client_get_load_state (client) == CAL_CLIENT_LOAD_LOADED); if (client) gtk_object_ref (GTK_OBJECT (client)); if (priv->client) { gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), tedit); gtk_object_unref (GTK_OBJECT (priv->client)); } priv->client = client; if (priv->client) { gtk_signal_connect (GTK_OBJECT (priv->client), "obj_updated", GTK_SIGNAL_FUNC (obj_updated_cb), tedit); gtk_signal_connect (GTK_OBJECT (priv->client), "obj_removed", GTK_SIGNAL_FUNC (obj_removed_cb), tedit); } } /* Callback used when the calendar client tells us that an object changed */ static void obj_updated_cb (CalClient *client, const char *uid, gpointer data) { /* FIXME: Do something sensible if the component changes under our feet */ #if 0 TaskEditor *tedit; TaskEditorPrivate *priv; CalComponent *comp; CalClientGetStatus status; const gchar *editing_uid; tedit = TASK_EDITOR (data); g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; /* If we aren't showing the object which has been updated, return. */ if (!priv->comp) return; cal_component_get_uid (priv->comp, &editing_uid); if (strcmp (uid, editing_uid)) return; /* Get the task from the server. */ status = cal_client_get_object (priv->client, uid, &comp); switch (status) { case CAL_CLIENT_GET_SUCCESS: /* Everything is fine */ break; case CAL_CLIENT_GET_SYNTAX_ERROR: g_message ("obj_updated_cb(): Syntax error when getting object `%s'", uid); return; case CAL_CLIENT_GET_NOT_FOUND: /* The object is no longer in the server, so do nothing */ return; default: g_assert_not_reached (); return; } raise_and_focus (priv->app); #endif } /* Callback used when the calendar client tells us that an object was removed */ static void obj_removed_cb (CalClient *client, const char *uid, gpointer data) { /* FIXME: Do something sensible if the component is removed under our * feet. */ #if 0 TaskEditor *tedit; TaskEditorPrivate *priv; const gchar *editing_uid; tedit = TASK_EDITOR (data); g_return_if_fail (tedit != NULL); g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; /* If we aren't showing the object which has been updated, return. */ if (!priv->comp) return; cal_component_get_uid (priv->comp, &editing_uid); if (strcmp (uid, editing_uid)) return; raise_and_focus (priv->app); #endif } /* Brings attention to a window by raising it and giving it focus */ static void raise_and_focus (GtkWidget *widget) { g_assert (GTK_WIDGET_REALIZED (widget)); gdk_window_show (widget->window); gtk_widget_grab_focus (widget); } /** * task_editor_set_todo_object: * @tedit: A #TaskEditor. * @comp: A todo object. * * Sets the todo object that a task editor dialog will manipulate. **/ void task_editor_set_todo_object (TaskEditor *tedit, CalComponent *comp) { TaskEditorPrivate *priv; g_return_if_fail (tedit != NULL); g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; if (priv->comp) { gtk_object_unref (GTK_OBJECT (priv->comp)); priv->comp = NULL; } if (comp) priv->comp = cal_component_clone (comp); set_title_from_comp (tedit, priv->comp); fill_widgets (tedit); } void task_editor_focus (TaskEditor *tedit) { TaskEditorPrivate *priv; g_return_if_fail (tedit != NULL); g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; gtk_widget_show_now (priv->app); raise_and_focus (priv->app); } #warning this is duplicated function from ../event-editor.c /* Creates an appropriate title for the task editor dialog */ static char * make_title_from_comp (CalComponent *comp) { char *title; const char *type_string; CalComponentVType type; CalComponentText text; if (!comp) return g_strdup (_("Edit Task")); type = cal_component_get_vtype (comp); switch (type) { case CAL_COMPONENT_EVENT: type_string = _("Appointment - %s"); break; case CAL_COMPONENT_TODO: type_string = _("Task - %s"); break; case CAL_COMPONENT_JOURNAL: type_string = _("Journal entry - %s"); break; default: g_message ("make_title_from_comp(): Cannot handle object of type %d", type); return NULL; } cal_component_get_summary (comp, &text); if (text.value) { char *summary; summary = e_utf8_to_locale_string (text.value); title = g_strdup_printf (type_string, summary); g_free (summary); } else title = g_strdup_printf (type_string, _("No summary")); return title; } /* Sets the event editor's window title from a calendar component */ static void set_title_from_comp (TaskEditor *tedit, CalComponent *comp) { TaskEditorPrivate *priv = tedit->priv; char *title; title = make_title_from_comp (comp); gtk_window_set_title (GTK_WINDOW (priv->app), title); g_free (title); } /* Fills the widgets with default values */ static void clear_widgets (TaskEditor *tedit) { TaskEditorPrivate *priv; priv = tedit->priv; } /* Fills in the widgets with the proper values */ static void fill_widgets (TaskEditor *tedit) { TaskEditorPrivate *priv; CalComponentText text; CalComponentDateTime d; CalComponentClassification cl; struct icaltimetype *completed; GSList *l; time_t t; int *priority_value, *percent; icalproperty_status status; TaskEditorPriority priority; const char *url; const char *categories; priv = tedit->priv; task_editor_set_changed (tedit, FALSE); clear_widgets (tedit); if (!priv->comp) return; /* We want to ignore any signals emitted while changing fields. */ priv->ignore_callbacks = TRUE; cal_component_get_summary (priv->comp, &text); e_dialog_editable_set (priv->summary, text.value); cal_component_get_description_list (priv->comp, &l); if (l) { text = *(CalComponentText *)l->data; e_dialog_editable_set (priv->description, text.value); } else { e_dialog_editable_set (priv->description, NULL); } cal_component_free_text_list (l); /* Due Date. */ cal_component_get_due (priv->comp, &d); if (d.value) { t = icaltime_as_timet (*d.value); } else { t = -1; } e_date_edit_set_time (E_DATE_EDIT (priv->due_date), t); /* Start Date. */ cal_component_get_dtstart (priv->comp, &d); if (d.value) { t = icaltime_as_timet (*d.value); } else { t = -1; } e_date_edit_set_time (E_DATE_EDIT (priv->start_date), t); /* Completed Date. */ cal_component_get_completed (priv->comp, &completed); if (completed) { t = icaltime_as_timet (*completed); cal_component_free_icaltimetype (completed); } else { t = -1; } e_date_edit_set_time (E_DATE_EDIT (priv->completed_date), t); /* Percent Complete. */ cal_component_get_percent (priv->comp, &percent); if (percent) { e_dialog_spin_set (priv->percent_complete, *percent); cal_component_free_percent (percent); } else { /* FIXME: Could check if task is completed and set 100%. */ e_dialog_spin_set (priv->percent_complete, 0); } /* Status. */ cal_component_get_status (priv->comp, &status); if (status == ICAL_STATUS_NONE) { /* Try to user the percent value. */ if (percent) { if (*percent == 0) status = ICAL_STATUS_NEEDSACTION; else if (*percent == 100) status = ICAL_STATUS_COMPLETED; else status = ICAL_STATUS_INPROCESS; } else status = ICAL_STATUS_NEEDSACTION; } e_dialog_option_menu_set (priv->status, status, status_map); /* Priority. */ cal_component_get_priority (priv->comp, &priority_value); if (priority_value) { priority = priority_value_to_index (*priority_value); cal_component_free_priority (priority_value); } else { priority = PRIORITY_UNDEFINED; } e_dialog_option_menu_set (priv->priority, priority, priority_map); /* Classification. */ cal_component_get_classification (priv->comp, &cl); switch (cl) { case CAL_COMPONENT_CLASS_PUBLIC: e_dialog_radio_set (priv->classification_public, CAL_COMPONENT_CLASS_PUBLIC, classification_map); case CAL_COMPONENT_CLASS_PRIVATE: e_dialog_radio_set (priv->classification_public, CAL_COMPONENT_CLASS_PRIVATE, classification_map); case CAL_COMPONENT_CLASS_CONFIDENTIAL: e_dialog_radio_set (priv->classification_public, CAL_COMPONENT_CLASS_CONFIDENTIAL, classification_map); default: /* What do do? We can't g_assert_not_reached() since it is a * value from an external file. */ } /* Categories */ cal_component_get_categories (priv->comp, &categories); e_dialog_editable_set (priv->categories, categories); /* URL. */ cal_component_get_url (priv->comp, &url); e_dialog_editable_set (priv->url, url); priv->ignore_callbacks = FALSE; } static void save_todo_object (TaskEditor *tedit) { TaskEditorPrivate *priv; priv = tedit->priv; g_return_if_fail (priv->client != NULL); if (!priv->comp) return; dialog_to_comp_object (tedit); set_title_from_comp (tedit, priv->comp); if (!cal_client_update_object (priv->client, priv->comp)) g_message ("save_todo_object(): Could not update the object!"); else task_editor_set_changed (tedit, FALSE); } /* Get the values of the widgets in the event editor and put them in the iCalObject */ static void dialog_to_comp_object (TaskEditor *tedit) { TaskEditorPrivate *priv; CalComponent *comp; CalComponentDateTime date; time_t t; icalproperty_status status; TaskEditorPriority priority; int priority_value, percent; char *url, *cat; char *str; priv = tedit->priv; comp = priv->comp; /* Summary. */ str = e_dialog_editable_get (priv->summary); if (!str || strlen (str) == 0) cal_component_set_summary (comp, NULL); else { CalComponentText text; text.value = str; text.altrep = NULL; cal_component_set_summary (comp, &text); } if (str) g_free (str); /* Description */ str = e_dialog_editable_get (priv->description); if (!str || strlen (str) == 0) cal_component_set_description_list (comp, NULL); else { GSList l; CalComponentText text; text.value = str; text.altrep = NULL; l.data = &text; l.next = NULL; cal_component_set_description_list (comp, &l); } if (!str) g_free (str); /* Dates */ date.value = g_new (struct icaltimetype, 1); date.tzid = NULL; /* Due Date. */ t = e_date_edit_get_time (E_DATE_EDIT (priv->due_date)); if (t != -1) { *date.value = icaltime_from_timet (t, FALSE); cal_component_set_due (comp, &date); } else { cal_component_set_due (comp, NULL); } /* Start Date. */ t = e_date_edit_get_time (E_DATE_EDIT (priv->start_date)); if (t != -1) { *date.value = icaltime_from_timet (t, FALSE); cal_component_set_dtstart (comp, &date); } else { cal_component_set_dtstart (comp, NULL); } /* Completed Date. */ t = e_date_edit_get_time (E_DATE_EDIT (priv->completed_date)); if (t != -1) { *date.value = icaltime_from_timet (t, FALSE); cal_component_set_completed (comp, date.value); } else { cal_component_set_completed (comp, NULL); } g_free (date.value); /* Percent Complete. */ percent = e_dialog_spin_get_int (priv->percent_complete); cal_component_set_percent (comp, &percent); /* Status. */ status = e_dialog_option_menu_get (priv->status, status_map); cal_component_set_status (comp, status); /* Priority. */ priority = e_dialog_option_menu_get (priv->priority, priority_map); priority_value = priority_index_to_value (priority); cal_component_set_priority (comp, &priority_value); /* Classification. */ cal_component_set_classification (comp, classification_get (priv->classification_public)); /* Categories */ cat = e_dialog_editable_get (priv->categories); cal_component_set_categories (comp, cat); if (cat) g_free (cat); /* URL. */ url = e_dialog_editable_get (priv->url); cal_component_set_url (comp, url); if (url) g_free (url); cal_component_commit_sequence (comp); } static TaskEditorPriority priority_value_to_index (int priority_value) { TaskEditorPriority retval; if (priority_value == 0) retval = PRIORITY_UNDEFINED; else if (priority_value <= 4) retval = PRIORITY_HIGH; else if (priority_value == 5) retval = PRIORITY_NORMAL; else retval = PRIORITY_LOW; return retval; } static int priority_index_to_value (TaskEditorPriority priority) { int retval; switch (priority) { case PRIORITY_UNDEFINED: retval = 0; break; case PRIORITY_HIGH: retval = 3; break; case PRIORITY_NORMAL: retval = 5; break; case PRIORITY_LOW: retval = 7; break; default: retval = -1; g_assert_not_reached (); break; } return retval; } static void completed_changed (EDateEdit *dedit, TaskEditor *tedit) { TaskEditorPrivate *priv; time_t t; g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; if (priv->ignore_callbacks) return; task_editor_set_changed (tedit, TRUE); priv->ignore_callbacks = TRUE; t = e_date_edit_get_time (E_DATE_EDIT (priv->completed_date)); if (t == -1) { /* If the 'Completed Date' is set to 'None', we set the status to 'Not Started' and the percent-complete to 0. The task may actually be partially-complete, but we leave it to the user to set those fields. */ e_dialog_option_menu_set (priv->status, ICAL_STATUS_NEEDSACTION, status_map); e_dialog_spin_set (priv->percent_complete, 0); } else { e_dialog_option_menu_set (priv->status, ICAL_STATUS_COMPLETED, status_map); e_dialog_spin_set (priv->percent_complete, 100); } priv->ignore_callbacks = FALSE; } static void status_changed (GtkMenu *menu, TaskEditor *tedit) { TaskEditorPrivate *priv; icalproperty_status status; g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; if (priv->ignore_callbacks) return; task_editor_set_changed (tedit, TRUE); status = e_dialog_option_menu_get (priv->status, status_map); priv->ignore_callbacks = TRUE; if (status == ICAL_STATUS_NEEDSACTION) { e_dialog_spin_set (priv->percent_complete, 0); e_date_edit_set_time (E_DATE_EDIT (priv->completed_date), -1); } else if (status == ICAL_STATUS_COMPLETED) { e_dialog_spin_set (priv->percent_complete, 100); e_date_edit_set_time (E_DATE_EDIT (priv->completed_date), time (NULL)); } priv->ignore_callbacks = FALSE; } static void percent_complete_changed (GtkAdjustment *adj, TaskEditor *tedit) { TaskEditorPrivate *priv; gint percent; icalproperty_status status; time_t date_completed; g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; if (priv->ignore_callbacks) return; task_editor_set_changed (tedit, TRUE); percent = e_dialog_spin_get_int (priv->percent_complete); priv->ignore_callbacks = TRUE; if (percent == 100) { date_completed = time (NULL); status = ICAL_STATUS_COMPLETED; } else { /* FIXME: Set to 'None'. */ date_completed = time (NULL); if (percent == 0) status = ICAL_STATUS_NEEDSACTION; else status = ICAL_STATUS_INPROCESS; } e_date_edit_set_time (E_DATE_EDIT (priv->completed_date), date_completed); e_dialog_option_menu_set (priv->status, status, status_map); priv->ignore_callbacks = FALSE; } /* Decode the radio button group for classifications */ static CalComponentClassification classification_get (GtkWidget *widget) { return e_dialog_radio_get (widget, classification_map); } /* This is called when all fields except those handled above (status, percent complete & completed date) are changed. It just sets the "changed" flag. */ static void field_changed (GtkWidget *widget, TaskEditor *tedit) { TaskEditorPrivate *priv; g_return_if_fail (IS_TASK_EDITOR (tedit)); priv = tedit->priv; if (priv->ignore_callbacks) return; task_editor_set_changed (tedit, TRUE); } static void task_editor_set_changed (TaskEditor *tedit, gboolean changed) { TaskEditorPrivate *priv; priv = tedit->priv; #if 0 g_print ("In task_editor_set_changed: %s\n", changed ? "TRUE" : "FALSE"); #endif priv->changed = changed; if (priv->app) gnome_property_box_set_state (GNOME_PROPERTY_BOX (priv->app), changed); } /* This checks if the "changed" field is set, and if so it prompts to save the changes using a "Save/Discard/Cancel" modal dialog. It then saves the changes if requested. It returns TRUE if the dialog should now be closed. */ static gboolean prompt_to_save_changes (TaskEditor *tedit) { TaskEditorPrivate *priv; priv = tedit->priv; if (!priv->changed) return TRUE; switch (save_component_dialog (GTK_WINDOW (priv->app))) { case 0: /* Save */ /* FIXME: If an error occurs here, we should popup a dialog and then return FALSE. */ save_todo_object (tedit); return TRUE; case 1: /* Discard */ return TRUE; case 2: /* Cancel */ default: return FALSE; break; } } static void categories_clicked(GtkWidget *button, TaskEditor *tedit) { char *categories; GnomeDialog *dialog; int result; GtkWidget *entry; entry = ((TaskEditorPrivate *)tedit->priv)->categories; categories = e_utf8_gtk_entry_get_text (GTK_ENTRY (entry)); dialog = GNOME_DIALOG (e_categories_new (categories)); result = gnome_dialog_run (dialog); g_free (categories); if (result == 0) { gtk_object_get (GTK_OBJECT (dialog), "categories", &categories, NULL); e_utf8_gtk_entry_set_text (GTK_ENTRY (entry), categories); g_free (categories); } gtk_object_destroy (GTK_OBJECT (dialog)); }