diff options
author | JP Rosevear <jpr@ximian.com> | 2001-09-19 12:33:33 +0800 |
---|---|---|
committer | JP Rosevear <jpr@src.gnome.org> | 2001-09-19 12:33:33 +0800 |
commit | d639a620a6438ea93787cedfe9f3eb87014275a3 (patch) | |
tree | 602d9156e8e33197fc27752d8fb976cce99d896c /calendar/gui | |
parent | d284de85386149adba1e94f1b05b6b2d631e8585 (diff) | |
download | gsoc2013-evolution-d639a620a6438ea93787cedfe9f3eb87014275a3.tar gsoc2013-evolution-d639a620a6438ea93787cedfe9f3eb87014275a3.tar.gz gsoc2013-evolution-d639a620a6438ea93787cedfe9f3eb87014275a3.tar.bz2 gsoc2013-evolution-d639a620a6438ea93787cedfe9f3eb87014275a3.tar.lz gsoc2013-evolution-d639a620a6438ea93787cedfe9f3eb87014275a3.tar.xz gsoc2013-evolution-d639a620a6438ea93787cedfe9f3eb87014275a3.tar.zst gsoc2013-evolution-d639a620a6438ea93787cedfe9f3eb87014275a3.zip |
A page that shows the meeting time selector and free/busy data for
2001-09-18 JP Rosevear <jpr@ximian.com>
* gui/dialogs/schedule-page.*: A page that shows the meeting time
selector and free/busy data for attendees
* gui/dialogs/meeting-page.c: use the meeting model to track/edit
attendees, remove table value conversion routines and simple table
routines
(set_attendees): take a pointer array
(meeting_page_destroy): destroy the pointer array, save state
(meeting_page_init): new pointer array
(meeting_page_fill_widgets): don't null the deleted attendees
field
(popup_delegate_cb): array add
(popup_delete_cb): array add
(cleanup_attendees): iterate over the array to unref now
(meeting_page_fill_widgets): don't null out fields, no need to add
attendees here
(invite_entry_changed): use e_meeting_attendee routines
(popup_delegate_cb): ditto
(popup_delete_cb): ditto
(meeting_page_new): take new arg and pass it to construct
(meeting_page_construct): take new arg, use e-meeting-model
routines to construct table
* gui/dialogs/task-editor.c (task_editor_init): new meeting model
(task_editor_destroy): unref the model
* gui/dialogs/event-editor.c (event_editor_init): make new model
and pass it to meeting and schedule pages
(event_editor_set_cal_client): virtual function, set meeting model
client
(event_editor_edit_comp): add the attendees to the model
(event_editor_destroy): unref model
* gui/dialogs/comp-editor.h: add virtual function
* gui/dialogs/comp-editor.c (comp_editor_set_cal_client): make
set_cal_client a virutal function
* gui/e-meeting-types.h: generally useful type defines
* gui/e-meeting-time-sel*.[hc]: Move here and use an e-table for
the attendee list and extract display information from the new
meeting model and attendees
* gui/e-meeting-time-sel.etspec: spec for the table
* gui/e-meeting-attendee.[hc]: meeting attendees for the model,
with to/from conversions for CalComponentAttendee structure, emits
changed signal and allows getting and setting of free busy
periods
* gui/e-meeting-model.[hc]: move the model out on its own
* gui/e-itip-control.c (write_error_html): clean up warnings
svn path=/trunk/; revision=12968
Diffstat (limited to 'calendar/gui')
23 files changed, 7482 insertions, 789 deletions
diff --git a/calendar/gui/Makefile.am b/calendar/gui/Makefile.am index e7643167ca..06134d5485 100644 --- a/calendar/gui/Makefile.am +++ b/calendar/gui/Makefile.am @@ -38,6 +38,8 @@ INCLUDES = \ -I$(top_builddir)/calendar/cal-client \ -I$(top_srcdir)/libical/src/libical \ -I$(top_builddir)/libical/src/libical \ + -I$(top_srcdir)/addressbook/backend/ebook \ + -I$(top_builddir)/addressbook/backend/ebook \ -I$(top_srcdir)/widgets \ -I$(includedir) \ $(BONOBO_HTML_GNOME_CFLAGS) \ @@ -58,7 +60,7 @@ glade_DATA = \ goto-dialog.glade etspecdir = $(datadir)/evolution/etspec/ -etspec_DATA = e-calendar-table.etspec +etspec_DATA = e-calendar-table.etspec e-meeting-time-sel.etspec evolution_calendar_SOURCES = \ $(IDL_GENERATED) \ @@ -96,6 +98,15 @@ evolution_calendar_SOURCES = \ e-day-view.h \ e-itip-control.h \ e-itip-control.c \ + e-meeting-attendee.c \ + e-meeting-attendee.h \ + e-meeting-model.c \ + e-meeting-model.h \ + e-meeting-time-sel.c \ + e-meeting-time-sel.h \ + e-meeting-time-sel-item.c \ + e-meeting-time-sel-item.h \ + e-meeting-types.h \ e-week-view-event-item.c \ e-week-view-event-item.h \ e-week-view-layout.c \ @@ -143,7 +154,6 @@ evolution_calendar_LDADD = \ $(top_builddir)/libversit/libversit.la \ $(top_builddir)/libical/src/libical/libical.la \ $(top_builddir)/libwombat/libwombat.la \ - $(top_builddir)/widgets/meeting-time-sel/libevolutionmtsel.a \ $(top_builddir)/addressbook/backend/ebook/libebook.la \ $(top_builddir)/camel/libcamel.la \ $(top_builddir)/libibex/libibex.la \ diff --git a/calendar/gui/dialogs/Makefile.am b/calendar/gui/dialogs/Makefile.am index b93a5b2335..c0c410a565 100644 --- a/calendar/gui/dialogs/Makefile.am +++ b/calendar/gui/dialogs/Makefile.am @@ -69,6 +69,8 @@ libcal_dialogs_a_SOURCES = \ recurrence-page.h \ save-comp.c \ save-comp.h \ + schedule-page.c \ + schedule-page.h \ send-comp.c \ send-comp.h \ task-editor.c \ @@ -87,6 +89,7 @@ glade_DATA = \ event-page.glade \ meeting-page.glade \ recurrence-page.glade \ + schedule-page.glade \ task-details-page.glade \ task-page.glade diff --git a/calendar/gui/dialogs/comp-editor.c b/calendar/gui/dialogs/comp-editor.c index 433a9879aa..de4e528334 100644 --- a/calendar/gui/dialogs/comp-editor.c +++ b/calendar/gui/dialogs/comp-editor.c @@ -76,6 +76,7 @@ static void comp_editor_class_init (CompEditorClass *class); static void comp_editor_init (CompEditor *editor); static void comp_editor_destroy (GtkObject *object); +static void real_set_cal_client (CompEditor *editor, CalClient *client); static void real_edit_comp (CompEditor *editor, CalComponent *comp); static void real_send_comp (CompEditor *editor, CalComponentItipMethod method); static void save_comp (CompEditor *editor); @@ -174,6 +175,7 @@ comp_editor_class_init (CompEditorClass *klass) parent_class = gtk_type_class (GTK_TYPE_OBJECT); + klass->set_cal_client = real_set_cal_client; klass->edit_comp = real_edit_comp; klass->send_comp = real_send_comp; @@ -242,7 +244,6 @@ comp_editor_destroy (GtkObject *object) { CompEditor *editor; CompEditorPrivate *priv; - GList *l; editor = COMP_EDITOR (object); priv = editor->priv; @@ -252,12 +253,6 @@ comp_editor_destroy (GtkObject *object) priv->window = NULL; } - /* We want to destroy the pages after the widgets get destroyed, - since they have lots of signal handlers connected to the widgets - with the pages as the data. */ - for (l = priv->pages; l != NULL; l = l->next) - gtk_object_unref (GTK_OBJECT (l->data)); - gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), editor); g_free (priv); @@ -398,41 +393,15 @@ comp_editor_show_page (CompEditor *editor, CompEditorPage *page) void comp_editor_set_cal_client (CompEditor *editor, CalClient *client) { - CompEditorPrivate *priv; - GList *elem; - + CompEditorClass *klass; + g_return_if_fail (editor != NULL); g_return_if_fail (IS_COMP_EDITOR (editor)); - priv = editor->priv; - - if (client == priv->client) - return; - - if (client) { - g_return_if_fail (IS_CAL_CLIENT (client)); - g_return_if_fail (cal_client_get_load_state (client) == - CAL_CLIENT_LOAD_LOADED); - gtk_object_ref (GTK_OBJECT (client)); - } + klass = COMP_EDITOR_CLASS (GTK_OBJECT (editor)->klass); - if (priv->client) { - gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), - editor); - gtk_object_unref (GTK_OBJECT (priv->client)); - } - - priv->client = client; - - /* Pass the client to any pages that need it. */ - for (elem = priv->pages; elem; elem = elem->next) - comp_editor_page_set_cal_client (elem->data, client); - - gtk_signal_connect (GTK_OBJECT (priv->client), "obj_updated", - GTK_SIGNAL_FUNC (obj_updated_cb), editor); - - gtk_signal_connect (GTK_OBJECT (priv->client), "obj_removed", - GTK_SIGNAL_FUNC (obj_removed_cb), editor); + if (klass->set_cal_client) + klass->set_cal_client (editor, client); } /** @@ -519,6 +488,46 @@ fill_widgets (CompEditor *editor) for (l = priv->pages; l != NULL; l = l->next) comp_editor_page_fill_widgets (l->data, priv->comp); +} + +static void +real_set_cal_client (CompEditor *editor, CalClient *client) +{ + CompEditorPrivate *priv; + GList *elem; + + g_return_if_fail (editor != NULL); + g_return_if_fail (IS_COMP_EDITOR (editor)); + + priv = editor->priv; + + if (client == priv->client) + return; + + if (client) { + g_return_if_fail (IS_CAL_CLIENT (client)); + g_return_if_fail (cal_client_get_load_state (client) == + CAL_CLIENT_LOAD_LOADED); + gtk_object_ref (GTK_OBJECT (client)); + } + + if (priv->client) { + gtk_signal_disconnect_by_data (GTK_OBJECT (priv->client), + editor); + gtk_object_unref (GTK_OBJECT (priv->client)); + } + + priv->client = client; + + /* Pass the client to any pages that need it. */ + for (elem = priv->pages; elem; elem = elem->next) + comp_editor_page_set_cal_client (elem->data, client); + + gtk_signal_connect (GTK_OBJECT (priv->client), "obj_updated", + GTK_SIGNAL_FUNC (obj_updated_cb), editor); + + gtk_signal_connect (GTK_OBJECT (priv->client), "obj_removed", + GTK_SIGNAL_FUNC (obj_removed_cb), editor); } static void @@ -814,11 +823,15 @@ static void close_dialog (CompEditor *editor) { CompEditorPrivate *priv; + GList *l; priv = editor->priv; g_assert (priv->window != NULL); + for (l = priv->pages; l != NULL; l = l->next) + gtk_object_unref (GTK_OBJECT (l->data)); + gtk_object_destroy (GTK_OBJECT (editor)); } diff --git a/calendar/gui/dialogs/comp-editor.h b/calendar/gui/dialogs/comp-editor.h index a8a459e008..64920879cc 100644 --- a/calendar/gui/dialogs/comp-editor.h +++ b/calendar/gui/dialogs/comp-editor.h @@ -52,6 +52,7 @@ typedef struct { GtkObjectClass parent_class; /* Virtual functions */ + void (* set_cal_client) (CompEditor *page, CalClient *client); void (* edit_comp) (CompEditor *page, CalComponent *comp); void (* send_comp) (CompEditor *page, CalComponentItipMethod method); } CompEditorClass; diff --git a/calendar/gui/dialogs/event-editor.c b/calendar/gui/dialogs/event-editor.c index a58d1d4b82..f95f201883 100644 --- a/calendar/gui/dialogs/event-editor.c +++ b/calendar/gui/dialogs/event-editor.c @@ -35,6 +35,7 @@ #include "alarm-page.h" #include "recurrence-page.h" #include "meeting-page.h" +#include "schedule-page.h" #include "cancel-comp.h" #include "event-editor.h" @@ -43,6 +44,9 @@ struct _EventEditorPrivate { AlarmPage *alarm_page; RecurrencePage *recur_page; MeetingPage *meet_page; + SchedulePage *sched_page; + + EMeetingModel *model; gboolean meeting_shown; }; @@ -51,6 +55,7 @@ struct _EventEditorPrivate { static void event_editor_class_init (EventEditorClass *class); static void event_editor_init (EventEditor *ee); +static void event_editor_set_cal_client (CompEditor *editor, CalClient *client); static void event_editor_edit_comp (CompEditor *editor, CalComponent *comp); static void event_editor_send_comp (CompEditor *editor, CalComponentItipMethod method); static void event_editor_destroy (GtkObject *object); @@ -117,6 +122,7 @@ event_editor_class_init (EventEditorClass *klass) parent_class = gtk_type_class (TYPE_COMP_EDITOR); + editor_class->set_cal_client = event_editor_set_cal_client; editor_class->edit_comp = event_editor_edit_comp; editor_class->send_comp = event_editor_send_comp; @@ -164,12 +170,19 @@ event_editor_init (EventEditor *ee) comp_editor_append_page (COMP_EDITOR (ee), COMP_EDITOR_PAGE (priv->recur_page), _("Recurrence")); - - priv->meet_page = meeting_page_new (); + + priv->model = E_MEETING_MODEL (e_meeting_model_new ()); + + priv->meet_page = meeting_page_new (priv->model); comp_editor_append_page (COMP_EDITOR (ee), COMP_EDITOR_PAGE (priv->meet_page), _("Meeting")); + priv->sched_page = schedule_page_new (priv->model); + comp_editor_append_page (COMP_EDITOR (ee), + COMP_EDITOR_PAGE (priv->sched_page), + _("Scheduling")); + comp_editor_merge_ui (COMP_EDITOR (ee), EVOLUTION_DATADIR "/gnome/ui/evolution-event-editor.xml", verbs); @@ -179,6 +192,21 @@ event_editor_init (EventEditor *ee) } static void +event_editor_set_cal_client (CompEditor *editor, CalClient *client) +{ + EventEditor *ee; + EventEditorPrivate *priv; + + ee = EVENT_EDITOR (editor); + priv = ee->priv; + + e_meeting_model_set_cal_client (priv->model, client); + + if (parent_class->set_cal_client) + parent_class->set_cal_client (editor, client); +} + +static void event_editor_edit_comp (CompEditor *editor, CalComponent *comp) { EventEditor *ee; @@ -191,9 +219,20 @@ event_editor_edit_comp (CompEditor *editor, CalComponent *comp) cal_component_get_attendee_list (comp, &attendees); if (attendees == NULL) { comp_editor_remove_page (editor, COMP_EDITOR_PAGE (priv->meet_page)); + comp_editor_remove_page (editor, COMP_EDITOR_PAGE (priv->sched_page)); + e_meeting_model_remove_all_attendees (priv->model); priv->meeting_shown = FALSE; set_menu_sens (ee); - } + } else { + GSList *l; + for (l = attendees; l != NULL; l = l->next) { + CalComponentAttendee *ca = l->data; + EMeetingAttendee *ia = E_MEETING_ATTENDEE (e_meeting_attendee_new_from_cal_component_attendee (ca)); + + e_meeting_model_add_attendee (priv->model, ia); + gtk_object_unref (GTK_OBJECT (ia)); + } + } cal_component_free_attendee_list (attendees); if (parent_class->edit_comp) @@ -240,6 +279,9 @@ event_editor_destroy (GtkObject *object) gtk_object_unref (GTK_OBJECT (priv->alarm_page)); gtk_object_unref (GTK_OBJECT (priv->recur_page)); gtk_object_unref (GTK_OBJECT (priv->meet_page)); + gtk_object_unref (GTK_OBJECT (priv->sched_page)); + + gtk_object_unref (GTK_OBJECT (priv->model)); if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); @@ -271,6 +313,9 @@ schedule_meeting_cmd (GtkWidget *widget, gpointer data) comp_editor_append_page (COMP_EDITOR (ee), COMP_EDITOR_PAGE (priv->meet_page), _("Meeting")); + comp_editor_append_page (COMP_EDITOR (ee), + COMP_EDITOR_PAGE (priv->sched_page), + _("Scheduling")); priv->meeting_shown = TRUE; set_menu_sens (ee); } diff --git a/calendar/gui/dialogs/meeting-page.c b/calendar/gui/dialogs/meeting-page.c index 9d833d304e..5a146e3d04 100644 --- a/calendar/gui/dialogs/meeting-page.c +++ b/calendar/gui/dialogs/meeting-page.c @@ -52,6 +52,8 @@ #include <e-destination.h> #include "Evolution-Addressbook-SelectNames.h" #include "../component-factory.h" +#include "../e-meeting-attendee.h" +#include "../e-meeting-model.h" #include "../itip-utils.h" #include "comp-editor-util.h" #include "e-delegate-dialog.h" @@ -74,27 +76,10 @@ enum columns { MEETING_COLUMN_COUNT }; -struct attendee { - char *address; - char *member; - - icalparameter_cutype cutype; - icalparameter_role role; - icalparameter_partstat status; - gboolean rsvp; - - char *delto; - char *delfrom; - char *sentby; - char *cn; - char *language; -}; - /* Private part of the MeetingPage structure */ struct _MeetingPagePrivate { /* Lists of attendees */ - GSList *attendees; - GSList *deleted_attendees; + GPtrArray *deleted_attendees; /* To use in case of cancellation */ CalComponent *comp; @@ -121,8 +106,8 @@ struct _MeetingPagePrivate { GtkWidget *invite; /* E Table stuff */ - ETableModel *model; - GtkWidget *etable; + EMeetingModel *model; + ETableScrolled *etable; gint row; /* For handling who the organizer is */ @@ -145,8 +130,6 @@ static void meeting_page_focus_main_widget (CompEditorPage *page); static void meeting_page_fill_widgets (CompEditorPage *page, CalComponent *comp); static void meeting_page_fill_component (CompEditorPage *page, CalComponent *comp); -static int row_count (ETableModel *etm, void *data); -static void *init_value (ETableModel *etm, int col, void *data); static gint right_click_cb (ETable *etable, gint row, gint col, GdkEvent *event, gpointer data); static CompEditorPageClass *parent_class = NULL; @@ -217,8 +200,7 @@ meeting_page_init (MeetingPage *mpage) priv = g_new0 (MeetingPagePrivate, 1); mpage->priv = priv; - priv->attendees = NULL; - priv->deleted_attendees = NULL; + priv->deleted_attendees = g_ptr_array_new (); priv->comp = NULL; @@ -233,56 +215,35 @@ meeting_page_init (MeetingPage *mpage) } static void -set_attendees (CalComponent *comp, GSList *attendees) +set_attendees (CalComponent *comp, const GPtrArray *attendees) { - GSList *comp_attendees = NULL; - GSList *l; + GSList *comp_attendees = NULL, *l; + int i; - for (l = attendees; l != NULL; l = l->next) { - struct attendee *attendee = l->data; - CalComponentAttendee *att = g_new0 (CalComponentAttendee, 1); + for (i = 0; i < attendees->len; i++) { + EMeetingAttendee *ia = g_ptr_array_index (attendees, i); + CalComponentAttendee *ca; + ca = e_meeting_attendee_as_cal_component_attendee (ia); - att->value = attendee->address; - att->member = (attendee->member && *attendee->member) ? attendee->member : NULL; - att->cutype= attendee->cutype; - att->role = attendee->role; - att->status = attendee->status; - att->rsvp = attendee->rsvp; - att->delto = (attendee->delto && *attendee->delto) ? attendee->delto : NULL; - att->delfrom = (attendee->delfrom && *attendee->delfrom) ? attendee->delfrom : NULL; - att->sentby = (attendee->sentby && *attendee->sentby) ? attendee->sentby : NULL; - att->cn = (attendee->cn && *attendee->cn) ? attendee->cn : NULL; - att->language = (attendee->language && *attendee->language) ? attendee->language : NULL; - - comp_attendees = g_slist_prepend (comp_attendees, att); + comp_attendees = g_slist_prepend (comp_attendees, ca); } comp_attendees = g_slist_reverse (comp_attendees); cal_component_set_attendee_list (comp, comp_attendees); + + for (l = comp_attendees; l != NULL; l = l->next) + g_free (l->data); g_slist_free (comp_attendees); } static void -cleanup_attendees (GSList *attendees) +cleanup_attendees (GPtrArray *attendees) { - GSList *l; + int i; - for (l = attendees; l != NULL; l = l->next) { - struct attendee *a = l->data; - - g_free (a->address); - g_free (a->member); - g_free (a->delto); - g_free (a->delfrom); - g_free (a->sentby); - g_free (a->cn); - g_free (a->language); - - g_free (a); - } - - g_slist_free (attendees); + for (i = 0; i < attendees->len; i++) + gtk_object_unref (GTK_OBJECT (g_ptr_array_index (attendees, i))); } /* Destroy handler for the task page */ @@ -291,6 +252,8 @@ meeting_page_destroy (GtkObject *object) { MeetingPage *mpage; MeetingPagePrivate *priv; + ETable *real_table; + char *filename; g_return_if_fail (object != NULL); g_return_if_fail (IS_MEETING_PAGE (object)); @@ -301,12 +264,21 @@ meeting_page_destroy (GtkObject *object) if (priv->comp != NULL) gtk_object_unref (GTK_OBJECT (priv->comp)); - cleanup_attendees (priv->attendees); cleanup_attendees (priv->deleted_attendees); + g_ptr_array_free (priv->deleted_attendees, FALSE); itip_addresses_free (priv->addresses); g_list_free (priv->address_strings); + gtk_object_unref (GTK_OBJECT (priv->model)); + + /* Save state */ + filename = g_strdup_printf ("%s/config/et-header-meeting-page", + evolution_dir); + real_table = e_table_scrolled_get_table (priv->etable); + e_table_save_state (real_table, filename); + g_free (filename); + if (priv->xml) { gtk_object_unref (GTK_OBJECT (priv->xml)); priv->xml = NULL; @@ -376,8 +348,8 @@ meeting_page_fill_widgets (CompEditorPage *page, CalComponent *comp) MeetingPage *mpage; MeetingPagePrivate *priv; CalComponentOrganizer organizer; - GSList *attendees, *l; - GList *l2; + GSList *attendees; + GList *l; mpage = MEETING_PAGE (page); priv = mpage->priv; @@ -389,10 +361,7 @@ meeting_page_fill_widgets (CompEditorPage *page, CalComponent *comp) gtk_object_unref (GTK_OBJECT (priv->comp)); priv->comp = NULL; - cleanup_attendees (priv->attendees); cleanup_attendees (priv->deleted_attendees); - priv->attendees = NULL; - priv->deleted_attendees = NULL; /* Clean the screen */ clear_widgets (mpage); @@ -403,8 +372,8 @@ meeting_page_fill_widgets (CompEditorPage *page, CalComponent *comp) /* Organizer */ cal_component_get_organizer (comp, &organizer); priv->addresses = itip_addresses_get (); - for (l2 = priv->addresses; l2 != NULL; l2 = l2->next) { - ItipAddress *a = l2->data; + for (l = priv->addresses; l != NULL; l = l->next) { + ItipAddress *a = l->data; priv->address_strings = g_list_append (priv->address_strings, a->full); if (a->default_address) @@ -431,36 +400,11 @@ meeting_page_fill_widgets (CompEditorPage *page, CalComponent *comp) e_dialog_editable_set (GTK_COMBO (priv->organizer)->entry, priv->default_address); } - /* Attendees */ - cal_component_get_attendee_list (comp, &attendees); - for (l = attendees; l != NULL; l = l->next) { - CalComponentAttendee *att = l->data; - struct attendee *attendee = g_new0 (struct attendee, 1); - - attendee->address = att->value ? g_strdup (att->value) : g_strdup (""); - attendee->member = att->member ? g_strdup (att->member) : g_strdup (""); - attendee->cutype= att->cutype; - attendee->role = att->role; - attendee->status = att->status; - attendee->rsvp = att->rsvp; - attendee->delto = att->delto ? g_strdup (att->delto) : g_strdup (""); - attendee->delfrom = att->delfrom ? g_strdup (att->delfrom) : g_strdup (""); - attendee->sentby = att->sentby ? g_strdup (att->sentby) : g_strdup (""); - attendee->cn = att->cn ? g_strdup (att->cn) : g_strdup (""); - attendee->language = att->language ? g_strdup (att->language) : g_strdup (""); - - priv->attendees = g_slist_prepend (priv->attendees, attendee); - - } - priv->attendees = g_slist_reverse (priv->attendees); - cal_component_free_attendee_list (attendees); - - /* Table */ - e_table_model_rows_inserted (priv->model, 0, row_count (priv->model, mpage)); - /* So the comp editor knows we need to send if anything changes */ - if (priv->attendees != NULL) + cal_component_get_attendee_list (comp, &attendees); + if (attendees != NULL) comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (mpage)); + cal_component_free_attendee_list (attendees); priv->updating = FALSE; } @@ -514,7 +458,7 @@ meeting_page_fill_component (CompEditorPage *page, CalComponent *comp) g_free (cn); } - set_attendees (comp, priv->attendees); + set_attendees (comp, e_meeting_model_get_attendees (priv->model)); } @@ -561,167 +505,6 @@ get_widgets (MeetingPage *mpage) && priv->existing_organizer_btn); } - -static icalparameter_cutype -text_to_type (const char *type) -{ - if (!g_strcasecmp (type, _("Individual"))) - return ICAL_CUTYPE_INDIVIDUAL; - else if (!g_strcasecmp (type, _("Group"))) - return ICAL_CUTYPE_GROUP; - else if (!g_strcasecmp (type, _("Resource"))) - return ICAL_CUTYPE_RESOURCE; - else if (!g_strcasecmp (type, _("Room"))) - return ICAL_CUTYPE_ROOM; - else - return ICAL_CUTYPE_NONE; -} - -static char * -type_to_text (icalparameter_cutype type) -{ - switch (type) { - case ICAL_CUTYPE_INDIVIDUAL: - return _("Individual"); - case ICAL_CUTYPE_GROUP: - return _("Group"); - case ICAL_CUTYPE_RESOURCE: - return _("Resource"); - case ICAL_CUTYPE_ROOM: - return _("Room"); - default: - return _("Unknown"); - } - - return NULL; - -} - -static icalparameter_role -text_to_role (const char *role) -{ - if (!g_strcasecmp (role, _("Chair"))) - return ICAL_ROLE_CHAIR; - else if (!g_strcasecmp (role, _("Required Participant"))) - return ICAL_ROLE_REQPARTICIPANT; - else if (!g_strcasecmp (role, _("Optional Participant"))) - return ICAL_ROLE_OPTPARTICIPANT; - else if (!g_strcasecmp (role, _("Non-Participant"))) - return ICAL_ROLE_NONPARTICIPANT; - else - return ICAL_ROLE_NONE; -} - -static char * -role_to_text (icalparameter_role role) -{ - switch (role) { - case ICAL_ROLE_CHAIR: - return _("Chair"); - case ICAL_ROLE_REQPARTICIPANT: - return _("Required Participant"); - case ICAL_ROLE_OPTPARTICIPANT: - return _("Optional Participant"); - case ICAL_ROLE_NONPARTICIPANT: - return _("Non-Participant"); - default: - return _("Unknown"); - } - - return NULL; -} - -static gboolean -text_to_boolean (const char *role) -{ - if (!g_strcasecmp (role, _("Yes"))) - return TRUE; - else - return FALSE; -} - -static char * -boolean_to_text (gboolean b) -{ - if (b) - return _("Yes"); - else - return _("No"); -} - -static icalparameter_partstat -text_to_partstat (const char *partstat) -{ - if (!g_strcasecmp (partstat, _("Needs Action"))) - return ICAL_PARTSTAT_NEEDSACTION; - else if (!g_strcasecmp (partstat, _("Accepted"))) - return ICAL_PARTSTAT_ACCEPTED; - else if (!g_strcasecmp (partstat, _("Declined"))) - return ICAL_PARTSTAT_DECLINED; - else if (!g_strcasecmp (partstat, _("Tentative"))) - return ICAL_PARTSTAT_TENTATIVE; - else if (!g_strcasecmp (partstat, _("Delegated"))) - return ICAL_PARTSTAT_DELEGATED; - else if (!g_strcasecmp (partstat, _("Completed"))) - return ICAL_PARTSTAT_COMPLETED; - else if (!g_strcasecmp (partstat, _("In Process"))) - return ICAL_PARTSTAT_INPROCESS; - else - return ICAL_PARTSTAT_NONE; -} - -static char * -partstat_to_text (icalparameter_partstat partstat) -{ - switch (partstat) { - case ICAL_PARTSTAT_NEEDSACTION: - return _("Needs Action"); - case ICAL_PARTSTAT_ACCEPTED: - return _("Accepted"); - case ICAL_PARTSTAT_DECLINED: - return _("Declined"); - case ICAL_PARTSTAT_TENTATIVE: - return _("Tentative"); - case ICAL_PARTSTAT_DELEGATED: - return _("Delegated"); - case ICAL_PARTSTAT_COMPLETED: - return _("Completed"); - case ICAL_PARTSTAT_INPROCESS: - return _("In Process"); - default: - return _("Unknown"); - } - - return NULL; -} - -static struct attendee * -find_match (MeetingPage *mpage, const char *address, int *pos) -{ - MeetingPagePrivate *priv; - struct attendee *a; - GSList *l; - int i; - - priv = mpage->priv; - - if (address == NULL) - return NULL; - - /* Make sure we can add the new delegatee person */ - for (l = priv->attendees, i = 0; l != NULL; l = l->next, i++) { - a = l->data; - - if (a->address != NULL && !g_strcasecmp (itip_strip_mailto (a->address), itip_strip_mailto (address))) { - if (pos != NULL) - *pos = i; - return a; - } - } - - return NULL; -} - static void duplicate_error (void) { @@ -759,50 +542,28 @@ invite_entry_changed (BonoboListener *listener, return; for (i = 0; destv[i] != NULL; i++) { - struct attendee *a; + EMeetingAttendee *ia; const char *name, *address; - char *str; - int row_cnt; name = e_destination_get_name (destv[i]); address = e_destination_get_email (destv[i]); - if (find_match (mpage, address, NULL) == NULL) { - a = g_new0 (struct attendee, 1); - - a->address = g_strdup_printf ("MAILTO:%s", address); - a->member = init_value (NULL, MEETING_MEMBER_COL, mpage); - str = init_value (NULL, MEETING_TYPE_COL, mpage); - a->cutype = text_to_type (str); - g_free (str); + if (e_meeting_model_find_attendee (priv->model, address, NULL) == NULL) { + ia = e_meeting_model_add_attendee_with_defaults (priv->model); + e_meeting_attendee_set_address (ia, g_strdup_printf ("MAILTO:%s", address)); if (!strcmp (section, _("Chair Persons"))) - a->role = ICAL_ROLE_CHAIR; + e_meeting_attendee_set_role (ia, ICAL_ROLE_CHAIR); else if (!strcmp (section, _("Required Participants"))) - a->role = ICAL_ROLE_REQPARTICIPANT; + e_meeting_attendee_set_role (ia, ICAL_ROLE_REQPARTICIPANT); else if (!strcmp (section, _("Optional Participants"))) - a->role = ICAL_ROLE_OPTPARTICIPANT; + e_meeting_attendee_set_role (ia, ICAL_ROLE_OPTPARTICIPANT); else if (!strcmp (section, _("Non-Participants"))) - a->role = ICAL_ROLE_NONPARTICIPANT; - - str = init_value (NULL, MEETING_RSVP_COL, mpage); - a->rsvp = text_to_boolean (str); - g_free (str); - a->delto = init_value (NULL, MEETING_DELTO_COL, mpage); - a->delfrom = init_value (NULL, MEETING_DELTO_COL, mpage); - str = init_value (NULL, MEETING_STATUS_COL, mpage); - a->status = text_to_partstat (str); - g_free (str); - a->cn = name ? g_strdup (name) : g_strdup (""); - a->language = init_value (NULL, MEETING_LANG_COL, mpage); - - priv->attendees = g_slist_append (priv->attendees, a); - - row_cnt = row_count (priv->model, mpage) - 1; - e_table_model_row_inserted (priv->model, row_cnt); + e_meeting_attendee_set_role (ia, ICAL_ROLE_NONPARTICIPANT); + e_meeting_attendee_set_cn (ia, g_strdup (name)); - comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (mpage)); - comp_editor_page_notify_changed (COMP_EDITOR_PAGE (mpage)); + comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (mpage)); + comp_editor_page_notify_changed (COMP_EDITOR_PAGE (mpage)); } } e_destination_freev (destv); @@ -976,373 +737,6 @@ init_widgets (MeetingPage *mpage) GTK_SIGNAL_FUNC (invite_cb), mpage); } -static int -column_count (ETableModel *etm, void *data) -{ - return MEETING_COLUMN_COUNT; -} - -static int -row_count (ETableModel *etm, void *data) -{ - MeetingPage *mpage; - MeetingPagePrivate *priv; - - mpage = MEETING_PAGE (data); - priv = mpage->priv; - - return g_slist_length (priv->attendees); -} - -static void -append_row (ETableModel *etm, ETableModel *model, int row, void *data) -{ - MeetingPage *mpage; - MeetingPagePrivate *priv; - struct attendee *attendee; - char *address; - gint row_cnt; - - mpage = MEETING_PAGE (data); - priv = mpage->priv; - - address = (char *) e_table_model_value_at (model, MEETING_ATTENDEE_COL, row); - if (find_match (mpage, address, NULL) != NULL) { - duplicate_error (); - return; - } - - attendee = g_new0 (struct attendee, 1); - - attendee->address = g_strdup_printf ("MAILTO:%s", address); - attendee->member = g_strdup (e_table_model_value_at (model, MEETING_MEMBER_COL, row)); - attendee->cutype = text_to_type (e_table_model_value_at (model, MEETING_TYPE_COL, row)); - attendee->role = text_to_role (e_table_model_value_at (model, MEETING_ROLE_COL, row)); - attendee->rsvp = text_to_boolean (e_table_model_value_at (model, MEETING_RSVP_COL, row)); - attendee->delto = g_strdup (e_table_model_value_at (model, MEETING_DELTO_COL, row)); - attendee->delfrom = g_strdup (e_table_model_value_at (model, MEETING_DELFROM_COL, row)); - attendee->status = text_to_partstat (e_table_model_value_at (model, MEETING_STATUS_COL, row)); - attendee->cn = g_strdup (e_table_model_value_at (model, MEETING_CN_COL, row)); - attendee->language = g_strdup (e_table_model_value_at (model, MEETING_LANG_COL, row)); - - priv->attendees = g_slist_append (priv->attendees, attendee); - - row_cnt = row_count (etm, data) - 1; - e_table_model_row_inserted (E_TABLE_MODEL (etm), row_cnt); - - comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (mpage)); - comp_editor_page_notify_changed (COMP_EDITOR_PAGE (mpage)); -} - -static void * -value_at (ETableModel *etm, int col, int row, void *data) -{ - MeetingPage *mpage; - MeetingPagePrivate *priv; - struct attendee *attendee; - - mpage = MEETING_PAGE (data); - priv = mpage->priv; - - attendee = g_slist_nth_data (priv->attendees, row); - - switch (col) { - case MEETING_ATTENDEE_COL: - return (void *)itip_strip_mailto (attendee->address); - case MEETING_MEMBER_COL: - return attendee->member; - case MEETING_TYPE_COL: - return type_to_text (attendee->cutype); - case MEETING_ROLE_COL: - return role_to_text (attendee->role); - case MEETING_RSVP_COL: - return boolean_to_text (attendee->rsvp); - case MEETING_DELTO_COL: - return (void *)itip_strip_mailto (attendee->delto); - case MEETING_DELFROM_COL: - return (void *)itip_strip_mailto (attendee->delfrom); - case MEETING_STATUS_COL: - return partstat_to_text (attendee->status); - case MEETING_CN_COL: - return attendee->cn; - case MEETING_LANG_COL: - return attendee->language; - } - - return NULL; -} - -static void -set_value_at (ETableModel *etm, int col, int row, const void *val, void *data) -{ - MeetingPage *mpage; - MeetingPagePrivate *priv; - struct attendee *attendee; - - mpage = MEETING_PAGE (data); - priv = mpage->priv; - - attendee = g_slist_nth_data (priv->attendees, row); - - switch (col) { - case MEETING_ATTENDEE_COL: - if (attendee->address) - g_free (attendee->address); - attendee->address = g_strdup_printf ("MAILTO:%s", (char *) val); - break; - case MEETING_MEMBER_COL: - if (attendee->member) - g_free (attendee->member); - attendee->member = g_strdup (val); - break; - case MEETING_TYPE_COL: - attendee->cutype = text_to_type (val); - break; - case MEETING_ROLE_COL: - attendee->role = text_to_role (val); - break; - case MEETING_RSVP_COL: - attendee->rsvp = text_to_boolean (val); - break; - case MEETING_DELTO_COL: - if (attendee->delto) - g_free (attendee->delto); - attendee->delto = g_strdup (val); - break; - case MEETING_DELFROM_COL: - if (attendee->delfrom) - g_free (attendee->delfrom); - attendee->delto = g_strdup (val); - break; - case MEETING_STATUS_COL: - attendee->status = text_to_partstat (val); - break; - case MEETING_CN_COL: - if (attendee->cn) - g_free (attendee->cn); - attendee->cn = g_strdup (val); - break; - case MEETING_LANG_COL: - if (attendee->language) - g_free (attendee->language); - attendee->language = g_strdup (val); - break; - } - - if (!priv->updating) { - comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (mpage)); - comp_editor_page_notify_changed (COMP_EDITOR_PAGE (mpage)); - } -} - -static gboolean -is_cell_editable (ETableModel *etm, int col, int row, void *data) -{ - switch (col) { - case MEETING_DELTO_COL: - case MEETING_DELFROM_COL: - return FALSE; - - default: - } - - return TRUE; -} - -static void * -duplicate_value (ETableModel *etm, int col, const void *val, void *data) -{ - return g_strdup (val); -} - -static void -free_value (ETableModel *etm, int col, void *val, void *data) -{ - g_free (val); -} - -static void * -init_value (ETableModel *etm, int col, void *data) -{ - switch (col) { - case MEETING_ATTENDEE_COL: - return g_strdup (""); - case MEETING_MEMBER_COL: - return g_strdup (""); - case MEETING_TYPE_COL: - return g_strdup (_("Individual")); - case MEETING_ROLE_COL: - return g_strdup (_("Required Participant")); - case MEETING_RSVP_COL: - return g_strdup (_("Yes")); - case MEETING_DELTO_COL: - return g_strdup (""); - case MEETING_DELFROM_COL: - return g_strdup (""); - case MEETING_STATUS_COL: - return g_strdup (_("Needs Action")); - case MEETING_CN_COL: - return g_strdup (""); - case MEETING_LANG_COL: - return g_strdup ("en"); - } - - return g_strdup (""); -} - -static gboolean -value_is_empty (ETableModel *etm, int col, const void *val, void *data) -{ - - switch (col) { - case MEETING_ATTENDEE_COL: - case MEETING_MEMBER_COL: - case MEETING_DELTO_COL: - case MEETING_DELFROM_COL: - case MEETING_CN_COL: - if (val && !g_strcasecmp (val, "")) - return TRUE; - else - return FALSE; - default: - } - - return TRUE; -} - -static char * -value_to_string (ETableModel *etm, int col, const void *val, void *data) -{ - return g_strdup (val); -} - -static void -etable_destroy_cb (ETable *real_table, MeetingPage *mpage) -{ - char *filename; - - filename = g_strdup_printf ("%s/config/et-header-meeting-page", - evolution_dir); - e_table_save_state (real_table, filename); - g_free (filename); -} - -static void -build_etable (MeetingPage *mpage) -{ - MeetingPagePrivate *priv; - ETable *real_table; - ETableExtras *extras; - GList *strings; - ECell *popup_cell, *cell; - - char *filename; - - priv = mpage->priv; - - extras = e_table_extras_new (); - - /* For type */ - cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); - popup_cell = e_cell_combo_new (); - e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell); - gtk_object_unref (GTK_OBJECT (cell)); - - strings = NULL; - strings = g_list_append (strings, _("Individual")); - strings = g_list_append (strings, _("Group")); - strings = g_list_append (strings, _("Resource")); - strings = g_list_append (strings, _("Room")); - strings = g_list_append (strings, _("Unknown")); - - e_cell_combo_set_popdown_strings (E_CELL_COMBO (popup_cell), strings); - e_table_extras_add_cell (extras, "typeedit", popup_cell); - - /* For role */ - cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); - popup_cell = e_cell_combo_new (); - e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell); - gtk_object_unref (GTK_OBJECT (cell)); - - strings = NULL; - strings = g_list_append (strings, _("Chair")); - strings = g_list_append (strings, _("Required Participant")); - strings = g_list_append (strings, _("Optional Participant")); - strings = g_list_append (strings, _("Non-Participant")); - strings = g_list_append (strings, _("Unknown")); - - e_cell_combo_set_popdown_strings (E_CELL_COMBO (popup_cell), strings); - e_table_extras_add_cell (extras, "roleedit", popup_cell); - - /* For rsvp */ - cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); - popup_cell = e_cell_combo_new (); - e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell); - gtk_object_unref (GTK_OBJECT (cell)); - - strings = NULL; - strings = g_list_append (strings, _("Yes")); - strings = g_list_append (strings, _("No")); - - e_cell_combo_set_popdown_strings (E_CELL_COMBO (popup_cell), strings); - e_table_extras_add_cell (extras, "rsvpedit", popup_cell); - - /* For status */ - cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); - popup_cell = e_cell_combo_new (); - e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell); - gtk_object_unref (GTK_OBJECT (cell)); - - strings = NULL; - strings = g_list_append (strings, _("Needs Action")); - strings = g_list_append (strings, _("Accepted")); - strings = g_list_append (strings, _("Declined")); - strings = g_list_append (strings, _("Tentative")); - strings = g_list_append (strings, _("Delegated")); - - e_cell_combo_set_popdown_strings (E_CELL_COMBO (popup_cell), strings); - e_table_extras_add_cell (extras, "statusedit", popup_cell); - - - /* The table itself */ - priv->model = e_table_simple_new (column_count, - row_count, - append_row, - - value_at, - set_value_at, - is_cell_editable, - - NULL, - NULL, - - duplicate_value, - free_value, - init_value, - value_is_empty, - value_to_string, - mpage); - - priv->etable = e_table_scrolled_new_from_spec_file (priv->model, - extras, - EVOLUTION_ETSPECDIR "/meeting-page.etspec", - NULL); - filename = g_strdup_printf ("%s/config/et-header-meeting-page", - evolution_dir); - real_table = e_table_scrolled_get_table (E_TABLE_SCROLLED (priv->etable)); - e_table_load_state (real_table, filename); - g_free (filename); - - gtk_signal_connect (GTK_OBJECT (real_table), - "destroy", GTK_SIGNAL_FUNC (etable_destroy_cb), - mpage); - - gtk_signal_connect (GTK_OBJECT (real_table), - "right_click", GTK_SIGNAL_FUNC (right_click_cb), mpage); - - gtk_object_unref (GTK_OBJECT (extras)); -} - static void popup_delegate_cb (GtkWidget *widget, gpointer data) { @@ -1350,78 +744,49 @@ popup_delegate_cb (GtkWidget *widget, gpointer data) MeetingPagePrivate *priv; EDelegateDialog *edd; GtkWidget *dialog; - struct attendee *a; + EMeetingAttendee *ia; char *address = NULL, *name = NULL; - gint row_cnt; priv = mpage->priv; - a = g_slist_nth_data (priv->attendees, priv->row); + ia = e_meeting_model_find_attendee_at_row (priv->model, priv->row); /* Show dialog. */ - edd = e_delegate_dialog_new (NULL, itip_strip_mailto (a->delto)); + edd = e_delegate_dialog_new (NULL, itip_strip_mailto (e_meeting_attendee_get_delto (ia))); dialog = e_delegate_dialog_get_toplevel (edd); if (gnome_dialog_run_and_close (GNOME_DIALOG (dialog)) == 0){ - struct attendee *a; - char *str; + EMeetingAttendee *ic; name = e_delegate_dialog_get_delegate_name (edd); address = e_delegate_dialog_get_delegate (edd); /* Make sure we can add the new delegatee person */ - if (find_match (mpage, address, NULL) != NULL) { + if (e_meeting_model_find_attendee (priv->model, address, NULL) != NULL) { duplicate_error (); goto cleanup; } /* Update information for attendee */ - a = g_slist_nth_data (priv->attendees, priv->row); - if (a->delto) { - struct attendee *b; + if (e_meeting_attendee_is_set_delto (ia)) { + EMeetingAttendee *ib; - b = find_match (mpage, a->delto, NULL); - if (b != NULL) { - priv->attendees = g_slist_remove (priv->attendees, b); - priv->deleted_attendees = g_slist_append (priv->deleted_attendees, b); + ib = e_meeting_model_find_attendee (priv->model, itip_strip_mailto (e_meeting_attendee_get_delto (ia)), NULL); + if (ib != NULL) { + gtk_object_ref (GTK_OBJECT (ib)); + g_ptr_array_add (priv->deleted_attendees, ib); - e_table_model_row_deleted (priv->model, priv->row); + e_meeting_model_remove_attendee (priv->model, ib); } - g_free (a->delto); } - - a->delto = g_strdup_printf ("MAILTO:%s", address); + e_meeting_attendee_set_delto (ia, g_strdup_printf ("MAILTO:%s", address)); /* Construct delegatee information */ - a = g_new0 (struct attendee, 1); - - a->address = g_strdup_printf ("MAILTO:%s", address); - a->member = init_value (NULL, MEETING_MEMBER_COL, mpage); - str = init_value (NULL, MEETING_TYPE_COL, mpage); - a->cutype = text_to_type (str); - g_free (str); - str = init_value (NULL, MEETING_ROLE_COL, mpage); - a->role = text_to_role (str); - g_free (str); - str = init_value (NULL, MEETING_RSVP_COL, mpage); - a->rsvp = text_to_boolean (str); - g_free (str); - a->delto = init_value (NULL, MEETING_DELTO_COL, mpage); - a->delfrom = g_strdup_printf ("MAILTO:%s", (char *) value_at (NULL, MEETING_ATTENDEE_COL, priv->row, mpage)); - str = init_value (NULL, MEETING_STATUS_COL, mpage); - a->status = text_to_partstat (str); - g_free (str); - a->cn = name ? g_strdup (name) : g_strdup (""); - a->language = init_value (NULL, MEETING_LANG_COL, mpage); + ic = e_meeting_model_add_attendee_with_defaults (priv->model); - priv->attendees = g_slist_append (priv->attendees, a); - - row_cnt = row_count (priv->model, mpage) - 1; - e_table_model_row_changed (priv->model, priv->row); - e_table_model_row_inserted (priv->model, row_cnt); - - comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (mpage)); - comp_editor_page_notify_changed (COMP_EDITOR_PAGE (mpage)); + e_meeting_attendee_set_address (ic, g_strdup_printf ("MAILTO:%s", address)); + e_meeting_attendee_set_delfrom (ic, g_strdup (e_meeting_attendee_get_address (ia))); + e_meeting_attendee_set_cn (ic, g_strdup (name)); } cleanup: @@ -1435,44 +800,33 @@ popup_delete_cb (GtkWidget *widget, gpointer data) { MeetingPage *mpage = MEETING_PAGE (data); MeetingPagePrivate *priv; - struct attendee *a; + EMeetingAttendee *ia; int pos = 0; priv = mpage->priv; - - a = g_slist_nth_data (priv->attendees, priv->row); + + ia = e_meeting_model_find_attendee_at_row (priv->model, priv->row); /* If this was a delegatee, no longer delegate */ - if (a->delfrom != NULL && *a->delfrom != '\0') { - struct attendee *b; + if (e_meeting_attendee_is_set_delfrom (ia)) { + EMeetingAttendee *ib; - b = find_match (mpage, a->delfrom, &pos); - if (b != NULL && b->delto) { - g_free (b->delto); - b->delto = g_strdup (""); - - e_table_model_row_changed (priv->model, pos); - } + ib = e_meeting_model_find_attendee (priv->model, e_meeting_attendee_get_delfrom (ia), &pos); + if (ib != NULL) + e_meeting_attendee_set_delto (ib, NULL); } /* Handle deleting all attendees in the delegation chain */ - pos = priv->row; - while (a != NULL) { - struct attendee *b = NULL; - - e_table_model_pre_change (priv->model); - - priv->attendees = g_slist_remove (priv->attendees, a); - priv->deleted_attendees = g_slist_append (priv->deleted_attendees, a); + while (ia != NULL) { + EMeetingAttendee *ib = NULL; - e_table_model_row_deleted (priv->model, pos); + gtk_object_ref (GTK_OBJECT (ia)); + g_ptr_array_add (priv->deleted_attendees, ia); + e_meeting_model_remove_attendee (priv->model, ia); - comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (mpage)); - comp_editor_page_notify_changed (COMP_EDITOR_PAGE (mpage)); - - if (a->delto != NULL) - b = find_match (mpage, a->delto, &pos); - a = b; + if (e_meeting_attendee_get_delto (ia) != NULL) + ib = e_meeting_model_find_attendee (priv->model, e_meeting_attendee_get_delto (ia), NULL); + ia = ib; } } @@ -1527,9 +881,11 @@ right_click_cb (ETable *etable, gint row, gint col, GdkEvent *event, gpointer da * be created. **/ MeetingPage * -meeting_page_construct (MeetingPage *mpage) +meeting_page_construct (MeetingPage *mpage, EMeetingModel *emm) { MeetingPagePrivate *priv; + ETable *real_table; + gchar *filename; priv = mpage->priv; @@ -1548,9 +904,21 @@ meeting_page_construct (MeetingPage *mpage) } /* The etable displaying attendees and their status */ - build_etable (mpage); - gtk_widget_show (priv->etable); - gtk_box_pack_start (GTK_BOX (priv->main), priv->etable, TRUE, TRUE, 2); + gtk_object_ref (GTK_OBJECT (emm)); + priv->model = emm; + + filename = g_strdup_printf ("%s/config/et-header-meeting-page", evolution_dir); + priv->etable = e_meeting_model_etable_from_model (priv->model, + EVOLUTION_ETSPECDIR "/meeting-page.etspec", + filename); + g_free (filename); + + real_table = e_table_scrolled_get_table (priv->etable); + gtk_signal_connect (GTK_OBJECT (real_table), + "right_click", GTK_SIGNAL_FUNC (right_click_cb), mpage); + + gtk_widget_show (GTK_WIDGET (priv->etable)); + gtk_box_pack_start (GTK_BOX (priv->main), GTK_WIDGET (priv->etable), TRUE, TRUE, 2); /* Init the widget signals */ init_widgets (mpage); @@ -1567,12 +935,12 @@ meeting_page_construct (MeetingPage *mpage) * not be created. **/ MeetingPage * -meeting_page_new (void) +meeting_page_new (EMeetingModel *emm) { MeetingPage *mpage; mpage = gtk_type_new (TYPE_MEETING_PAGE); - if (!meeting_page_construct (mpage)) { + if (!meeting_page_construct (mpage, emm)) { gtk_object_unref (GTK_OBJECT (mpage)); return NULL; } @@ -1605,4 +973,3 @@ meeting_page_get_cancel_comp (MeetingPage *mpage) return cal_component_clone (priv->comp); } - diff --git a/calendar/gui/dialogs/meeting-page.etspec b/calendar/gui/dialogs/meeting-page.etspec index da49873e61..96bc480fe9 100644 --- a/calendar/gui/dialogs/meeting-page.etspec +++ b/calendar/gui/dialogs/meeting-page.etspec @@ -1,4 +1,4 @@ -<ETableSpecification click-to-add="true" _click-to-add-message="Click here to add an attendee" draw-grid="true"> +<ETableSpecification click-to-add="true" click-to-add-end="true" _click-to-add-message="Click here to add an attendee" draw-grid="true"> <ETableColumn model_col= "0" _title="Attendee" expansion="2.0" minimum_width="10" resizable="true" cell="string" compare="string"/> <ETableColumn model_col= "1" _title="Member" expansion="2.0" minimum_width="10" resizable="true" cell="string" compare="string"/> <ETableColumn model_col= "2" _title="Type" expansion="1.0" minimum_width="10" resizable="true" cell="typeedit" compare="string"/> diff --git a/calendar/gui/dialogs/meeting-page.h b/calendar/gui/dialogs/meeting-page.h index ba8a6d18b4..206ee90bb8 100644 --- a/calendar/gui/dialogs/meeting-page.h +++ b/calendar/gui/dialogs/meeting-page.h @@ -25,6 +25,7 @@ #ifndef MEETING_PAGE_H #define MEETING_PAGE_H +#include "../e-meeting-model.h" #include "comp-editor-page.h" BEGIN_GNOME_DECLS @@ -52,8 +53,8 @@ typedef struct { GtkType meeting_page_get_type (void); -MeetingPage *meeting_page_construct (MeetingPage *mpage); -MeetingPage *meeting_page_new (void); +MeetingPage *meeting_page_construct (MeetingPage *mpage, EMeetingModel *emm); +MeetingPage *meeting_page_new (EMeetingModel *emm); CalComponent *meeting_page_get_cancel_comp (MeetingPage *mpage); diff --git a/calendar/gui/dialogs/schedule-page.c b/calendar/gui/dialogs/schedule-page.c new file mode 100644 index 0000000000..42510e0b5a --- /dev/null +++ b/calendar/gui/dialogs/schedule-page.c @@ -0,0 +1,402 @@ +/* Evolution calendar - Scheduling page + * + * Copyright (C) 2001 Ximian, Inc. + * + * Authors: Federico Mena-Quintero <federico@ximian.com> + * Miguel de Icaza <miguel@ximian.com> + * Seth Alves <alves@hungry.com> + * JP Rosevear <jpr@ximian.com> + * + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <liboaf/liboaf.h> +#include <bonobo/bonobo-control.h> +#include <bonobo/bonobo-widget.h> +#include <bonobo/bonobo-exception.h> +#include <gtk/gtksignal.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkwindow.h> +#include <libgnome/gnome-defs.h> +#include <libgnome/gnome-i18n.h> +#include <libgnomeui/gnome-stock.h> +#include <libgnomeui/gnome-dialog-util.h> +#include <glade/glade.h> +#include <gal/e-table/e-cell-combo.h> +#include <gal/e-table/e-cell-text.h> +#include <gal/e-table/e-table-simple.h> +#include <gal/e-table/e-table-scrolled.h> +#include <gal/widgets/e-unicode.h> +#include <gal/widgets/e-popup-menu.h> +#include <gal/widgets/e-gui-utils.h> +#include <widgets/misc/e-dateedit.h> +#include <e-util/e-dialog-widgets.h> +#include <e-destination.h> +#include "Evolution-Addressbook-SelectNames.h" +#include "../e-meeting-time-sel.h" +#include "../itip-utils.h" +#include "comp-editor-util.h" +#include "e-delegate-dialog.h" +#include "schedule-page.h" + + + +/* Private part of the SchedulePage structure */ +struct _SchedulePagePrivate { + /* Glade XML data */ + GladeXML *xml; + + /* Widgets from the Glade file */ + GtkWidget *main; + + /* Model */ + EMeetingModel *model; + + /* Selector */ + EMeetingTimeSelector *sel; + + gboolean updating; +}; + + + +static void schedule_page_class_init (SchedulePageClass *class); +static void schedule_page_init (SchedulePage *spage); +static void schedule_page_destroy (GtkObject *object); + +static GtkWidget *schedule_page_get_widget (CompEditorPage *page); +static void schedule_page_focus_main_widget (CompEditorPage *page); +static void schedule_page_fill_widgets (CompEditorPage *page, CalComponent *comp); +static void schedule_page_fill_component (CompEditorPage *page, CalComponent *comp); + +static void model_row_changed_cb (ETableModel *etm, int row, gpointer data); +static void row_count_changed_cb (ETableModel *etm, int row, int count, gpointer data); + +static CompEditorPageClass *parent_class = NULL; + + + +/** + * schedule_page_get_type: + * + * Registers the #SchedulePage class if necessary, and returns the type ID + * associated to it. + * + * Return value: The type ID of the #SchedulePage class. + **/ +GtkType +schedule_page_get_type (void) +{ + static GtkType schedule_page_type; + + if (!schedule_page_type) { + static const GtkTypeInfo schedule_page_info = { + "SchedulePage", + sizeof (SchedulePage), + sizeof (SchedulePageClass), + (GtkClassInitFunc) schedule_page_class_init, + (GtkObjectInitFunc) schedule_page_init, + NULL, /* reserved_1 */ + NULL, /* reserved_2 */ + (GtkClassInitFunc) NULL + }; + + schedule_page_type = + gtk_type_unique (TYPE_COMP_EDITOR_PAGE, + &schedule_page_info); + } + + return schedule_page_type; +} + +/* Class initialization function for the schedule page */ +static void +schedule_page_class_init (SchedulePageClass *class) +{ + CompEditorPageClass *editor_page_class; + GtkObjectClass *object_class; + + editor_page_class = (CompEditorPageClass *) class; + object_class = (GtkObjectClass *) class; + + parent_class = gtk_type_class (TYPE_COMP_EDITOR_PAGE); + + editor_page_class->get_widget = schedule_page_get_widget; + editor_page_class->focus_main_widget = schedule_page_focus_main_widget; + editor_page_class->fill_widgets = schedule_page_fill_widgets; + editor_page_class->fill_component = schedule_page_fill_component; + editor_page_class->set_summary = NULL; + editor_page_class->set_dates = NULL; + + object_class->destroy = schedule_page_destroy; +} + +/* Object initialization function for the schedule page */ +static void +schedule_page_init (SchedulePage *spage) +{ + SchedulePagePrivate *priv; + + priv = g_new0 (SchedulePagePrivate, 1); + spage->priv = priv; + + priv->xml = NULL; + + priv->main = NULL; + + priv->updating = FALSE; +} + +/* Destroy handler for the schedule page */ +static void +schedule_page_destroy (GtkObject *object) +{ + SchedulePage *spage; + SchedulePagePrivate *priv; + + g_return_if_fail (object != NULL); + g_return_if_fail (IS_SCHEDULE_PAGE (object)); + + spage = SCHEDULE_PAGE (object); + priv = spage->priv; + + if (priv->xml) { + gtk_object_unref (GTK_OBJECT (priv->xml)); + priv->xml = NULL; + } + + gtk_object_unref (GTK_OBJECT (priv->model)); + + g_free (priv); + spage->priv = NULL; + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + + + +/* get_widget handler for the schedule page */ +static GtkWidget * +schedule_page_get_widget (CompEditorPage *page) +{ + SchedulePage *spage; + SchedulePagePrivate *priv; + + spage = SCHEDULE_PAGE (page); + priv = spage->priv; + + return priv->main; +} + +/* focus_main_widget handler for the schedule page */ +static void +schedule_page_focus_main_widget (CompEditorPage *page) +{ + SchedulePage *spage; + SchedulePagePrivate *priv; + + spage = SCHEDULE_PAGE (page); + priv = spage->priv; + + gtk_widget_grab_focus (GTK_WIDGET (priv->sel)); +} + +/* Fills the widgets with default values */ +static void +clear_widgets (SchedulePage *spage) +{ + SchedulePagePrivate *priv; + + priv = spage->priv; +} + +/* fill_widgets handler for the schedule page */ +static void +schedule_page_fill_widgets (CompEditorPage *page, CalComponent *comp) +{ + SchedulePage *spage; + SchedulePagePrivate *priv; + GSList *attendees; + + spage = SCHEDULE_PAGE (page); + priv = spage->priv; + + priv->updating = TRUE; + + /* Clean the screen */ + clear_widgets (spage); + + /* Attendees */ + cal_component_get_attendee_list (comp, &attendees); + + /* So the comp editor knows we need to send if anything changes */ + if (attendees != NULL) + comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (spage)); + + cal_component_free_attendee_list (attendees); + + priv->updating = FALSE; +} + +/* fill_component handler for the schedule page */ +static void +schedule_page_fill_component (CompEditorPage *page, CalComponent *comp) +{ + SchedulePage *spage; + SchedulePagePrivate *priv; + + spage = SCHEDULE_PAGE (page); + priv = spage->priv; +} + + + +/* Gets the widgets from the XML file and returns if they are all available. */ +static gboolean +get_widgets (SchedulePage *spage) +{ + SchedulePagePrivate *priv; + + priv = spage->priv; + +#define GW(name) glade_xml_get_widget (priv->xml, name) + + priv->main = GW ("schedule-page"); + if (!priv->main) + return FALSE; + + gtk_widget_ref (priv->main); + gtk_widget_unparent (priv->main); + +#undef GW + + return TRUE; +} + +/* Hooks the widget signals */ +static void +init_widgets (SchedulePage *spage) +{ + SchedulePagePrivate *priv; + + priv = spage->priv; + + gtk_signal_connect (GTK_OBJECT (priv->model), "model_row_changed", + GTK_SIGNAL_FUNC (model_row_changed_cb), spage); + gtk_signal_connect (GTK_OBJECT (priv->model), "model_rows_inserted", + GTK_SIGNAL_FUNC (row_count_changed_cb), spage); + gtk_signal_connect (GTK_OBJECT (priv->model), "model_rows_deleted", + GTK_SIGNAL_FUNC (row_count_changed_cb), spage); +} + + + +/** + * schedule_page_construct: + * @spage: An schedule page. + * + * Constructs an schedule page by loading its Glade data. + * + * Return value: The same object as @spage, or NULL if the widgets could not + * be created. + **/ +SchedulePage * +schedule_page_construct (SchedulePage *spage, EMeetingModel *emm) +{ + SchedulePagePrivate *priv; + + priv = spage->priv; + + priv->xml = glade_xml_new (EVOLUTION_GLADEDIR + "/schedule-page.glade", NULL); + if (!priv->xml) { + g_message ("schedule_page_construct(): " + "Could not load the Glade XML file!"); + return NULL; + } + + if (!get_widgets (spage)) { + g_message ("schedule_page_construct(): " + "Could not find all widgets in the XML file!"); + return NULL; + } + + /* Model */ + gtk_object_ref (GTK_OBJECT (emm)); + priv->model = emm; + + /* Selector */ + priv->sel = E_MEETING_TIME_SELECTOR (e_meeting_time_selector_new (emm)); + gtk_widget_show (GTK_WIDGET (priv->sel)); + gtk_box_pack_start (GTK_BOX (priv->main), GTK_WIDGET (priv->sel), TRUE, TRUE, 2); + + /* Init the widget signals */ + init_widgets (spage); + + return spage; +} + +/** + * schedule_page_new: + * + * Creates a new schedule page. + * + * Return value: A newly-created schedule page, or NULL if the page could + * not be created. + **/ +SchedulePage * +schedule_page_new (EMeetingModel *emm) +{ + SchedulePage *spage; + + spage = gtk_type_new (TYPE_SCHEDULE_PAGE); + if (!schedule_page_construct (spage, emm)) { + gtk_object_unref (GTK_OBJECT (spage)); + return NULL; + } + + return spage; +} + +static void +model_row_changed_cb (ETableModel *etm, int row, gpointer data) +{ + SchedulePage *spage = SCHEDULE_PAGE (data); + SchedulePagePrivate *priv; + + priv = spage->priv; + + if (!priv->updating) + comp_editor_page_notify_changed (COMP_EDITOR_PAGE (spage)); +} + +static void +row_count_changed_cb (ETableModel *etm, int row, int count, gpointer data) +{ + SchedulePage *spage = SCHEDULE_PAGE (data); + SchedulePagePrivate *priv; + + priv = spage->priv; + + if (!priv->updating) + comp_editor_page_notify_changed (COMP_EDITOR_PAGE (spage)); +} diff --git a/calendar/gui/dialogs/schedule-page.glade b/calendar/gui/dialogs/schedule-page.glade new file mode 100644 index 0000000000..95a2147659 --- /dev/null +++ b/calendar/gui/dialogs/schedule-page.glade @@ -0,0 +1,36 @@ +<?xml version="1.0"?> +<GTK-Interface> + +<project> + <name>task-details-page</name> + <program_name>task-details-page</program_name> + <directory></directory> + <source_directory>src</source_directory> + <pixmaps_directory>pixmaps</pixmaps_directory> + <language>C</language> + <gnome_support>True</gnome_support> + <gettext_support>True</gettext_support> +</project> + +<widget> + <class>GtkWindow</class> + <name>schedule-toplevel</name> + <visible>False</visible> + <title>window1</title> + <type>GTK_WINDOW_TOPLEVEL</type> + <position>GTK_WIN_POS_NONE</position> + <modal>False</modal> + <allow_shrink>False</allow_shrink> + <allow_grow>True</allow_grow> + <auto_shrink>False</auto_shrink> + + <widget> + <class>GtkVBox</class> + <name>schedule-page</name> + <border_width>4</border_width> + <homogeneous>False</homogeneous> + <spacing>4</spacing> + </widget> +</widget> + +</GTK-Interface> diff --git a/calendar/gui/dialogs/schedule-page.h b/calendar/gui/dialogs/schedule-page.h new file mode 100644 index 0000000000..b9a136e86c --- /dev/null +++ b/calendar/gui/dialogs/schedule-page.h @@ -0,0 +1,60 @@ +/* Evolution calendar - Scheduling page + * + * Copyright (C) 2001 Ximian, Inc. + * + * Authors: JP Rosevear <jpr@ximian.com> + * + * 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. + */ + +#ifndef SCHEDULE_PAGE_H +#define SCHEDULE_PAGE_H + +#include "../e-meeting-model.h" +#include "comp-editor-page.h" + +BEGIN_GNOME_DECLS + + + +#define TYPE_SCHEDULE_PAGE (schedule_page_get_type ()) +#define SCHEDULE_PAGE(obj) (GTK_CHECK_CAST ((obj), TYPE_SCHEDULE_PAGE, SchedulePage)) +#define SCHEDULE_PAGE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), TYPE_SCHEDULE_PAGE, SchedulePageClass)) +#define IS_SCHEDULE_PAGE(obj) (GTK_CHECK_TYPE ((obj), TYPE_SCHEDULE_PAGE)) +#define IS_SCHEDULE_PAGE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((obj), TYPE_SCHEDULE_PAGE)) + +typedef struct _SchedulePagePrivate SchedulePagePrivate; + +typedef struct { + CompEditorPage page; + + /* Private data */ + SchedulePagePrivate *priv; +} SchedulePage; + +typedef struct { + CompEditorPageClass parent_class; +} SchedulePageClass; + + +GtkType schedule_page_get_type (void); +SchedulePage *schedule_page_construct (SchedulePage *mpage, EMeetingModel *emm); +SchedulePage *schedule_page_new (EMeetingModel *emm); + + + +END_GNOME_DECLS + +#endif diff --git a/calendar/gui/dialogs/task-editor.c b/calendar/gui/dialogs/task-editor.c index 22b89e5ce5..c7879a1986 100644 --- a/calendar/gui/dialogs/task-editor.c +++ b/calendar/gui/dialogs/task-editor.c @@ -41,6 +41,8 @@ struct _TaskEditorPrivate { TaskDetailsPage *task_details_page; MeetingPage *meet_page; + EMeetingModel *model; + gboolean meeting_shown; }; @@ -155,7 +157,9 @@ task_editor_init (TaskEditor *te) COMP_EDITOR_PAGE (priv->task_details_page), _("Details")); - priv->meet_page = meeting_page_new (); + priv->model = E_MEETING_MODEL (e_meeting_model_new ()); + + priv->meet_page = meeting_page_new (priv->model); comp_editor_append_page (COMP_EDITOR (te), COMP_EDITOR_PAGE (priv->meet_page), _("Assignment")); @@ -207,6 +211,8 @@ task_editor_destroy (GtkObject *object) gtk_object_unref (GTK_OBJECT (priv->task_details_page)); gtk_object_unref (GTK_OBJECT (priv->meet_page)); + gtk_object_unref (GTK_OBJECT (priv->model)); + if (GTK_OBJECT_CLASS (parent_class)->destroy) (* GTK_OBJECT_CLASS (parent_class)->destroy) (object); } diff --git a/calendar/gui/e-itip-control.c b/calendar/gui/e-itip-control.c index bc528ac8c0..0045ebb0ae 100644 --- a/calendar/gui/e-itip-control.c +++ b/calendar/gui/e-itip-control.c @@ -523,10 +523,6 @@ write_error_html (EItipControl *itip, gchar *itip_err) { EItipControlPrivate *priv; GtkHTMLStream *html_stream; - CalComponentText text; - CalComponentOrganizer organizer; - CalComponentAttendee *attendee; - GSList *attendees, *l = NULL; gchar *html; priv = itip->priv; diff --git a/calendar/gui/e-meeting-attendee.c b/calendar/gui/e-meeting-attendee.c new file mode 100644 index 0000000000..08ec9a1152 --- /dev/null +++ b/calendar/gui/e-meeting-attendee.c @@ -0,0 +1,998 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-attendee.c + * + * Copyright (C) 2001 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. + * + * Author: JP Rosevear + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdlib.h> +#include <gtk/gtk.h> +#include "e-meeting-attendee.h" + +struct _EMeetingAttendeePrivate { + EMeetingAttendeeType type; + + gchar *address; + gchar *member; + + icalparameter_cutype cutype; + icalparameter_role role; + + gboolean rsvp; + + gchar *delto; + gchar *delfrom; + + icalparameter_partstat status; + + gchar *sentby; + gchar *cn; + gchar *language; + + gboolean has_calendar_info; + + GArray *busy_periods; + gboolean busy_periods_sorted; + + EMeetingTime busy_periods_start; + EMeetingTime busy_periods_end; + gboolean start_busy_range_set; + gboolean end_busy_range_set; + + gint longest_period_in_days; +}; + +enum { + CHANGED, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL]; + +static void class_init (EMeetingAttendeeClass *klass); +static void init (EMeetingAttendee *ia); +static void destroy (GtkObject *obj); + + +static GtkObjectClass *parent_class = NULL; + + +GtkType +e_meeting_attendee_get_type (void) +{ + static GtkType type = 0; + + if (type == 0) + { + static const GtkTypeInfo info = + { + "EMeetingAttendee", + sizeof (EMeetingAttendee), + sizeof (EMeetingAttendeeClass), + (GtkClassInitFunc) class_init, + (GtkObjectInitFunc) init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + type = gtk_type_unique (gtk_object_get_type (), &info); + } + + return type; +} + +static void +class_init (EMeetingAttendeeClass *klass) +{ + GtkObjectClass *object_class; + + object_class = GTK_OBJECT_CLASS (klass); + + parent_class = gtk_type_class (gtk_object_get_type ()); + + signals[CHANGED] = + gtk_signal_new ("changed", + GTK_RUN_FIRST, + object_class->type, + GTK_SIGNAL_OFFSET (EMeetingAttendeeClass, changed), + gtk_marshal_NONE__NONE, + GTK_TYPE_NONE, 0); + + gtk_object_class_add_signals (object_class, signals, LAST_SIGNAL); + + object_class->destroy = destroy; +} + +static gchar * +string_test (gchar *string) +{ + return string != NULL ? string : g_strdup (""); +} + +static gboolean +string_is_set (gchar *string) +{ + if (string != NULL && *string != '\0') + return TRUE; + + return FALSE; +} + +static void +notify_changed (EMeetingAttendee *ia) +{ + gtk_signal_emit (GTK_OBJECT (ia), signals[CHANGED]); +} + +static void +init (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = g_new0 (EMeetingAttendeePrivate, 1); + + ia->priv = priv; + + priv->type = E_MEETING_ATTENDEE_REQUIRED_PERSON; + + priv->address = string_test (NULL); + priv->member = string_test (NULL); + + priv->cutype = ICAL_CUTYPE_NONE; + priv->role = ICAL_ROLE_NONE; + + priv->rsvp = FALSE; + + priv->delto = string_test (NULL); + priv->delfrom = string_test (NULL); + + priv->status = ICAL_PARTSTAT_NONE; + + priv->sentby = string_test (NULL); + priv->cn = string_test (NULL); + priv->language = string_test (NULL); + + priv->has_calendar_info = FALSE; + + priv->busy_periods = g_array_new (FALSE, FALSE, sizeof (EMeetingFreeBusyPeriod)); + priv->busy_periods_sorted = FALSE; + + g_date_clear (&priv->busy_periods_start.date, 1); + priv->busy_periods_start.hour = 0; + priv->busy_periods_start.minute = 0; + + g_date_clear (&priv->busy_periods_end.date, 1); + priv->busy_periods_end.hour = 0; + priv->busy_periods_end.minute = 0; + + priv->start_busy_range_set = FALSE; + priv->end_busy_range_set = FALSE; + + priv->longest_period_in_days = 0; +} + + +static void +destroy (GtkObject *obj) +{ + EMeetingAttendee *ia = E_MEETING_ATTENDEE (obj); + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + g_free (priv->address); + g_free (priv->member); + + g_free (priv->delto); + g_free (priv->delfrom); + + g_free (priv->sentby); + g_free (priv->cn); + g_free (priv->language); + + g_array_free (priv->busy_periods, TRUE); + + g_free (priv); +} + +GtkObject * +e_meeting_attendee_new (void) +{ + return gtk_type_new (E_TYPE_MEETING_ATTENDEE); +} + +GtkObject * +e_meeting_attendee_new_from_cal_component_attendee (CalComponentAttendee *ca) +{ + EMeetingAttendee *ia; + + ia = E_MEETING_ATTENDEE (gtk_type_new (E_TYPE_MEETING_ATTENDEE)); + + e_meeting_attendee_set_address (ia, g_strdup (ca->value)); + e_meeting_attendee_set_member (ia, g_strdup (ca->member)); + e_meeting_attendee_set_cutype (ia, ca->cutype); + e_meeting_attendee_set_role (ia, ca->role); + e_meeting_attendee_set_status (ia, ca->status); + e_meeting_attendee_set_rsvp (ia, ca->rsvp); + e_meeting_attendee_set_delto (ia, g_strdup (ca->delto)); + e_meeting_attendee_set_delfrom (ia, g_strdup (ca->delfrom)); + e_meeting_attendee_set_sentby (ia, g_strdup (ca->sentby)); + e_meeting_attendee_set_cn (ia, g_strdup (ca->cn)); + e_meeting_attendee_set_language (ia, g_strdup (ca->language)); + + return GTK_OBJECT (ia); +} + +CalComponentAttendee * +e_meeting_attendee_as_cal_component_attendee (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + CalComponentAttendee *ca; + + priv = ia->priv; + + ca = g_new0 (CalComponentAttendee, 1); + + ca->value = priv->address; + ca->member = string_is_set (priv->member) ? priv->member : NULL; + ca->cutype= priv->cutype; + ca->role = priv->role; + ca->status = priv->status; + ca->rsvp = priv->rsvp; + ca->delto = string_is_set (priv->delto) ? priv->delto : NULL; + ca->delfrom = string_is_set (priv->delfrom) ? priv->delfrom : NULL; + ca->sentby = string_is_set (priv->sentby) ? priv->sentby : NULL; + ca->cn = string_is_set (priv->cn) ? priv->cn : NULL; + ca->language = string_is_set (priv->language) ? priv->language : NULL; + + return ca; +} + + +const gchar * +e_meeting_attendee_get_address (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->address; +} + +void +e_meeting_attendee_set_address (EMeetingAttendee *ia, gchar *address) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + if (priv->address != NULL) + g_free (priv->address); + + priv->address = string_test (address); + + notify_changed (ia); +} + +gboolean +e_meeting_attendee_is_set_address (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return string_is_set (priv->address); +} + +const gchar * +e_meeting_attendee_get_member (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->member; +} + +void +e_meeting_attendee_set_member (EMeetingAttendee *ia, gchar *member) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + if (priv->member != NULL) + g_free (priv->member); + + priv->member = string_test (member); + + notify_changed (ia); +} + +gboolean +e_meeting_attendee_is_set_member (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return string_is_set (priv->member); +} + +icalparameter_cutype +e_meeting_attendee_get_cutype (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->cutype; +} + +void +e_meeting_attendee_set_cutype (EMeetingAttendee *ia, icalparameter_cutype cutype) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + priv->cutype = cutype; + + notify_changed (ia); +} + +icalparameter_role +e_meeting_attendee_get_role (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->role; +} + +void +e_meeting_attendee_set_role (EMeetingAttendee *ia, icalparameter_role role) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + priv->role = role; + + notify_changed (ia); +} + +gboolean +e_meeting_attendee_get_rsvp (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->rsvp; +} + +void +e_meeting_attendee_set_rsvp (EMeetingAttendee *ia, gboolean rsvp) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + priv->rsvp = rsvp; + + notify_changed (ia); +} + +const gchar * +e_meeting_attendee_get_delto (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->delto; +} + +void +e_meeting_attendee_set_delto (EMeetingAttendee *ia, gchar *delto) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + if (priv->delto != NULL) + g_free (priv->delto); + + priv->delto = string_test (delto); + + notify_changed (ia); +} + +gboolean +e_meeting_attendee_is_set_delto (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return string_is_set (priv->delto); +} + +const gchar * +e_meeting_attendee_get_delfrom (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->delfrom; +} + +void +e_meeting_attendee_set_delfrom (EMeetingAttendee *ia, gchar *delfrom) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + if (priv->delfrom != NULL) + g_free (priv->delfrom); + + priv->delfrom = string_test (delfrom); + + notify_changed (ia); +} + +gboolean +e_meeting_attendee_is_set_delfrom (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return string_is_set (priv->delfrom); +} + +icalparameter_partstat +e_meeting_attendee_get_status (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->status; +} + +void +e_meeting_attendee_set_status (EMeetingAttendee *ia, icalparameter_partstat status) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + priv->status = status; + + notify_changed (ia); +} + +const gchar * +e_meeting_attendee_get_sentby (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->sentby; +} + +void +e_meeting_attendee_set_sentby (EMeetingAttendee *ia, gchar *sentby) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + if (priv->sentby != NULL) + g_free (priv->sentby); + + priv->sentby = string_test (sentby); + + notify_changed (ia); +} + +gboolean +e_meeting_attendee_is_set_sentby (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return string_is_set (priv->sentby); +} + +const gchar * +e_meeting_attendee_get_cn (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->cn; +} + +void +e_meeting_attendee_set_cn (EMeetingAttendee *ia, gchar *cn) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + if (priv->cn != NULL) + g_free (priv->cn); + + priv->cn = string_test (cn); + + notify_changed (ia); +} + +gboolean +e_meeting_attendee_is_set_cn (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return string_is_set (priv->cn); +} + +const gchar * +e_meeting_attendee_get_language (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->language; +} + +void +e_meeting_attendee_set_language (EMeetingAttendee *ia, gchar *language) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + if (priv->language != NULL) + g_free (priv->language); + + priv->language = string_test (language); + + notify_changed (ia); +} + +gboolean +e_meeting_attendee_is_set_language (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return string_is_set (priv->language); +} + +EMeetingAttendeeType +e_meeting_attendee_get_atype (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->type; +} + +void +e_meeting_attendee_set_atype (EMeetingAttendee *ia, EMeetingAttendeeType type) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + priv->type = type; +} + + +static gint +compare_times (EMeetingTime *time1, + EMeetingTime *time2) +{ + gint day_comparison; + + day_comparison = g_date_compare (&time1->date, + &time2->date); + if (day_comparison != 0) + return day_comparison; + + if (time1->hour < time2->hour) + return -1; + if (time1->hour > time2->hour) + return 1; + + if (time1->minute < time2->minute) + return -1; + if (time1->minute > time2->minute) + return 1; + + /* The start times are exactly the same. */ + return 0; +} + +static gint +compare_period_starts (const void *arg1, + const void *arg2) +{ + EMeetingFreeBusyPeriod *period1, *period2; + + period1 = (EMeetingFreeBusyPeriod *) arg1; + period2 = (EMeetingFreeBusyPeriod *) arg2; + + return compare_times (&period1->start, &period2->start); +} + +static void +ensure_periods_sorted (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + if (priv->busy_periods_sorted) + return; + + qsort (priv->busy_periods->data, priv->busy_periods->len, + sizeof (EMeetingFreeBusyPeriod), + compare_period_starts); + + priv->busy_periods_sorted = TRUE; +} + +gboolean +e_meeting_attendee_get_has_calendar_info (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->has_calendar_info; +} + +void +e_meeting_attendee_set_has_calendar_info (EMeetingAttendee *ia, gboolean has_calendar_info) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + priv->has_calendar_info = has_calendar_info; +} + +const GArray * +e_meeting_attendee_get_busy_periods (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + ensure_periods_sorted (ia); + + return priv->busy_periods; +} + +gint +e_meeting_attendee_find_first_busy_period (EMeetingAttendee *ia, GDate *date) +{ + EMeetingAttendeePrivate *priv; + EMeetingFreeBusyPeriod *period; + gint lower, upper, middle = 0, cmp = 0; + GDate tmp_date; + + priv = ia->priv; + + /* Make sure the busy periods have been sorted. */ + ensure_periods_sorted (ia); + + /* Calculate the first day which could have a busy period which + continues onto our given date. */ + tmp_date = *date; + g_date_subtract_days (&tmp_date, priv->longest_period_in_days); + + /* We want the first busy period which starts on tmp_date. */ + lower = 0; + upper = priv->busy_periods->len; + + if (upper == 0) + return -1; + + while (lower < upper) { + middle = (lower + upper) >> 1; + + period = &g_array_index (priv->busy_periods, + EMeetingFreeBusyPeriod, middle); + + cmp = g_date_compare (&tmp_date, &period->start.date); + + if (cmp == 0) + break; + else if (cmp < 0) + upper = middle; + else + lower = middle + 1; + } + + /* There may be several busy periods on the same day so we step + backwards to the first one. */ + if (cmp == 0) { + while (middle > 0) { + period = &g_array_index (priv->busy_periods, + EMeetingFreeBusyPeriod, middle - 1); + if (g_date_compare (&tmp_date, &period->start.date) != 0) + break; + middle--; + } + } else if (cmp > 0) { + /* This means we couldn't find a period on the given day, and + the last one we looked at was before it, so if there are + any more periods after this one we return it. */ + middle++; + if (priv->busy_periods->len <= middle) + return -1; + } + + return middle; +} + +gboolean +e_meeting_attendee_add_busy_period (EMeetingAttendee *ia, + gint start_year, + gint start_month, + gint start_day, + gint start_hour, + gint start_minute, + gint end_year, + gint end_month, + gint end_day, + gint end_hour, + gint end_minute, + EMeetingFreeBusyType busy_type) +{ + EMeetingAttendeePrivate *priv; + EMeetingFreeBusyPeriod period; + gint period_in_days; + + g_return_val_if_fail (ia != NULL, FALSE); + g_return_val_if_fail (E_IS_MEETING_ATTENDEE (ia), FALSE); + g_return_val_if_fail (busy_type >= 0, FALSE); + g_return_val_if_fail (busy_type < E_MEETING_FREE_BUSY_LAST, FALSE); + + priv = ia->priv; + + /* Check the dates are valid. */ + if (!g_date_valid_dmy (start_day, start_month, start_year)) + return FALSE; + if (!g_date_valid_dmy (end_day, end_month, end_year)) + return FALSE; + if (start_hour < 0 || start_hour > 23) + return FALSE; + if (end_hour < 0 || end_hour > 23) + return FALSE; + if (start_minute < 0 || start_minute > 59) + return FALSE; + if (end_minute < 0 || end_minute > 59) + return FALSE; + + g_date_clear (&period.start.date, 1); + g_date_clear (&period.end.date, 1); + g_date_set_dmy (&period.start.date, start_day, start_month, start_year); + g_date_set_dmy (&period.end.date, end_day, end_month, end_year); + period.start.hour = start_hour; + period.start.minute = start_minute; + period.end.hour = end_hour; + period.end.minute = end_minute; + period.busy_type = busy_type; + + /* Check that the start time is before or equal to the end time. */ + if (compare_times (&period.start, &period.end) > 0) + return FALSE; + + /* If the busy range is not set elsewhere, track it as best we can */ + if (!priv->start_busy_range_set) { + if (!g_date_valid (&priv->busy_periods_start.date)) { + priv->busy_periods_start.date = period.start.date; + priv->busy_periods_start.hour = period.start.hour; + priv->busy_periods_start.minute = period.start.minute; + } else { + switch (g_date_compare (&period.start.date, &priv->busy_periods_start.date)) { + case -1: + priv->busy_periods_start.date = period.start.date; + priv->busy_periods_start.hour = period.start.hour; + priv->busy_periods_start.minute = period.start.minute; + break; + case 0: + if (period.start.hour < priv->busy_periods_start.hour + || (period.start.hour == priv->busy_periods_start.hour + && period.start.minute < priv->busy_periods_start.minute)) { + priv->busy_periods_start.date = period.start.date; + priv->busy_periods_start.hour = period.start.hour; + priv->busy_periods_start.minute = period.start.minute; + break; + } + break; + } + } + } + if (!priv->end_busy_range_set) { + if (!g_date_valid (&priv->busy_periods_end.date)) { + priv->busy_periods_end.date = period.end.date; + priv->busy_periods_end.hour = period.end.hour; + priv->busy_periods_end.minute = period.end.minute; + } else { + switch (g_date_compare (&period.end.date, &priv->busy_periods_end.date)) { + case 0: + if (period.end.hour > priv->busy_periods_end.hour + || (period.end.hour == priv->busy_periods_end.hour + && period.end.minute > priv->busy_periods_end.minute)) { + priv->busy_periods_end.date = period.end.date; + priv->busy_periods_end.hour = period.end.hour; + priv->busy_periods_end.minute = period.end.minute; + break; + } + break; + case 1: + priv->busy_periods_end.date = period.end.date; + priv->busy_periods_end.hour = period.end.hour; + priv->busy_periods_end.minute = period.end.minute; + break; + } + } + } + + g_array_append_val (priv->busy_periods, period); + priv->has_calendar_info = TRUE; + priv->busy_periods_sorted = FALSE; + + period_in_days = g_date_julian (&period.end.date) - g_date_julian (&period.start.date) + 1; + priv->longest_period_in_days = MAX (priv->longest_period_in_days, period_in_days); + + return TRUE; +} + +EMeetingTime +e_meeting_attendee_get_start_busy_range (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->busy_periods_start; +} + +EMeetingTime +e_meeting_attendee_get_end_busy_range (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + priv = ia->priv; + + return priv->busy_periods_end; +} + +gboolean +e_meeting_attendee_set_start_busy_range (EMeetingAttendee *ia, + gint start_year, + gint start_month, + gint start_day, + gint start_hour, + gint start_minute) +{ + EMeetingAttendeePrivate *priv; + + g_return_val_if_fail (E_IS_MEETING_ATTENDEE (ia), FALSE); + + priv = ia->priv; + + /* Check the dates are valid. */ + if (!g_date_valid_dmy (start_day, start_month, start_year)) + return FALSE; + if (start_hour < 0 || start_hour > 23) + return FALSE; + if (start_minute < 0 || start_minute > 59) + return FALSE; + + g_date_clear (&priv->busy_periods_start.date, 1); + g_date_set_dmy (&priv->busy_periods_start.date, + start_day, start_month, start_year); + priv->busy_periods_start.hour = start_hour; + priv->busy_periods_start.minute = start_minute; + + priv->start_busy_range_set = TRUE; + + return TRUE; +} + +gboolean +e_meeting_attendee_set_end_busy_range (EMeetingAttendee *ia, + gint end_year, + gint end_month, + gint end_day, + gint end_hour, + gint end_minute) +{ + EMeetingAttendeePrivate *priv; + + g_return_val_if_fail (E_IS_MEETING_ATTENDEE (ia), FALSE); + + priv = ia->priv; + + /* Check the dates are valid. */ + if (!g_date_valid_dmy (end_day, end_month, end_year)) + return FALSE; + if (end_hour < 0 || end_hour > 23) + return FALSE; + if (end_minute < 0 || end_minute > 59) + return FALSE; + + g_date_clear (&priv->busy_periods_end.date, 1); + g_date_set_dmy (&priv->busy_periods_end.date, + end_day, end_month, end_year); + priv->busy_periods_end.hour = end_hour; + priv->busy_periods_end.minute = end_minute; + + priv->end_busy_range_set = TRUE; + + return TRUE; +} + +/* Clears all busy times for the given attendee. */ +void +e_meeting_attendee_clear_busy_periods (EMeetingAttendee *ia) +{ + EMeetingAttendeePrivate *priv; + + g_return_if_fail (E_IS_MEETING_ATTENDEE (ia)); + + priv = ia->priv; + + g_array_set_size (priv->busy_periods, 0); + priv->busy_periods_sorted = TRUE; + + g_date_clear (&priv->busy_periods_start.date, 1); + priv->busy_periods_start.hour = 0; + priv->busy_periods_start.minute = 0; + + g_date_clear (&priv->busy_periods_end.date, 1); + priv->busy_periods_end.hour = 0; + priv->busy_periods_end.minute = 0; + + priv->longest_period_in_days = 0; +} diff --git a/calendar/gui/e-meeting-attendee.h b/calendar/gui/e-meeting-attendee.h new file mode 100644 index 0000000000..97de14f8e9 --- /dev/null +++ b/calendar/gui/e-meeting-attendee.h @@ -0,0 +1,164 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-meeting_attendee.h + * + * Copyright (C) 2001 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. + * + * Author: JP Rosevear + */ + +#ifndef _E_MEETING_ATTENDEE_H_ +#define _E_MEETING_ATTENDEE_H_ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <cal-util/cal-component.h> +#include "e-meeting-types.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#define E_TYPE_MEETING_ATTENDEE (e_meeting_attendee_get_type ()) +#define E_MEETING_ATTENDEE(obj) (GTK_CHECK_CAST ((obj), E_TYPE_MEETING_ATTENDEE, EMeetingAttendee)) +#define E_MEETING_ATTENDEE_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), E_TYPE_MEETING_ATTENDEE, EMeetingAttendeeClass)) +#define E_IS_MEETING_ATTENDEE(obj) (GTK_CHECK_TYPE ((obj), E_TYPE_MEETING_ATTENDEE)) +#define E_IS_MEETING_ATTENDEE_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((obj), E_TYPE_MEETING_ATTENDEE)) + + +typedef struct _EMeetingAttendee EMeetingAttendee; +typedef struct _EMeetingAttendeePrivate EMeetingAttendeePrivate; +typedef struct _EMeetingAttendeeClass EMeetingAttendeeClass; + +/* These specify the type of attendee. Either a person or a resource (e.g. a + meeting room). These are used for the Autopick options, where the user can + ask for a time when, for example, all people and one resource are free. + The default is E_MEETING_ATTENDEE_REQUIRED_PERSON. */ +typedef enum +{ + E_MEETING_ATTENDEE_REQUIRED_PERSON, + E_MEETING_ATTENDEE_OPTIONAL_PERSON, + E_MEETING_ATTENDEE_RESOURCE +} EMeetingAttendeeType; + +struct _EMeetingAttendee { + GtkObject parent; + + EMeetingAttendeePrivate *priv; +}; + +struct _EMeetingAttendeeClass { + GtkObjectClass parent_class; + + void (* changed) (EMeetingAttendee *ia); +}; + + +GtkType e_meeting_attendee_get_type (void); +GtkObject *e_meeting_attendee_new (void); +GtkObject *e_meeting_attendee_new_from_cal_component_attendee (CalComponentAttendee *ca); + +CalComponentAttendee *e_meeting_attendee_as_cal_component_attendee (EMeetingAttendee *ia); + +const gchar *e_meeting_attendee_get_address (EMeetingAttendee *ia); +void e_meeting_attendee_set_address (EMeetingAttendee *ia, gchar *address); +gboolean e_meeting_attendee_is_set_address (EMeetingAttendee *ia); + +const gchar *e_meeting_attendee_get_member (EMeetingAttendee *ia); +void e_meeting_attendee_set_member (EMeetingAttendee *ia, gchar *member); +gboolean e_meeting_attendee_is_set_member (EMeetingAttendee *ia); + +icalparameter_cutype e_meeting_attendee_get_cutype (EMeetingAttendee *ia); +void e_meeting_attendee_set_cutype (EMeetingAttendee *ia, icalparameter_cutype cutype); + +icalparameter_role e_meeting_attendee_get_role (EMeetingAttendee *ia); +void e_meeting_attendee_set_role (EMeetingAttendee *ia, icalparameter_role role); + +gboolean e_meeting_attendee_get_rsvp (EMeetingAttendee *ia); +void e_meeting_attendee_set_rsvp (EMeetingAttendee *ia, gboolean rsvp); + +const gchar *e_meeting_attendee_get_delto (EMeetingAttendee *ia); +void e_meeting_attendee_set_delto (EMeetingAttendee *ia, gchar *delto); +gboolean e_meeting_attendee_is_set_delto (EMeetingAttendee *ia); + +const gchar *e_meeting_attendee_get_delfrom (EMeetingAttendee *ia); +void e_meeting_attendee_set_delfrom (EMeetingAttendee *ia, gchar *delfrom); +gboolean e_meeting_attendee_is_set_delfrom (EMeetingAttendee *ia); + +icalparameter_partstat e_meeting_attendee_get_status (EMeetingAttendee *ia); +void e_meeting_attendee_set_status (EMeetingAttendee *ia, icalparameter_partstat status); + +const gchar *e_meeting_attendee_get_sentby (EMeetingAttendee *ia); +void e_meeting_attendee_set_sentby (EMeetingAttendee *ia, gchar *sentby); +gboolean e_meeting_attendee_is_set_sentby (EMeetingAttendee *ia); + +const gchar *e_meeting_attendee_get_cn (EMeetingAttendee *ia); +void e_meeting_attendee_set_cn (EMeetingAttendee *ia, gchar *cn); +gboolean e_meeting_attendee_is_set_cn (EMeetingAttendee *ia); + +const gchar *e_meeting_attendee_get_language (EMeetingAttendee *ia); +void e_meeting_attendee_set_language (EMeetingAttendee *ia, gchar *language); +gboolean e_meeting_attendee_is_set_language (EMeetingAttendee *ia); + +EMeetingAttendeeType e_meeting_attendee_get_atype (EMeetingAttendee *ia); +void e_meeting_attendee_set_atype (EMeetingAttendee *ia, EMeetingAttendeeType type); + +gboolean e_meeting_attendee_get_has_calendar_info (EMeetingAttendee *ia); +void e_meeting_attendee_set_has_calendar_info (EMeetingAttendee *ia, gboolean has_calendar_info); + +const GArray *e_meeting_attendee_get_busy_periods (EMeetingAttendee *ia); +gint e_meeting_attendee_find_first_busy_period (EMeetingAttendee *ia, GDate *date); +gboolean e_meeting_attendee_add_busy_period (EMeetingAttendee *ia, + gint start_year, + gint start_month, + gint start_day, + gint start_hour, + gint start_minute, + gint end_year, + gint end_month, + gint end_day, + gint end_hour, + gint end_minute, + EMeetingFreeBusyType busy_type); + +EMeetingTime e_meeting_attendee_get_start_busy_range (EMeetingAttendee *ia); +EMeetingTime e_meeting_attendee_get_end_busy_range (EMeetingAttendee *ia); + +gboolean e_meeting_attendee_set_start_busy_range (EMeetingAttendee *ia, + gint start_year, + gint start_month, + gint start_day, + gint start_hour, + gint start_minute); +gboolean e_meeting_attendee_set_end_busy_range (EMeetingAttendee *ia, + gint end_year, + gint end_month, + gint end_day, + gint end_hour, + gint end_minute); + +void e_meeting_attendee_clear_busy_periods (EMeetingAttendee *ia); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _E_MEETING_ATTENDEE_H_ */ diff --git a/calendar/gui/e-meeting-model.c b/calendar/gui/e-meeting-model.c new file mode 100644 index 0000000000..f492c89a07 --- /dev/null +++ b/calendar/gui/e-meeting-model.c @@ -0,0 +1,1321 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* itip-model.c + * + * Copyright (C) 2001 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. + * + * Author: JP Rosevear + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include <libgnome/gnome-defs.h> +#include <libgnome/gnome-i18n.h> +#include <libgnome/gnome-util.h> +#include <libgnomevfs/gnome-vfs.h> +#include <gal/e-table/e-cell-text.h> +#include <gal/e-table/e-cell-popup.h> +#include <gal/e-table/e-cell-combo.h> +#include <e-book.h> +#include <e-card-types.h> +#include <e-card-cursor.h> +#include <e-card.h> +#include <e-card-simple.h> +#include <cal-util/cal-component.h> +#include <cal-util/cal-util.h> +#include <cal-util/timeutil.h> +#include "calendar-config.h" +#include "itip-utils.h" +#include "e-meeting-attendee.h" +#include "e-meeting-model.h" + +enum columns { + ITIP_ADDRESS_COL, + ITIP_MEMBER_COL, + ITIP_TYPE_COL, + ITIP_ROLE_COL, + ITIP_RSVP_COL, + ITIP_DELTO_COL, + ITIP_DELFROM_COL, + ITIP_STATUS_COL, + ITIP_CN_COL, + ITIP_LANGUAGE_COL, + ITIP_COLUMN_COUNT +}; + +struct _EMeetingModelPrivate +{ + GPtrArray *attendees; + + CalClient *client; + + EBook *ebook; + gboolean book_loaded; + gboolean book_load_wait; + + GList *refresh_callbacks; + GList *refresh_data; + gint refresh_count; + gboolean refreshing; +}; + +#define BUF_SIZE 1024 + +typedef struct _EMeetingModelAttendeeRefreshData EMeetingModelAttendeeRefreshData; +struct _EMeetingModelAttendeeRefreshData { + char buffer[BUF_SIZE]; + GString *string; + + EMeetingAttendee *ia; +}; + +typedef struct _EMeetingModelRefreshData EMeetingModelRefreshData; +struct _EMeetingModelRefreshData { + EMeetingModel *im; + + EMeetingModelAttendeeRefreshData attendee_data; +}; + + +static void class_init (EMeetingModelClass *klass); +static void init (EMeetingModel *model); +static void destroy (GtkObject *obj); + +static void attendee_changed_cb (EMeetingAttendee *ia, gpointer data); + +static ETableModelClass *parent_class = NULL; + + +GtkType +e_meeting_model_get_type (void) +{ + static GtkType type = 0; + + if (type == 0) + { + static const GtkTypeInfo info = + { + "EMeetingModel", + sizeof (EMeetingModel), + sizeof (EMeetingModelClass), + (GtkClassInitFunc) class_init, + (GtkObjectInitFunc) init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + type = gtk_type_unique (e_table_model_get_type (), &info); + } + + return type; +} + +static void +book_open_cb (EBook *book, EBookStatus status, gpointer data) +{ + EMeetingModel *im = E_MEETING_MODEL (data); + EMeetingModelPrivate *priv; + + priv = im->priv; + + if (status == E_BOOK_STATUS_SUCCESS) + priv->book_loaded = TRUE; + else + g_warning ("Book not loaded"); + + if (priv->book_load_wait) { + priv->book_load_wait = FALSE; + gtk_main_quit (); + } +} + +static int +start_addressbook_server (EMeetingModel *im) +{ + EMeetingModelPrivate *priv; + gchar *uri, *path; + + priv = im->priv; + + priv->ebook = e_book_new (); + + path = g_concat_dir_and_file (g_get_home_dir (), + "evolution/local/Contacts/addressbook.db"); + uri = g_strdup_printf ("file://%s", path); + g_free (path); + + e_book_load_uri (priv->ebook, uri, book_open_cb, im); + + g_free (uri); +} + +static EMeetingAttendee * +find_match (EMeetingModel *im, const char *address, int *pos) +{ + EMeetingModelPrivate *priv; + EMeetingAttendee *ia; + const gchar *ia_address; + int i; + + priv = im->priv; + + if (address == NULL) + return NULL; + + /* Make sure we can add the new delegatee person */ + for (i = 0; i < priv->attendees->len; i++) { + ia = g_ptr_array_index (priv->attendees, i); + + ia_address = e_meeting_attendee_get_address (ia); + if (ia_address != NULL && !g_strcasecmp (itip_strip_mailto (ia_address), itip_strip_mailto (address))) { + if (pos != NULL) + *pos = i; + return ia; + } + } + + return NULL; +} + +static icalparameter_cutype +text_to_type (const char *type) +{ + if (!g_strcasecmp (type, _("Individual"))) + return ICAL_CUTYPE_INDIVIDUAL; + else if (!g_strcasecmp (type, _("Group"))) + return ICAL_CUTYPE_GROUP; + else if (!g_strcasecmp (type, _("Resource"))) + return ICAL_CUTYPE_RESOURCE; + else if (!g_strcasecmp (type, _("Room"))) + return ICAL_CUTYPE_ROOM; + else + return ICAL_CUTYPE_NONE; +} + +static char * +type_to_text (icalparameter_cutype type) +{ + switch (type) { + case ICAL_CUTYPE_INDIVIDUAL: + return _("Individual"); + case ICAL_CUTYPE_GROUP: + return _("Group"); + case ICAL_CUTYPE_RESOURCE: + return _("Resource"); + case ICAL_CUTYPE_ROOM: + return _("Room"); + default: + return _("Unknown"); + } + + return NULL; + +} + +static icalparameter_role +text_to_role (const char *role) +{ + if (!g_strcasecmp (role, _("Chair"))) + return ICAL_ROLE_CHAIR; + else if (!g_strcasecmp (role, _("Required Participant"))) + return ICAL_ROLE_REQPARTICIPANT; + else if (!g_strcasecmp (role, _("Optional Participant"))) + return ICAL_ROLE_OPTPARTICIPANT; + else if (!g_strcasecmp (role, _("Non-Participant"))) + return ICAL_ROLE_NONPARTICIPANT; + else + return ICAL_ROLE_NONE; +} + +static char * +role_to_text (icalparameter_role role) +{ + switch (role) { + case ICAL_ROLE_CHAIR: + return _("Chair"); + case ICAL_ROLE_REQPARTICIPANT: + return _("Required Participant"); + case ICAL_ROLE_OPTPARTICIPANT: + return _("Optional Participant"); + case ICAL_ROLE_NONPARTICIPANT: + return _("Non-Participant"); + default: + return _("Unknown"); + } + + return NULL; +} + +static gboolean +text_to_boolean (const char *role) +{ + if (!g_strcasecmp (role, _("Yes"))) + return TRUE; + else + return FALSE; +} + +static char * +boolean_to_text (gboolean b) +{ + if (b) + return _("Yes"); + else + return _("No"); +} + +static icalparameter_partstat +text_to_partstat (const char *partstat) +{ + if (!g_strcasecmp (partstat, _("Needs Action"))) + return ICAL_PARTSTAT_NEEDSACTION; + else if (!g_strcasecmp (partstat, _("Accepted"))) + return ICAL_PARTSTAT_ACCEPTED; + else if (!g_strcasecmp (partstat, _("Declined"))) + return ICAL_PARTSTAT_DECLINED; + else if (!g_strcasecmp (partstat, _("Tentative"))) + return ICAL_PARTSTAT_TENTATIVE; + else if (!g_strcasecmp (partstat, _("Delegated"))) + return ICAL_PARTSTAT_DELEGATED; + else if (!g_strcasecmp (partstat, _("Completed"))) + return ICAL_PARTSTAT_COMPLETED; + else if (!g_strcasecmp (partstat, _("In Process"))) + return ICAL_PARTSTAT_INPROCESS; + else + return ICAL_PARTSTAT_NONE; +} + +static char * +partstat_to_text (icalparameter_partstat partstat) +{ + switch (partstat) { + case ICAL_PARTSTAT_NEEDSACTION: + return _("Needs Action"); + case ICAL_PARTSTAT_ACCEPTED: + return _("Accepted"); + case ICAL_PARTSTAT_DECLINED: + return _("Declined"); + case ICAL_PARTSTAT_TENTATIVE: + return _("Tentative"); + case ICAL_PARTSTAT_DELEGATED: + return _("Delegated"); + case ICAL_PARTSTAT_COMPLETED: + return _("Completed"); + case ICAL_PARTSTAT_INPROCESS: + return _("In Process"); + case ICAL_PARTSTAT_NONE: + default: + return _("Unknown"); + } + + return NULL; +} + +static int +column_count (ETableModel *etm) +{ + return ITIP_COLUMN_COUNT; +} + +static int +row_count (ETableModel *etm) +{ + EMeetingModel *im; + EMeetingModelPrivate *priv; + + im = E_MEETING_MODEL (etm); + priv = im->priv; + + return (priv->attendees->len); +} + +static void +append_row (ETableModel *etm, ETableModel *source, int row) +{ + EMeetingModel *im; + EMeetingModelPrivate *priv; + EMeetingAttendee *ia; + char *address; + + im = E_MEETING_MODEL (etm); + priv = im->priv; + + address = (char *) e_table_model_value_at (source, ITIP_ADDRESS_COL, row); + if (find_match (im, address, NULL) != NULL) { +// duplicate_error (); + return; + } + + ia = E_MEETING_ATTENDEE (e_meeting_attendee_new ()); + + e_meeting_attendee_set_address (ia, g_strdup_printf ("MAILTO:%s", address)); + e_meeting_attendee_set_member (ia, g_strdup (e_table_model_value_at (source, ITIP_MEMBER_COL, row))); + e_meeting_attendee_set_cutype (ia, text_to_type (e_table_model_value_at (source, ITIP_TYPE_COL, row))); + e_meeting_attendee_set_role (ia, text_to_role (e_table_model_value_at (source, ITIP_ROLE_COL, row))); + e_meeting_attendee_set_rsvp (ia, text_to_boolean (e_table_model_value_at (source, ITIP_RSVP_COL, row))); + e_meeting_attendee_set_delto (ia, g_strdup (e_table_model_value_at (source, ITIP_DELTO_COL, row))); + e_meeting_attendee_set_delfrom (ia, g_strdup (e_table_model_value_at (source, ITIP_DELFROM_COL, row))); + e_meeting_attendee_set_status (ia, text_to_partstat (e_table_model_value_at (source, ITIP_STATUS_COL, row))); + e_meeting_attendee_set_cn (ia, g_strdup (e_table_model_value_at (source, ITIP_CN_COL, row))); + e_meeting_attendee_set_language (ia, g_strdup (e_table_model_value_at (source, ITIP_LANGUAGE_COL, row))); + + e_meeting_model_add_attendee (E_MEETING_MODEL (etm), ia); + +// comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (im)); +// comp_editor_page_notify_changed (COMP_EDITOR_PAGE (im)); +} + +static void * +value_at (ETableModel *etm, int col, int row) +{ + EMeetingModel *im; + EMeetingModelPrivate *priv; + EMeetingAttendee *ia; + + im = E_MEETING_MODEL (etm); + priv = im->priv; + + ia = g_ptr_array_index (priv->attendees, row); + + switch (col) { + case ITIP_ADDRESS_COL: + return (void *)itip_strip_mailto (e_meeting_attendee_get_address (ia)); + case ITIP_MEMBER_COL: + return (void *)e_meeting_attendee_get_member (ia); + case ITIP_TYPE_COL: + return type_to_text (e_meeting_attendee_get_cutype (ia)); + case ITIP_ROLE_COL: + return role_to_text (e_meeting_attendee_get_role (ia)); + case ITIP_RSVP_COL: + return boolean_to_text (e_meeting_attendee_get_rsvp (ia)); + case ITIP_DELTO_COL: + return (void *)itip_strip_mailto (e_meeting_attendee_get_delto (ia)); + case ITIP_DELFROM_COL: + return (void *)itip_strip_mailto (e_meeting_attendee_get_delfrom (ia)); + case ITIP_STATUS_COL: + return partstat_to_text (e_meeting_attendee_get_status (ia)); + case ITIP_CN_COL: + return (void *)e_meeting_attendee_get_cn (ia); + case ITIP_LANGUAGE_COL: + return (void *)e_meeting_attendee_get_language (ia); + } + + return NULL; +} + +static void +set_value_at (ETableModel *etm, int col, int row, const void *val) +{ + EMeetingModel *im; + EMeetingModelPrivate *priv; + EMeetingAttendee *ia; + + im = E_MEETING_MODEL (etm); + priv = im->priv; + + ia = g_ptr_array_index (priv->attendees, row); + + switch (col) { + case ITIP_ADDRESS_COL: + e_meeting_attendee_set_address (ia, g_strdup_printf ("MAILTO:%s", (char *) val)); + break; + case ITIP_MEMBER_COL: + e_meeting_attendee_set_member (ia, g_strdup (val)); + break; + case ITIP_TYPE_COL: + e_meeting_attendee_set_cutype (ia, text_to_type (val)); + break; + case ITIP_ROLE_COL: + e_meeting_attendee_set_role (ia, text_to_role (val)); + break; + case ITIP_RSVP_COL: + e_meeting_attendee_set_rsvp (ia, text_to_boolean (val)); + break; + case ITIP_DELTO_COL: + e_meeting_attendee_set_delto (ia, g_strdup (val)); + break; + case ITIP_DELFROM_COL: + e_meeting_attendee_set_delfrom (ia, g_strdup (val)); + break; + case ITIP_STATUS_COL: + e_meeting_attendee_set_status (ia, text_to_partstat (val)); + break; + case ITIP_CN_COL: + e_meeting_attendee_set_cn (ia, g_strdup (val)); + break; + case ITIP_LANGUAGE_COL: + e_meeting_attendee_set_language (ia, g_strdup (val)); + break; + } + +// comp_editor_page_notify_needs_send (COMP_EDITOR_PAGE (im)); +// comp_editor_page_notify_changed (COMP_EDITOR_PAGE (im)); +} + +static gboolean +is_cell_editable (ETableModel *etm, int col, int row) +{ + switch (col) { + case ITIP_DELTO_COL: + case ITIP_DELFROM_COL: + return FALSE; + + default: + } + + return TRUE; +} + +static void * +duplicate_value (ETableModel *etm, int col, const void *val) +{ + return g_strdup (val); +} + +static void +free_value (ETableModel *etm, int col, void *val) +{ + g_free (val); +} + +static void * +init_value (ETableModel *etm, int col) +{ + switch (col) { + case ITIP_ADDRESS_COL: + return g_strdup (""); + case ITIP_MEMBER_COL: + return g_strdup (""); + case ITIP_TYPE_COL: + return g_strdup (_("Individual")); + case ITIP_ROLE_COL: + return g_strdup (_("Required Participant")); + case ITIP_RSVP_COL: + return g_strdup (_("Yes")); + case ITIP_DELTO_COL: + return g_strdup (""); + case ITIP_DELFROM_COL: + return g_strdup (""); + case ITIP_STATUS_COL: + return g_strdup (_("Needs Action")); + case ITIP_CN_COL: + return g_strdup (""); + case ITIP_LANGUAGE_COL: + return g_strdup ("en"); + } + + return g_strdup (""); +} + +static gboolean +value_is_empty (ETableModel *etm, int col, const void *val) +{ + + switch (col) { + case ITIP_ADDRESS_COL: + case ITIP_MEMBER_COL: + case ITIP_DELTO_COL: + case ITIP_DELFROM_COL: + case ITIP_CN_COL: + if (val && !g_strcasecmp (val, "")) + return TRUE; + else + return FALSE; + default: + } + + return TRUE; +} + +static char * +value_to_string (ETableModel *etm, int col, const void *val) +{ + return g_strdup (val); +} + + +static void +class_init (EMeetingModelClass *klass) +{ + GtkObjectClass *object_class; + ETableModelClass *etm_class; + + object_class = GTK_OBJECT_CLASS (klass); + etm_class = E_TABLE_MODEL_CLASS (klass); + + parent_class = gtk_type_class (E_TABLE_MODEL_TYPE); + + object_class->destroy = destroy; + + etm_class->column_count = column_count; + etm_class->row_count = row_count; + etm_class->value_at = value_at; + etm_class->set_value_at = set_value_at; + etm_class->is_cell_editable = is_cell_editable; + etm_class->append_row = append_row; + etm_class->duplicate_value = duplicate_value; + etm_class->free_value = free_value; + etm_class->initialize_value = init_value; + etm_class->value_is_empty = value_is_empty; + etm_class->value_to_string = value_to_string; +} + + +static void +init (EMeetingModel *im) +{ + EMeetingModelPrivate *priv; + + priv = g_new0 (EMeetingModelPrivate, 1); + + im->priv = priv; + + priv->attendees = g_ptr_array_new (); + + priv->client = NULL; + + priv->ebook = NULL; + priv->book_loaded = FALSE; + priv->book_load_wait = FALSE; + + priv->refreshing = FALSE; + + start_addressbook_server (im); +} + +static void +destroy (GtkObject *obj) +{ + EMeetingModel *model = E_MEETING_MODEL (obj); + EMeetingModelPrivate *priv; + gint i; + + priv = model->priv; + + for (i = 0; i < priv->attendees->len; i++) + gtk_object_unref (GTK_OBJECT (g_ptr_array_index(priv->attendees, i))); + g_ptr_array_free (priv->attendees, FALSE); + + if (priv->client != NULL) + gtk_object_unref (GTK_OBJECT (priv->client)); + + if (priv->ebook != NULL) + gtk_object_unref (GTK_OBJECT (priv->ebook)); + + g_free (priv); +} + +GtkObject * +e_meeting_model_new (void) +{ + return gtk_type_new (E_TYPE_MEETING_MODEL); +} + + +CalClient * +e_meeting_model_get_cal_client (EMeetingModel *im) +{ + EMeetingModelPrivate *priv; + + priv = im->priv; + + return priv->client; +} + +void +e_meeting_model_set_cal_client (EMeetingModel *im, CalClient *client) +{ + EMeetingModelPrivate *priv; + + priv = im->priv; + + if (priv->client != NULL) + gtk_object_unref (GTK_OBJECT (priv->client)); + + if (client != NULL) + gtk_object_ref (GTK_OBJECT (client)); + priv->client = client; +} + +static ETableScrolled * +build_etable (ETableModel *model, const gchar *spec_file, const gchar *state_file) +{ + GtkWidget *etable; + ETable *real_table; + ETableExtras *extras; + GList *strings; + ECell *popup_cell, *cell; + + extras = e_table_extras_new (); + + /* For type */ + cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); + popup_cell = e_cell_combo_new (); + e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell); + gtk_object_unref (GTK_OBJECT (cell)); + + strings = NULL; + strings = g_list_append (strings, _("Individual")); + strings = g_list_append (strings, _("Group")); + strings = g_list_append (strings, _("Resource")); + strings = g_list_append (strings, _("Room")); + strings = g_list_append (strings, _("Unknown")); + + e_cell_combo_set_popdown_strings (E_CELL_COMBO (popup_cell), strings); + e_table_extras_add_cell (extras, "typeedit", popup_cell); + + /* For role */ + cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); + popup_cell = e_cell_combo_new (); + e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell); + gtk_object_unref (GTK_OBJECT (cell)); + + strings = NULL; + strings = g_list_append (strings, _("Chair")); + strings = g_list_append (strings, _("Required Participant")); + strings = g_list_append (strings, _("Optional Participant")); + strings = g_list_append (strings, _("Non-Participant")); + strings = g_list_append (strings, _("Unknown")); + + e_cell_combo_set_popdown_strings (E_CELL_COMBO (popup_cell), strings); + e_table_extras_add_cell (extras, "roleedit", popup_cell); + + /* For rsvp */ + cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); + popup_cell = e_cell_combo_new (); + e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell); + gtk_object_unref (GTK_OBJECT (cell)); + + strings = NULL; + strings = g_list_append (strings, _("Yes")); + strings = g_list_append (strings, _("No")); + + e_cell_combo_set_popdown_strings (E_CELL_COMBO (popup_cell), strings); + e_table_extras_add_cell (extras, "rsvpedit", popup_cell); + + /* For status */ + cell = e_cell_text_new (NULL, GTK_JUSTIFY_LEFT); + popup_cell = e_cell_combo_new (); + e_cell_popup_set_child (E_CELL_POPUP (popup_cell), cell); + gtk_object_unref (GTK_OBJECT (cell)); + + strings = NULL; + strings = g_list_append (strings, _("Needs Action")); + strings = g_list_append (strings, _("Accepted")); + strings = g_list_append (strings, _("Declined")); + strings = g_list_append (strings, _("Tentative")); + strings = g_list_append (strings, _("Delegated")); + + e_cell_combo_set_popdown_strings (E_CELL_COMBO (popup_cell), strings); + e_table_extras_add_cell (extras, "statusedit", popup_cell); + + + etable = e_table_scrolled_new_from_spec_file (model, extras, spec_file, NULL); + + real_table = e_table_scrolled_get_table (E_TABLE_SCROLLED (etable)); + e_scroll_frame_set_policy (E_SCROLL_FRAME (etable), GTK_POLICY_NEVER, GTK_POLICY_NEVER); + e_scroll_frame_set_scrollbar_spacing (E_SCROLL_FRAME (etable), 0); + e_table_load_state (real_table, state_file); + +// gtk_signal_connect (GTK_OBJECT (real_table), +// "right_click", GTK_SIGNAL_FUNC (right_click_cb), mpage); +// gtk_signal_connect (GTK_OBJECT (real_table->sort_info), +// "sort_info_changed", GTK_SIGNAL_FUNC (sort_info_changed_cb), mts); + + gtk_object_unref (GTK_OBJECT (extras)); + + return E_TABLE_SCROLLED (etable); +} + +void +e_meeting_model_add_attendee (EMeetingModel *im, EMeetingAttendee *ia) +{ + EMeetingModelPrivate *priv; + + priv = im->priv; + + gtk_object_ref (GTK_OBJECT (ia)); + g_ptr_array_add (priv->attendees, ia); + + gtk_signal_connect (GTK_OBJECT (ia), "changed", + GTK_SIGNAL_FUNC (attendee_changed_cb), im); + + e_table_model_row_inserted (E_TABLE_MODEL (im), row_count (E_TABLE_MODEL (im)) - 1); +} + +EMeetingAttendee * +e_meeting_model_add_attendee_with_defaults (EMeetingModel *im) +{ + EMeetingAttendee *ia; + char *str; + + ia = E_MEETING_ATTENDEE (e_meeting_attendee_new ()); + + e_meeting_attendee_set_address (ia, init_value (E_TABLE_MODEL (im), ITIP_ADDRESS_COL)); + e_meeting_attendee_set_member (ia, init_value (E_TABLE_MODEL (im), ITIP_MEMBER_COL)); + + str = init_value (E_TABLE_MODEL (im), ITIP_TYPE_COL); + e_meeting_attendee_set_cutype (ia, text_to_type (str)); + g_free (str); + str = init_value (E_TABLE_MODEL (im), ITIP_ROLE_COL); + e_meeting_attendee_set_role (ia, text_to_role (str)); + g_free (str); + str = init_value (E_TABLE_MODEL (im), ITIP_RSVP_COL); + e_meeting_attendee_set_role (ia, text_to_boolean (str)); + g_free (str); + + e_meeting_attendee_set_delto (ia, init_value (E_TABLE_MODEL (im), ITIP_DELTO_COL)); + e_meeting_attendee_set_delfrom (ia, init_value (E_TABLE_MODEL (im), ITIP_DELFROM_COL)); + + str = init_value (E_TABLE_MODEL (im), ITIP_STATUS_COL); + e_meeting_attendee_set_status (ia, text_to_partstat (str)); + g_free (str); + + e_meeting_attendee_set_cn (ia, init_value (E_TABLE_MODEL (im), ITIP_CN_COL)); + e_meeting_attendee_set_language (ia, init_value (E_TABLE_MODEL (im), ITIP_LANGUAGE_COL)); + + e_meeting_model_add_attendee (im, ia); + + return ia; +} + +void +e_meeting_model_remove_attendee (EMeetingModel *im, EMeetingAttendee *ia) +{ + EMeetingModelPrivate *priv; + gint i, row = -1; + + priv = im->priv; + + for (i = 0; i < priv->attendees->len; i++) { + if (ia == g_ptr_array_index (priv->attendees, i)) { + row = i; + break; + } + } + + if (row != -1) { + g_ptr_array_remove_index (priv->attendees, row); + gtk_object_unref (GTK_OBJECT (ia)); + + e_table_model_row_deleted (E_TABLE_MODEL (im), row); + } +} + +void +e_meeting_model_remove_all_attendees (EMeetingModel *im) +{ + EMeetingModelPrivate *priv; + gint i; + + priv = im->priv; + + for (i = 0; i < priv->attendees->len; i++) { + EMeetingAttendee *ia = g_ptr_array_index (priv->attendees, i); + gtk_object_unref (GTK_OBJECT (ia)); + } + + e_table_model_rows_deleted (E_TABLE_MODEL (im), 0, priv->attendees->len); + g_ptr_array_set_size (priv->attendees, 0); +} + +EMeetingAttendee * +e_meeting_model_find_attendee (EMeetingModel *im, const gchar *address, gint *row) +{ + EMeetingModelPrivate *priv; + EMeetingAttendee *ia; + int i; + + priv = im->priv; + + if (address == NULL) + return NULL; + + for (i = 0; i < priv->attendees->len; i++) { + const gchar *ia_address; + + ia = g_ptr_array_index (priv->attendees, i); + + ia_address = e_meeting_attendee_get_address (ia); + if (ia_address && !g_strcasecmp (itip_strip_mailto (ia_address), itip_strip_mailto (address))) { + if (row != NULL) + *row = i; + + return ia; + } + } + + return NULL; +} + +EMeetingAttendee * +e_meeting_model_find_attendee_at_row (EMeetingModel *im, gint row) +{ + EMeetingModelPrivate *priv; + + priv = im->priv; + + return g_ptr_array_index (priv->attendees, row); +} + +gint +e_meeting_model_count_attendees (EMeetingModel *im) +{ + EMeetingModelPrivate *priv; + + priv = im->priv; + + return priv->attendees->len; +} + +const GPtrArray * +e_meeting_model_get_attendees (EMeetingModel *im) +{ + EMeetingModelPrivate *priv; + + priv = im->priv; + + return priv->attendees; +} + +static icaltimezone * +find_zone (icalproperty *ip, icalcomponent *tz_top_level) +{ + icalparameter *param; + icalcomponent *sub_comp; + const char *tzid; + icalcompiter iter; + + if (tz_top_level == NULL) + return NULL; + + param = icalproperty_get_first_parameter (ip, ICAL_TZID_PARAMETER); + tzid = icalparameter_get_tzid (param); + + iter = icalcomponent_begin_component (tz_top_level, ICAL_VTIMEZONE_COMPONENT); + while ((sub_comp = icalcompiter_deref (&iter)) != NULL) { + icalcomponent *clone; + const char *tz_tzid; + + tz_tzid = icalproperty_get_tzid (sub_comp); + if (!strcmp (tzid, tz_tzid)) { + icaltimezone *zone; + + zone = icaltimezone_new (); + clone = icalcomponent_new_clone (sub_comp); + icaltimezone_set_component (zone, clone); + + return zone; + } + + icalcompiter_next (&iter); + } + + return NULL; +} + +static struct icaltimetype +convert_time (struct icaltimetype itt, icaltimezone *from, icaltimezone *to) +{ + if (from == NULL) + from = icaltimezone_get_utc_timezone (); + + icaltimezone_convert_time (&itt, from, to); + + return itt; +} + +static void +process_free_busy_comp (EMeetingAttendee *ia, icalcomponent *fb_comp, icalcomponent *tz_top_level) +{ + icalproperty *ip; + icaltimezone *view_zone; + + view_zone = icaltimezone_get_builtin_timezone (calendar_config_get_timezone ()); + + ip = icalcomponent_get_first_property (fb_comp, ICAL_DTSTART_PROPERTY); + if (ip != NULL) { + struct icaltimetype dtstart; + icaltimezone *ds_zone = NULL; + + dtstart = icalproperty_get_dtstart (ip); + if (!dtstart.is_utc) { + ds_zone = find_zone (ip, tz_top_level); + if (ds_zone != NULL) + dtstart = convert_time (dtstart, ds_zone, view_zone); + } + + e_meeting_attendee_set_start_busy_range (ia, + dtstart.year, + dtstart.month, + dtstart.day, + dtstart.hour, + dtstart.minute); + } + + ip = icalcomponent_get_first_property (fb_comp, ICAL_DTEND_PROPERTY); + if (ip != NULL) { + struct icaltimetype dtend; + icaltimezone *de_zone = NULL; + + dtend = icalproperty_get_dtend (ip); + if (!dtend.is_utc) { + de_zone = find_zone (ip, tz_top_level); + if (de_zone != NULL) + dtend = convert_time (dtend, de_zone, view_zone); + } + + e_meeting_attendee_set_end_busy_range (ia, + dtend.year, + dtend.month, + dtend.day, + dtend.hour, + dtend.minute); + } + + ip = icalcomponent_get_first_property (fb_comp, ICAL_FREEBUSY_PROPERTY); + while (ip != NULL) { + icalparameter *param; + struct icalperiodtype fb; + EMeetingFreeBusyType busy_type = E_MEETING_FREE_BUSY_LAST; + icalparameter_fbtype fbtype = ICAL_FBTYPE_BUSY; + + fb = icalproperty_get_freebusy (ip); + param = icalproperty_get_first_parameter (ip, ICAL_FBTYPE_PARAMETER); + if (param != NULL) + fbtype = icalparameter_get_fbtype (param); + + switch (fbtype) { + case ICAL_FBTYPE_BUSY: + busy_type = E_MEETING_FREE_BUSY_BUSY; + break; + + case ICAL_FBTYPE_BUSYUNAVAILABLE: + busy_type = E_MEETING_FREE_BUSY_OUT_OF_OFFICE; + break; + + case ICAL_FBTYPE_BUSYTENTATIVE: + busy_type = E_MEETING_FREE_BUSY_TENTATIVE; + break; + + default: + } + + if (fbtype != E_MEETING_FREE_BUSY_LAST) { + fb.start = convert_time (fb.start, NULL, view_zone); + fb.end = convert_time (fb.end, NULL, view_zone); + e_meeting_attendee_add_busy_period (ia, + fb.start.year, + fb.start.month, + fb.start.day, + fb.start.hour, + fb.start.minute, + fb.end.year, + fb.end.month, + fb.end.day, + fb.end.hour, + fb.end.minute, + busy_type); + } + + ip = icalcomponent_get_next_property (fb_comp, ICAL_FREEBUSY_PROPERTY); + } +} + +static void +process_free_busy (EMeetingModel *im, EMeetingAttendee *ia, char *text) +{ + EMeetingModelPrivate *priv; + icalcomponent *main_comp; + icalcomponent_kind kind = ICAL_NO_COMPONENT; + + priv = im->priv; + + main_comp = icalparser_parse_string (text); + if (main_comp == NULL) + return; + + kind = icalcomponent_isa (main_comp); + if (kind == ICAL_VCALENDAR_COMPONENT) { + icalcompiter iter; + icalcomponent *tz_top_level, *sub_comp; + + tz_top_level = cal_util_new_top_level (); + + iter = icalcomponent_begin_component (main_comp, ICAL_VTIMEZONE_COMPONENT); + while ((sub_comp = icalcompiter_deref (&iter)) != NULL) { + icalcomponent *clone; + + clone = icalcomponent_new_clone (sub_comp); + icalcomponent_add_component (tz_top_level, clone); + + icalcompiter_next (&iter); + } + + iter = icalcomponent_begin_component (main_comp, ICAL_VFREEBUSY_COMPONENT); + while ((sub_comp = icalcompiter_deref (&iter)) != NULL) { + process_free_busy_comp (ia, sub_comp, tz_top_level); + + icalcompiter_next (&iter); + } + icalcomponent_free (tz_top_level); + } else if (kind == ICAL_VFREEBUSY_COMPONENT) { + process_free_busy_comp (ia, main_comp, NULL); + } else { + return; + } + + icalcomponent_free (main_comp); +} + +static void +async_close (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer data) +{ + EMeetingModelRefreshData *r_data = data; + EMeetingModelPrivate *priv; + + process_free_busy (r_data->im, r_data->attendee_data.ia, r_data->attendee_data.string->str); + + priv = r_data->im->priv; + + priv->refresh_count--; + + if (priv->refresh_count == 0) { + GList *l, *m; + + for (l = priv->refresh_callbacks, m = priv->refresh_data; l != NULL; l = l->next, m = m->next) { + EMeetingModelRefreshCallback cb = l->data; + + cb (m->data); + } + + priv->refreshing = FALSE; + } +} + +static void +async_read (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer buffer, + GnomeVFSFileSize requested, + GnomeVFSFileSize read, + gpointer data) +{ + EMeetingModelRefreshData *r_data = data; + GnomeVFSFileSize buf_size = BUF_SIZE - 1; + + if (result != GNOME_VFS_OK) { + gnome_vfs_async_close (handle, async_close, r_data); + return; + } + + ((char *)buffer)[read] = '\0'; + r_data->attendee_data.string = g_string_append (r_data->attendee_data.string, buffer); + + if (read < requested) { + gnome_vfs_async_close (handle, async_close, r_data); + return; + } + + gnome_vfs_async_read (handle, r_data->attendee_data.buffer, buf_size, async_read, r_data); +} + +static void +async_open (GnomeVFSAsyncHandle *handle, + GnomeVFSResult result, + gpointer data) +{ + EMeetingModelRefreshData *r_data = data; + GnomeVFSFileSize buf_size = BUF_SIZE - 1; + + gnome_vfs_async_read (handle, r_data->attendee_data.buffer, buf_size, async_read, r_data); +} + +static void +cursor_cb (EBook *book, EBookStatus status, ECardCursor *cursor, gpointer data) +{ + EMeetingModel *im = E_MEETING_MODEL (data); + EMeetingModelPrivate *priv; + int length, i, j; + + if (status != E_BOOK_STATUS_SUCCESS) + return; + + priv = im->priv; + + length = e_card_cursor_get_length (cursor); + priv->refresh_count = 0; + + for (i = 0; i < length; i ++) { + GnomeVFSAsyncHandle *handle; + ECard *card = e_card_cursor_get_nth (cursor, i); + EMeetingModelRefreshData *r_data = g_new0 (EMeetingModelRefreshData, 1); + EMeetingAttendee *ia = NULL; + + if (card->fburl == NULL) + continue; + + for (j = 0; j < priv->attendees->len; j++) { + ia = g_ptr_array_index (priv->attendees, j); + if (e_card_email_match_string (card, itip_strip_mailto (e_meeting_attendee_get_address (ia)))) + break; + } + if (ia == NULL) + continue; + + r_data->im = im; + r_data->attendee_data.string = g_string_new (NULL); + r_data->attendee_data.ia = ia; + + priv->refresh_count++; + + /* Read in free/busy data from the url */ + gnome_vfs_async_open (&handle, card->fburl, GNOME_VFS_OPEN_READ, async_open, r_data); + } +} + +void +e_meeting_model_refresh_busy_periods (EMeetingModel *im, EMeetingModelRefreshCallback call_back, gpointer data) +{ + EMeetingModelPrivate *priv; + GPtrArray *not_found; + GString *string; + int i; + + priv = im->priv; + + priv->refresh_callbacks = g_list_append (priv->refresh_callbacks, call_back); + priv->refresh_data = g_list_append (priv->refresh_data, data); + + if (priv->refreshing) + return; + + priv->refreshing = TRUE; + + /* To track what we don't find on the server */ + not_found = g_ptr_array_new (); + g_ptr_array_set_size (not_found, priv->attendees->len); + for (i = 0; i < priv->attendees->len; i++) + g_ptr_array_index (not_found, i) = g_ptr_array_index (priv->attendees, i); + +#if 0 + /* Check the server for free busy data */ + if (priv->client) { + GList *fb_data, *users = NULL, *l; + time_t start, end, now = time (NULL); + + start = now - 60 * 60 * 24; + end = time_add_week (now, 6); + + for (i = 0; i < priv->attendees->len; i++) { + EMeetingAttendee *ia = g_ptr_array_index (priv->attendees, i); + const char *user; + + user = itip_strip_mailto (e_meeting_attendee_get_address (ia)); + users = g_list_append (users, g_strdup (user)); + } + + fb_data = cal_client_get_free_busy (priv->client, users, start, end); + + g_list_foreach (users, (GFunc)g_free, NULL); + g_list_free (users); + + for (l = fb_data; l != NULL; l = l->next) { + CalComponent *comp = l->data; + EMeetingAttendee *ia = NULL; + CalComponentOrganizer org; + + /* Process the data for any attendees found */ + cal_component_get_organizer (comp, &org); + for (i = 0; i < priv->attendees->len; i++) { + ia = g_ptr_array_index (priv->attendees, i); + if (org.value && !strcmp (org.value, e_meeting_attendee_get_address (ia))) { + g_ptr_array_remove_fast (not_found, ia); + break; + } + ia = NULL; + } + + if (ia != NULL) + process_free_busy (im, ia, cal_component_get_as_string (comp)); + } + } +#endif + /* Look for fburl's of attendee with no free busy info on server */ + if (!priv->book_loaded) { + priv->book_load_wait = TRUE; + gtk_main (); + } + + string = g_string_new ("(or "); + for (i = 0; i < not_found->len; i++) { + EMeetingAttendee *ia = g_ptr_array_index (not_found, i); + char *query; + + if (!e_meeting_attendee_is_set_address (ia)) + continue; + + e_meeting_attendee_clear_busy_periods (ia); + + query = g_strdup_printf ("(contains \"email\" \"%s\")", itip_strip_mailto (e_meeting_attendee_get_address (ia))); + g_string_append (string, query); + g_free (query); + } + g_string_append_c (string, ')'); + + e_book_get_cursor (priv->ebook, string->str, cursor_cb, im); + + g_ptr_array_free (not_found, FALSE); + g_string_free (string, TRUE); +} + +ETableScrolled * +e_meeting_model_etable_from_model (EMeetingModel *im, const gchar *spec_file, const gchar *state_file) +{ + g_return_val_if_fail (im != NULL, NULL); + g_return_val_if_fail (E_IS_MEETING_MODEL (im), NULL); + + return build_etable (E_TABLE_MODEL (im), spec_file, state_file); +} + +static void +attendee_changed_cb (EMeetingAttendee *ia, gpointer data) +{ + EMeetingModel *im = E_MEETING_MODEL (data); + EMeetingModelPrivate *priv; + gint row = -1, i; + + priv = im->priv; + + for (i = 0; i < priv->attendees->len; i++) { + if (ia == g_ptr_array_index (priv->attendees, i)) { + row = 1; + break; + } + } + + if (row == -1) + return; + + e_table_model_row_changed (E_TABLE_MODEL (im), row); +} diff --git a/calendar/gui/e-meeting-model.h b/calendar/gui/e-meeting-model.h new file mode 100644 index 0000000000..2b90ea1d7b --- /dev/null +++ b/calendar/gui/e-meeting-model.h @@ -0,0 +1,91 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* e-model.h + * + * Copyright (C) 2001 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. + * + * Author: JP Rosevear + */ + +#ifndef _E_MODEL_H_ +#define _E_MODEL_H_ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <gtk/gtk.h> +#include <gal/e-table/e-table-scrolled.h> +#include <gal/e-table/e-table-model.h> +#include <cal-client/cal-client.h> +#include "e-meeting-attendee.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + +#define E_TYPE_MEETING_MODEL (e_meeting_model_get_type ()) +#define E_MEETING_MODEL(obj) (GTK_CHECK_CAST ((obj), E_TYPE_MEETING_MODEL, EMeetingModel)) +#define E_MEETING_MODEL_CLASS(klass) (GTK_CHECK_CLASS_CAST ((klass), E_TYPE_MEETING_MODEL, EMeetingModelClass)) +#define E_IS_MEETING_MODEL(obj) (GTK_CHECK_TYPE ((obj), E_TYPE_MEETING_MODEL)) +#define E_IS_MEETING_MODEL_CLASS(klass) (GTK_CHECK_CLASS_TYPE ((obj), E_TYPE_MEETING_MODEL)) + + +typedef struct _EMeetingModel EMeetingModel; +typedef struct _EMeetingModelPrivate EMeetingModelPrivate; +typedef struct _EMeetingModelClass EMeetingModelClass; + +struct _EMeetingModel { + ETableModel parent; + + EMeetingModelPrivate *priv; +}; + +struct _EMeetingModelClass { + ETableModelClass parent_class; +}; + +typedef void (* EMeetingModelRefreshCallback) (gpointer data); + + +GtkType e_meeting_model_get_type (void); +GtkObject *e_meeting_model_new (void); + +CalClient *e_meeting_model_get_cal_client (EMeetingModel *im); +void e_meeting_model_set_cal_client (EMeetingModel *im, CalClient *client); + +void e_meeting_model_add_attendee (EMeetingModel *im, EMeetingAttendee *ia); +EMeetingAttendee *e_meeting_model_add_attendee_with_defaults (EMeetingModel *im); + +void e_meeting_model_remove_attendee (EMeetingModel *im, EMeetingAttendee *ia); +void e_meeting_model_remove_all_attendees (EMeetingModel *im); + +EMeetingAttendee *e_meeting_model_find_attendee (EMeetingModel *im, const gchar *address, gint *row); +EMeetingAttendee *e_meeting_model_find_attendee_at_row (EMeetingModel *im, gint row); + +gint e_meeting_model_count_attendees (EMeetingModel *im); +const GPtrArray *e_meeting_model_get_attendees (EMeetingModel *im); +void e_meeting_model_refresh_busy_periods (EMeetingModel *im, EMeetingModelRefreshCallback call_back, gpointer data); + +ETableScrolled *e_meeting_model_etable_from_model (EMeetingModel *im, const gchar *spec_file, const gchar *state_file); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _E_MEETING_MODEL_H_ */ diff --git a/calendar/gui/e-meeting-time-sel-item.c b/calendar/gui/e-meeting-time-sel-item.c new file mode 100644 index 0000000000..3cc89dc5a2 --- /dev/null +++ b/calendar/gui/e-meeting-time-sel-item.c @@ -0,0 +1,1005 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@gtk.org> + * + * Copyright 1999, 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 + */ + +/* + * EMeetingTimeSelectorItem - A GnomeCanvasItem which is used for both the main + * display canvas and the top display (with the dates, times & All Attendees). + * I didn't make these separate GnomeCanvasItems since they share a lot of + * code. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <time.h> +#include <glib.h> +#include <libgnome/gnome-defs.h> +#include <libgnome/gnome-i18n.h> +#include "e-meeting-time-sel-item.h" +#include "e-meeting-time-sel.h" + +/* Initially the grid lines were drawn at the bottom of cells, but this didn't + line up well with the GtkEntry widgets, which in the default theme draw a + black shadow line across the top. So I've switched our code to draw the + lines across the top of cells. */ +#define E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM 0 + +static void e_meeting_time_selector_item_class_init (EMeetingTimeSelectorItemClass *mts_item_class); +static void e_meeting_time_selector_item_init (EMeetingTimeSelectorItem *mts_item); +static void e_meeting_time_selector_item_destroy (GtkObject *object); + +static void e_meeting_time_selector_item_set_arg (GtkObject *o, GtkArg *arg, + guint arg_id); +static void e_meeting_time_selector_item_realize (GnomeCanvasItem *item); +static void e_meeting_time_selector_item_unrealize (GnomeCanvasItem *item); +static void e_meeting_time_selector_item_update (GnomeCanvasItem *item, + double *affine, + ArtSVP *clip_path, int flags); +static void e_meeting_time_selector_item_draw (GnomeCanvasItem *item, + GdkDrawable *drawable, + int x, int y, + int width, int height); +static double e_meeting_time_selector_item_point (GnomeCanvasItem *item, + double x, double y, + int cx, int cy, + GnomeCanvasItem **actual_item); +static gint e_meeting_time_selector_item_event (GnomeCanvasItem *item, + GdkEvent *event); +static gint e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item, + GdkEvent *event); +static gint e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item, + GdkEvent *event); +static gint e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item, + GdkEvent *event); + +static void e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item, + GdkDrawable *drawable, + GDate *date, + int x, int scroll_y, + int width, int height); +static void e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item, GdkDrawable *drawable, GDate *date, int x, int y, int width, int height); +static void e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item, + GdkDrawable *drawable, + GDate *date, + int x, int scroll_y, + int width, int height); +static void e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item, GdkDrawable *drawable, GDate *date, int x, int scroll_y, int width, int height); +static gint e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item, GDate *date, gint row); +static void e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item, GdkDrawable *drawable, int row, int x, int y, int width, int first_period, EMeetingFreeBusyType busy_type); + +static EMeetingTimeSelectorPosition e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, gint x, gint y); +static gboolean e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts, + gint row, + gint x, + gint width, + gint *start_x, + gint *end_x); + +static GnomeCanvasItemClass *parent_class; + +/* The arguments we take */ +enum { + ARG_0, + ARG_MEETING_TIME_SELECTOR +}; + + +GtkType +e_meeting_time_selector_item_get_type (void) +{ + static GtkType e_meeting_time_selector_item_type = 0; + + if (!e_meeting_time_selector_item_type) { + GtkTypeInfo e_meeting_time_selector_item_info = { + "EMeetingTimeSelectorItem", + sizeof (EMeetingTimeSelectorItem), + sizeof (EMeetingTimeSelectorItemClass), + (GtkClassInitFunc) e_meeting_time_selector_item_class_init, + (GtkObjectInitFunc) e_meeting_time_selector_item_init, + NULL, /* reserved_1 */ + NULL, /* reserved_2 */ + (GtkClassInitFunc) NULL + }; + + e_meeting_time_selector_item_type = gtk_type_unique (gnome_canvas_item_get_type (), &e_meeting_time_selector_item_info); + } + + return e_meeting_time_selector_item_type; +} + + +static void +e_meeting_time_selector_item_class_init (EMeetingTimeSelectorItemClass *mts_item_class) +{ + GtkObjectClass *object_class; + GnomeCanvasItemClass *item_class; + + parent_class = gtk_type_class (gnome_canvas_item_get_type()); + + object_class = (GtkObjectClass *) mts_item_class; + item_class = (GnomeCanvasItemClass *) mts_item_class; + + gtk_object_add_arg_type ("EMeetingTimeSelectorItem::meeting_time_selector", + GTK_TYPE_POINTER, GTK_ARG_WRITABLE, + ARG_MEETING_TIME_SELECTOR); + + object_class->destroy = e_meeting_time_selector_item_destroy; + object_class->set_arg = e_meeting_time_selector_item_set_arg; + + /* GnomeCanvasItem method overrides */ + item_class->realize = e_meeting_time_selector_item_realize; + item_class->unrealize = e_meeting_time_selector_item_unrealize; + item_class->update = e_meeting_time_selector_item_update; + item_class->draw = e_meeting_time_selector_item_draw; + item_class->point = e_meeting_time_selector_item_point; + item_class->event = e_meeting_time_selector_item_event; +} + + +static void +e_meeting_time_selector_item_init (EMeetingTimeSelectorItem *mts_item) +{ + GnomeCanvasItem *item = GNOME_CANVAS_ITEM (mts_item); + + mts_item->mts = NULL; + + mts_item->main_gc = NULL; + mts_item->stipple_gc = NULL; + + /* Create the cursors. */ + mts_item->normal_cursor = gdk_cursor_new (GDK_LEFT_PTR); + mts_item->resize_cursor = gdk_cursor_new (GDK_SB_H_DOUBLE_ARROW); + mts_item->last_cursor_set = NULL; + + item->x1 = 0; + item->y1 = 0; + item->x2 = 0; + item->y2 = 0; +} + + +static void +e_meeting_time_selector_item_destroy (GtkObject *object) +{ + EMeetingTimeSelectorItem *mts_item; + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (object); + + gdk_cursor_destroy (mts_item->normal_cursor); + gdk_cursor_destroy (mts_item->resize_cursor); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (*GTK_OBJECT_CLASS (parent_class)->destroy)(object); +} + + +static void +e_meeting_time_selector_item_set_arg (GtkObject *o, GtkArg *arg, guint arg_id) +{ + GnomeCanvasItem *item; + EMeetingTimeSelectorItem *mts_item; + + item = GNOME_CANVAS_ITEM (o); + mts_item = E_MEETING_TIME_SELECTOR_ITEM (o); + + switch (arg_id){ + case ARG_MEETING_TIME_SELECTOR: + mts_item->mts = GTK_VALUE_POINTER (*arg); + break; + } +} + + +static void +e_meeting_time_selector_item_realize (GnomeCanvasItem *item) +{ + GnomeCanvas *canvas; + GdkWindow *window; + EMeetingTimeSelectorItem *mts_item; + + if (GNOME_CANVAS_ITEM_CLASS (parent_class)->realize) + (*GNOME_CANVAS_ITEM_CLASS (parent_class)->realize)(item); + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); + + canvas = item->canvas; + window = GTK_WIDGET (canvas)->window; + + mts_item->main_gc = gdk_gc_new (window); + mts_item->stipple_gc = gdk_gc_new (window); +} + + +static void +e_meeting_time_selector_item_unrealize (GnomeCanvasItem *item) +{ + EMeetingTimeSelectorItem *mts_item; + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); + + gdk_gc_unref (mts_item->main_gc); + mts_item->main_gc = NULL; + gdk_gc_unref (mts_item->stipple_gc); + mts_item->stipple_gc = NULL; + + if (GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize) + (*GNOME_CANVAS_ITEM_CLASS (parent_class)->unrealize)(item); +} + + +static void +e_meeting_time_selector_item_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags) +{ + if (GNOME_CANVAS_ITEM_CLASS (parent_class)->update) + (* GNOME_CANVAS_ITEM_CLASS (parent_class)->update) (item, affine, clip_path, flags); + + /* The grid covers the entire canvas area. */ + item->x1 = 0; + item->y1 = 0; + item->x2 = INT_MAX; + item->y2 = INT_MAX; +} + + +/* + * DRAWING ROUTINES - functions to paint the canvas item. + */ + +static void +e_meeting_time_selector_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height) +{ + EMeetingTimeSelector *mts; + EMeetingTimeSelectorItem *mts_item; + EMeetingAttendee *ia; + gint day_x, meeting_start_x, meeting_end_x, bar_y, bar_height; + gint row, row_y, start_x, end_x; + GDate date, last_date, current_date; + gboolean is_display_top, show_meeting_time; + GdkGC *gc, *stipple_gc; + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); + mts = mts_item->mts; + g_return_if_fail (mts != NULL); + gc = mts_item->main_gc; + stipple_gc = mts_item->stipple_gc; + + is_display_top = (GTK_WIDGET (item->canvas) == mts->display_top) + ? TRUE : FALSE; + + /* Calculate the first and last visible days and positions. */ + e_meeting_time_selector_calculate_day_and_position (mts, x, + &date, &day_x); + e_meeting_time_selector_calculate_day_and_position (mts, x + width, + &last_date, NULL); + + /* For the top display draw the 'All Attendees' row background. */ + if (is_display_top) { + gdk_gc_set_foreground (gc, &mts->all_attendees_bg_color); + gdk_draw_rectangle (drawable, gc, TRUE, + 0, mts->row_height * 2 - y, + width, mts->row_height); + } else { + gdk_gc_set_foreground (gc, &mts->bg_color); + gdk_draw_rectangle (drawable, gc, TRUE, 0, 0, width, height); + } + + /* Calculate the x coordinates of the meeting time. */ + show_meeting_time = e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x); + + /* Draw the meeting time background. */ + if (show_meeting_time + && (meeting_end_x - 1 >= x) && (meeting_start_x + 1 < x + width) + && (meeting_end_x - meeting_start_x > 2)) { + gdk_gc_set_foreground (gc, &mts->meeting_time_bg_color); + if (is_display_top) + gdk_draw_rectangle (drawable, gc, TRUE, + meeting_start_x + 1 - x, mts->row_height * 2 - y, + meeting_end_x - meeting_start_x - 2, mts->row_height); + else + gdk_draw_rectangle (drawable, gc, TRUE, + meeting_start_x + 1 - x, 0, + meeting_end_x - meeting_start_x - 2, height); + } + + /* For the main display draw the stipple background for attendee's + that have no calendar information. */ + if (!is_display_top) { + gdk_gc_set_foreground (gc, &mts->grid_color); + gdk_gc_set_foreground (stipple_gc, &mts->grid_color); + gdk_gc_set_background (stipple_gc, &mts->stipple_bg_color); + gdk_gc_set_stipple (stipple_gc, mts->stipple); + gnome_canvas_set_stipple_origin (item->canvas, stipple_gc); + gdk_gc_set_fill (stipple_gc, GDK_OPAQUE_STIPPLED); + row = y / mts->row_height; + row_y = row * mts->row_height - y; + while (row < e_meeting_model_count_attendees (mts->model) && row_y < height) { + ETable *real_table = e_table_scrolled_get_table (E_TABLE_SCROLLED (mts->etable)); + gint model_row = e_table_view_to_model_row (real_table, row); + + ia = e_meeting_model_find_attendee_at_row (mts->model, model_row); + + if (e_meeting_attendee_get_has_calendar_info (ia)) { + if (e_meeting_time_selector_item_calculate_busy_range (mts, model_row, x, width, &start_x, &end_x)) { + if (start_x >= width || end_x <= 0) { + gdk_draw_rectangle (drawable, stipple_gc, TRUE, 0, row_y, width, mts->row_height); + } else { + if (start_x >= 0) { + gdk_draw_rectangle (drawable, stipple_gc, TRUE, 0, row_y, start_x, mts->row_height); + gdk_draw_line (drawable, gc, start_x, row_y, start_x, row_y + mts->row_height); + } + if (end_x <= width) { + gdk_draw_rectangle (drawable, stipple_gc, TRUE, end_x, row_y, width - end_x, mts->row_height); + gdk_draw_line (drawable, gc, end_x, row_y, end_x, row_y + mts->row_height); + } + } + } + } else { + gdk_draw_rectangle (drawable, stipple_gc, TRUE, + 0, row_y, + width, mts->row_height); + } + row++; + row_y += mts->row_height; + } + gdk_gc_set_fill (gc, GDK_SOLID); + } + + /* Now paint the visible days one by one. */ + current_date = date; + for (;;) { + /* Currently we use the same GnomeCanvasItem class for the + top display and the main display. We may use separate + classes in future if necessary. */ + if (is_display_top) + e_meeting_time_selector_item_paint_day_top (mts_item, drawable, ¤t_date, day_x, y, width, height); + else + e_meeting_time_selector_item_paint_day (mts_item, drawable, ¤t_date, day_x, y, width, height); + + day_x += mts_item->mts->day_width; + if (g_date_compare (¤t_date, &last_date) == 0) + break; + g_date_add_days (¤t_date, 1); + } + + /* Draw the busy periods. */ + if (is_display_top) + e_meeting_time_selector_item_paint_all_attendees_busy_periods (mts_item, drawable, &date, x, y, width, height); + else + e_meeting_time_selector_item_paint_busy_periods (mts_item, drawable, &date, x, y, width, height); + + + /* Draw the currently-selected meeting time vertical bars. */ + if (show_meeting_time) { + if (is_display_top) { + bar_y = mts->row_height * 2 - y; + bar_height = mts->row_height; + } else { + bar_y = 0; + bar_height = height; + } + + gdk_gc_set_foreground (gc, &mts->grid_color); + + if ((meeting_start_x + 2 >= x) + && (meeting_start_x - 2 < x + width)) { + gdk_draw_rectangle (drawable, gc, TRUE, + meeting_start_x - 2 - x, bar_y, + 5, bar_height); + } + + if ((meeting_end_x + 2 >= x) + && (meeting_end_x - 2 < x + width)) { + gdk_draw_rectangle (drawable, gc, TRUE, + meeting_end_x - 2 - x, bar_y, + 5, bar_height); + } + } +} + + +static void +e_meeting_time_selector_item_paint_day_top (EMeetingTimeSelectorItem *mts_item, + GdkDrawable *drawable, GDate *date, + int x, int scroll_y, + int width, int height) +{ + EMeetingTimeSelector *mts; + GdkGC *gc; + GdkFont *font; + gint y, grid_x; + gchar buffer[128], *format; + gint hour, hour_x, hour_y; + GdkRectangle clip_rect; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + gdk_gc_set_foreground (gc, &mts->grid_color); + + /* Draw the horizontal lines. */ + y = mts->row_height - 1 - scroll_y; + gdk_draw_line (drawable, gc, x, y, x + mts->day_width - 1, y); + gdk_gc_set_foreground (gc, &mts->grid_shadow_color); + gdk_draw_line (drawable, gc, x, y + 1, x + mts->day_width - 1, y + 1); + gdk_gc_set_foreground (gc, &mts->grid_color); + y += mts->row_height; + gdk_draw_line (drawable, gc, x, y, x + mts->day_width - 1, y); + y += mts->row_height; + gdk_draw_line (drawable, gc, x, y, x + mts->day_width - 1, y); + + + /* Draw the vertical grid lines. */ + for (grid_x = mts->col_width - 1; + grid_x < mts->day_width - mts->col_width; + grid_x += mts->col_width) { + gdk_draw_line (drawable, gc, + x + grid_x, mts->row_height * 2 - 4 - scroll_y, + x + grid_x, height); + } + grid_x = mts->day_width - 2; + gdk_draw_line (drawable, gc, x + grid_x, 0, x + grid_x, height); + grid_x++; + gdk_draw_line (drawable, gc, x + grid_x, 0, x + grid_x, height); + + /* Draw the date. Set a clipping rectangle so we don't draw over the + next day. */ + font = GTK_WIDGET (mts)->style->font; + if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_FULL) + /* This is a strftime() format string %A = full weekday name, + %B = full month name, %d = month day, %Y = full year. */ + format = _("%A, %B %d, %Y"); + else if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY) + /* This is a strftime() format string %a = abbreviated weekday + name, %m = month number, %d = month day, %Y = full year. */ + format = _("%a %m/%d/%Y"); + else + /* This is a strftime() format string %m = month number, + %d = month day, %Y = full year. */ + format = _("%m/%d/%Y"); + + g_date_strftime (buffer, sizeof (buffer), format, date); + + clip_rect.x = x; + clip_rect.y = -scroll_y; + clip_rect.width = mts->day_width - 2; + clip_rect.height = mts->row_height - 2; + gdk_gc_set_clip_rectangle (gc, &clip_rect); + gdk_draw_string (drawable, font, gc, + x + 4, 4 + font->ascent - scroll_y, buffer); + gdk_gc_set_clip_rectangle (gc, NULL); + + /* Draw the hours. */ + hour = mts->first_hour_shown + (mts->zoomed_out ? 3 : 1); + hour_x = x + mts->col_width; + hour_y = mts->row_height + 4 + font->ascent - scroll_y; + while (hour < mts->last_hour_shown) { + gdk_draw_string (drawable, font, gc, + hour_x - (mts->hour_widths[hour] / 2), + hour_y, EMeetingTimeSelectorHours[hour]); + + hour += mts->zoomed_out ? 3 : 1; + hour_x += mts->col_width; + } +} + + +/* This paints the colored bars representing busy periods for the combined + list of attendees. For now we just paint the bars for each attendee of + each other. If we want to speed it up we could optimise it later. */ +static void +e_meeting_time_selector_item_paint_all_attendees_busy_periods (EMeetingTimeSelectorItem *mts_item, GdkDrawable *drawable, GDate *date, int x, int scroll_y, int width, int height) +{ + EMeetingTimeSelector *mts; + EMeetingAttendee *ia; + EMeetingFreeBusyType busy_type; + gint row, y; + GdkGC *gc; + gint *first_periods; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + /* Calculate the y coordinate to paint the row at in the drawable. */ + y = 2 * mts->row_height - scroll_y - 1; + + /* Get the first visible busy periods for all the attendees. */ + first_periods = g_new (gint, e_meeting_model_count_attendees (mts->model)); + for (row = 0; row < e_meeting_model_count_attendees (mts->model); row++) { + ia = e_meeting_model_find_attendee_at_row (mts->model, row); + first_periods[row] = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, row); + } + + for (busy_type = 0; + busy_type < E_MEETING_FREE_BUSY_LAST; + busy_type++) { + gdk_gc_set_foreground (gc, &mts->busy_colors[busy_type]); + for (row = 0; row < e_meeting_model_count_attendees (mts->model); row++) { + if (first_periods[row] == -1) + continue; + e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, drawable, x, y, width, row, first_periods[row], busy_type); + } + } + + g_free (first_periods); +} + + +static void +e_meeting_time_selector_item_paint_day (EMeetingTimeSelectorItem *mts_item, + GdkDrawable *drawable, GDate *date, + int x, int scroll_y, + int width, int height) +{ + EMeetingTimeSelector *mts; + GdkGC *gc; + gint grid_x, grid_y, attendee_index, unused_y; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + /* Draw the grid lines. The grid lines around unused rows are drawn in + a different color. */ + + /* Draw the horizontal grid lines. */ + attendee_index = scroll_y / mts->row_height; +#if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM + for (grid_y = mts->row_height - 1 - (scroll_y % mts->row_height); +#else + for (grid_y = - (scroll_y % mts->row_height); +#endif + grid_y < height; + grid_y += mts->row_height) + { + if (attendee_index <= e_meeting_model_count_attendees (mts->model)) { + gdk_gc_set_foreground (gc, &mts->grid_color); + gdk_draw_line (drawable, gc, 0, grid_y, + width, grid_y); + } else { + gdk_gc_set_foreground (gc, &mts->grid_unused_color); + gdk_draw_line (drawable, gc, 0, grid_y, + width, grid_y); + } + attendee_index++; + } + + /* Draw the vertical grid lines. */ + unused_y = (e_meeting_model_count_attendees (mts->model) * mts->row_height) - scroll_y; + if (unused_y >= 0) { + gdk_gc_set_foreground (gc, &mts->grid_color); + for (grid_x = mts->col_width - 1; + grid_x < mts->day_width - mts->col_width; + grid_x += mts->col_width) + { + gdk_draw_line (drawable, gc, + x + grid_x, 0, + x + grid_x, unused_y - 1); + } + gdk_draw_rectangle (drawable, gc, TRUE, + x + mts->day_width - 2, 0, + 2, unused_y); + } + + if (unused_y < height) { + gdk_gc_set_foreground (gc, &mts->grid_unused_color); + for (grid_x = mts->col_width - 1; + grid_x < mts->day_width - mts->col_width; + grid_x += mts->col_width) + { + gdk_draw_line (drawable, gc, + x + grid_x, unused_y, + x + grid_x, height); + } + gdk_draw_rectangle (drawable, gc, TRUE, + x + mts->day_width - 2, unused_y, + 2, height - unused_y); + } + + +} + + +/* This paints the colored bars representing busy periods for the individual + attendees. */ +static void +e_meeting_time_selector_item_paint_busy_periods (EMeetingTimeSelectorItem *mts_item, GdkDrawable *drawable, GDate *date, int x, int scroll_y, int width, int height) +{ + EMeetingTimeSelector *mts; + EMeetingFreeBusyType busy_type; + ETable *real_table; + gint row, model_row, y, first_period; + GdkGC *gc; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + real_table = e_table_scrolled_get_table (E_TABLE_SCROLLED (mts->etable)); + + /* Calculate the first visible attendee row. */ + row = scroll_y / mts->row_height; + + /* Calculate the y coordinate to paint the row at in the drawable. */ + y = row * mts->row_height - scroll_y; + + /* Step through the attendees painting the busy periods. */ + while (y < height && row < e_meeting_model_count_attendees (mts->model)) { + model_row = e_table_view_to_model_row (real_table, row); + + /* Find the first visible busy period. */ + first_period = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, model_row); + if (first_period != -1) { + /* Paint the different types of busy periods, in + reverse order of precedence, so the highest + precedences are displayed. */ + for (busy_type = 0; + busy_type < E_MEETING_FREE_BUSY_LAST; + busy_type++) { + gdk_gc_set_foreground (gc, &mts->busy_colors[busy_type]); + e_meeting_time_selector_item_paint_attendee_busy_periods (mts_item, drawable, x, y, width, model_row, first_period, busy_type); + } + } + y += mts->row_height; + row++; + } +} + + +/* This subtracts the attendees longest_period_in_days from the given date, + and does a binary search of the attendee's busy periods array to find the + first one which could possible end on the given day or later. + If none are found it returns -1. */ +static gint +e_meeting_time_selector_item_find_first_busy_period (EMeetingTimeSelectorItem *mts_item, GDate *date, gint row) +{ + EMeetingTimeSelector *mts; + EMeetingAttendee *ia; + const GArray *busy_periods; + EMeetingFreeBusyPeriod *period; + gint period_num; + + mts = mts_item->mts; + + ia = e_meeting_model_find_attendee_at_row (mts->model, row); + + period_num = e_meeting_attendee_find_first_busy_period (ia, date); + if (period_num == -1) + return -1; + + /* Check if the period starts after the end of the current canvas + scroll area. */ + busy_periods = e_meeting_attendee_get_busy_periods (ia); + period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num); + if (g_date_compare (&mts->last_date_shown, &period->start.date) < 0) + return -1; + + return period_num; +} + + +/* This paints the visible busy periods for one attendee which are of a certain + busy type, e.g out of office. It is passed the index of the first visible + busy period of the attendee and continues until it runs off the screen. */ +static void +e_meeting_time_selector_item_paint_attendee_busy_periods (EMeetingTimeSelectorItem *mts_item, GdkDrawable *drawable, int x, int y, int width, int row, int first_period, EMeetingFreeBusyType busy_type) +{ + EMeetingTimeSelector *mts; + EMeetingAttendee *ia; + const GArray *busy_periods; + EMeetingFreeBusyPeriod *period; + GdkGC *gc; + gint period_num, x1, x2, x2_within_day, x2_within_col; + + mts = mts_item->mts; + gc = mts_item->main_gc; + + ia = e_meeting_model_find_attendee_at_row (mts->model, row); + + busy_periods = e_meeting_attendee_get_busy_periods (ia); + for (period_num = first_period; + period_num < busy_periods->len; + period_num++) { + period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num); + + if (period->busy_type != busy_type) + continue; + + /* Convert the period start and end times to x coordinates. */ + x1 = e_meeting_time_selector_calculate_time_position (mts, &period->start); + /* If the period is off the right of the area being drawn, we + are finished. */ + if (x1 >= x + width) + return; + + x2 = e_meeting_time_selector_calculate_time_position (mts, &period->end); + /* If the period is off the left edge of the area skip it. */ + if (x2 <= x) + continue; + + /* We paint from x1 to x2 - 1, so that for example a time + from 5:00-6:00 is distinct from 6:00-7:00. + We never finish on a grid line separating days, and we only + ever paint on a normal vertical grid line if the period is + only 1 pixel wide. */ + x2_within_day = x2 % mts->day_width; + if (x2_within_day == 0) { + x2 -= 2; + } else if (x2_within_day == mts->day_width - 1) { + x2 -= 1; + } else { + x2_within_col = x2_within_day % mts->col_width; + if (x2_within_col == 0 && x2 > x1 + 1) + x2 -= 1; + } + + /* Paint the rectangle. We leave a gap of 2 pixels at the + top and bottom, remembering that the grid is painted along + the top/bottom line of each row. */ + if (x2 - x1 > 0) { +#if E_MEETING_TIME_SELECTOR_DRAW_GRID_LINES_AT_BOTTOM + gdk_draw_rectangle (drawable, gc, TRUE, + x1 - x, y + 2, + x2 - x1, mts->row_height - 5); +#else + gdk_draw_rectangle (drawable, gc, TRUE, + x1 - x, y + 3, + x2 - x1, mts->row_height - 5); +#endif + } + } +} + + +/* + * CANVAS ITEM ROUTINES - functions to be a GnomeCanvasItem. + */ + +/* This is supposed to return the nearest item the the point and the distance. + Since we are the only item we just return ourself and 0 for the distance. + This is needed so that we get button/motion events. */ +static double +e_meeting_time_selector_item_point (GnomeCanvasItem *item, double x, double y, + int cx, int cy, + GnomeCanvasItem **actual_item) +{ + *actual_item = item; + return 0.0; +} + + +static gint +e_meeting_time_selector_item_event (GnomeCanvasItem *item, GdkEvent *event) +{ + EMeetingTimeSelectorItem *mts_item; + + mts_item = E_MEETING_TIME_SELECTOR_ITEM (item); + + switch (event->type) { + case GDK_BUTTON_PRESS: + return e_meeting_time_selector_item_button_press (mts_item, + event); + case GDK_BUTTON_RELEASE: + return e_meeting_time_selector_item_button_release (mts_item, + event); + case GDK_MOTION_NOTIFY: + return e_meeting_time_selector_item_motion_notify (mts_item, + event); + default: + break; + } + + return FALSE; +} + + +/* This handles all button press events for the item. If the cursor is over + one of the meeting time vertical bars we start a drag. If not we set the + meeting time to the nearest half-hour interval. + Note that GnomeCanvas converts the event coords to world coords, + i.e. relative to the entire canvas scroll area. */ +static gint +e_meeting_time_selector_item_button_press (EMeetingTimeSelectorItem *mts_item, + GdkEvent *event) +{ + EMeetingTimeSelector *mts; + EMeetingTime start_time, end_time; + EMeetingTimeSelectorPosition position; + GDate *start_date, *end_date; + gint x, y; + + mts = mts_item->mts; + x = (gint) event->button.x; + y = (gint) event->button.y; + + /* Check if we are starting a drag of the vertical meeting time bars.*/ + position = e_meeting_time_selector_item_get_drag_position (mts_item, + x, y); + if (position != E_MEETING_TIME_SELECTOR_POS_NONE) { + if (gnome_canvas_item_grab (GNOME_CANVAS_ITEM (mts_item), + GDK_POINTER_MOTION_MASK + | GDK_BUTTON_RELEASE_MASK, + mts_item->resize_cursor, + event->button.time) == 0 /*Success*/) { + mts->dragging_position = position; + return TRUE; + } + } + + /* Convert the x coordinate into a EMeetingTimeSelectorTime. */ + e_meeting_time_selector_calculate_time (mts, x, &start_time); + start_date = &start_time.date; + end_date = &end_time.date; + + /* Find the nearest half-hour or hour interval, depending on whether + zoomed_out is set. */ + if (mts->zoomed_out) { + start_time.minute = 0; + end_time = start_time; + end_time.hour += 1; + } else { + start_time.minute -= start_time.minute % 30; + end_time = start_time; + end_time.minute += 30; + } + + /* Fix any overflows. */ + e_meeting_time_selector_fix_time_overflows (&end_time); + + /* Set the new meeting time. */ + e_meeting_time_selector_set_meeting_time (mts_item->mts, + g_date_year (start_date), + g_date_month (start_date), + g_date_day (start_date), + start_time.hour, + start_time.minute, + g_date_year (end_date), + g_date_month (end_date), + g_date_day (end_date), + end_time.hour, + end_time.minute); + + + return FALSE; +} + + +/* This handles all button release events for the item. If we were dragging, + we finish the drag. */ +static gint +e_meeting_time_selector_item_button_release (EMeetingTimeSelectorItem *mts_item, + GdkEvent *event) +{ + EMeetingTimeSelector *mts; + + mts = mts_item->mts; + + /* Reset any drag. */ + if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) { + mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE; + e_meeting_time_selector_remove_timeout (mts); + gnome_canvas_item_ungrab (GNOME_CANVAS_ITEM (mts_item), + event->button.time); + } + + return FALSE; +} + + +/* This handles all motion notify events for the item. If button1 is pressed + we check if a drag is in progress. If not, we set the cursor if we are over + the meeting time vertical bars. Note that GnomeCanvas doesn't use motion + hints, which may affect performance. */ +static gint +e_meeting_time_selector_item_motion_notify (EMeetingTimeSelectorItem *mts_item, + GdkEvent *event) +{ + EMeetingTimeSelector *mts; + EMeetingTimeSelectorPosition position; + GdkCursor *cursor; + gint x, y; + + mts = mts_item->mts; + x = (gint) event->motion.x; + y = (gint) event->motion.y; + + if (mts->dragging_position != E_MEETING_TIME_SELECTOR_POS_NONE) { + e_meeting_time_selector_drag_meeting_time (mts, x); + return TRUE; + } + + position = e_meeting_time_selector_item_get_drag_position (mts_item, + x, y); + + /* Determine which cursor should be used. */ + if (position == E_MEETING_TIME_SELECTOR_POS_NONE) + cursor = mts_item->normal_cursor; + else + cursor = mts_item->resize_cursor; + + /* Only set the cursor if it is different to the last one we set. */ + if (mts_item->last_cursor_set != cursor) { + mts_item->last_cursor_set = cursor; + gdk_window_set_cursor (GTK_WIDGET (GNOME_CANVAS_ITEM (mts_item)->canvas)->window, cursor); + } + + return FALSE; +} + + +static EMeetingTimeSelectorPosition +e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, + gint x, gint y) +{ + EMeetingTimeSelector *mts; + gboolean is_display_top; + gint meeting_start_x, meeting_end_x; + + mts = mts_item->mts; + + is_display_top = (GTK_WIDGET (GNOME_CANVAS_ITEM (mts_item)->canvas) == mts->display_top) ? TRUE : FALSE; + + if (is_display_top && y < mts->row_height * 2) + return E_MEETING_TIME_SELECTOR_POS_NONE; + + if (!e_meeting_time_selector_get_meeting_time_positions (mts, &meeting_start_x, &meeting_end_x)) + return E_MEETING_TIME_SELECTOR_POS_NONE; + + if (x >= meeting_end_x - 2 && x <= meeting_end_x + 2) + return E_MEETING_TIME_SELECTOR_POS_END; + + if (x >= meeting_start_x - 2 && x <= meeting_start_x + 2) + return E_MEETING_TIME_SELECTOR_POS_START; + + return E_MEETING_TIME_SELECTOR_POS_NONE; +} + + +static gboolean +e_meeting_time_selector_item_calculate_busy_range (EMeetingTimeSelector *mts, + gint row, + gint x, + gint width, + gint *start_x, + gint *end_x) +{ + EMeetingAttendee *ia; + EMeetingTime busy_periods_start; + EMeetingTime busy_periods_end; + + ia = e_meeting_model_find_attendee_at_row (mts->model, row); + busy_periods_start = e_meeting_attendee_get_start_busy_range (ia); + busy_periods_end = e_meeting_attendee_get_end_busy_range (ia); + + *start_x = -1; + *end_x = -1; + + if (!g_date_valid (&busy_periods_start.date) + || !g_date_valid (&busy_periods_end.date)) + return FALSE; + + *start_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_start) - x - 1; + + *end_x = e_meeting_time_selector_calculate_time_position (mts, &busy_periods_end) - x; + + return TRUE; +} diff --git a/calendar/gui/e-meeting-time-sel-item.h b/calendar/gui/e-meeting-time-sel-item.h new file mode 100644 index 0000000000..7ad1f232b2 --- /dev/null +++ b/calendar/gui/e-meeting-time-sel-item.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@gtk.org> + * + * Copyright 1999, 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 + */ + +/* + * MeetingTimeSelectorItem - A GnomeCanvasItem which is used for both the main + * display canvas and the top display (with the dates, times & All Attendees). + * I didn't make these separate GnomeCanvasItems since they share a lot of + * code. + */ + +#ifndef _E_MEETING_TIME_SELECTOR_ITEM_H_ +#define _E_MEETING_TIME_SELECTOR_ITEM_H_ + +#include "e-meeting-time-sel.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +#define E_MEETING_TIME_SELECTOR_ITEM(obj) (GTK_CHECK_CAST((obj), e_meeting_time_selector_item_get_type (), EMeetingTimeSelectorItem)) +#define E_MEETING_TIME_SELECTOR_ITEM_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), e_meeting_time_selector_item_get_type (), EMeetingTimeSelectorItemClass)) +#define IS_E_MEETING_TIME_SELECTOR_ITEM(o) (GTK_CHECK_TYPE((o), e_meeting_time_selector_item_get_type ())) + + +typedef struct _EMeetingTimeSelectorItem EMeetingTimeSelectorItem; +typedef struct _EMeetingTimeSelectorItemClass EMeetingTimeSelectorItemClass; + +struct _EMeetingTimeSelectorItem +{ + GnomeCanvasItem canvas_item; + + /* The parent EMeetingTimeSelector widget. */ + EMeetingTimeSelector *mts; + + /* This GC is used for most of the drawing. The fg/bg colors are + changed for each bit. */ + GdkGC *main_gc; + GdkGC *stipple_gc; + + /* The normal & resize cursors. */ + GdkCursor *normal_cursor; + GdkCursor *resize_cursor; + + /* This remembers the last cursor set on the window. */ + GdkCursor *last_cursor_set; +}; + + +struct _EMeetingTimeSelectorItemClass +{ + GnomeCanvasItemClass parent_class; +}; + +GtkType e_meeting_time_selector_item_get_type (void); + + +#endif /* _E_MEETING_TIME_SELECTOR_ITEM_H_ */ diff --git a/calendar/gui/e-meeting-time-sel.c b/calendar/gui/e-meeting-time-sel.c new file mode 100644 index 0000000000..cfb87b5663 --- /dev/null +++ b/calendar/gui/e-meeting-time-sel.c @@ -0,0 +1,2622 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@gtk.org> + * + * Copyright 1999, 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 + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "e-meeting-time-sel.h" + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <time.h> +#include <glib.h> +#include <gdk/gdkkeysyms.h> +#include <gtk/gtkalignment.h> +#include <gtk/gtkarrow.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkdrawingarea.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkfixed.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkhscrollbar.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkradiomenuitem.h> +#include <gtk/gtksignal.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkvscrollbar.h> +#include <libgnomeui/gnome-dateedit.h> +#include <libgnome/gnome-i18n.h> +#include <libgnomeui/gnome-canvas-widget.h> + +#include <gal/widgets/e-canvas.h> +#include <gal/widgets/e-canvas-utils.h> +#include <gal/e-table/e-cell-combo.h> +#include <gal/e-table/e-cell-text.h> +#include <gal/e-table/e-table-simple.h> +#include <gal/e-table/e-table-scrolled.h> + +#include <widgets/misc/e-dateedit.h> +#include "component-factory.h" +#include "calendar-config.h" +#include "e-meeting-time-sel-item.h" + +/* An array of hour strings, "0:00" .. "23:00". */ +const gchar *EMeetingTimeSelectorHours[24] = { + "0:00", "1:00", "2:00", "3:00", "4:00", "5:00", "6:00", "7:00", + "8:00", "9:00", "10:00", "11:00", "12:00", "13:00", "14:00", "15:00", + "16:00", "17:00", "18:00", "19:00", "20:00", "21:00", "22:00", "23:00" +}; + +/* The number of days shown in the entire canvas. */ +#define E_MEETING_TIME_SELECTOR_DAYS_SHOWN 365 + +/* This is the number of pixels between the mouse has to move before the + scroll speed is incremented. */ +#define E_MEETING_TIME_SELECTOR_SCROLL_INCREMENT_WIDTH 10 + +/* This is the maximum scrolling speed. */ +#define E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED 4 + + +static void e_meeting_time_selector_class_init (EMeetingTimeSelectorClass * klass); +static void e_meeting_time_selector_init (EMeetingTimeSelector * mts); +static void e_meeting_time_selector_destroy (GtkObject *object); +static void e_meeting_time_selector_alloc_named_color (EMeetingTimeSelector * mts, + const char *name, GdkColor *c); +static void e_meeting_time_selector_add_key_color (EMeetingTimeSelector * mts, + GtkWidget *hbox, + gchar *label_text, + GdkColor *color); +static gint e_meeting_time_selector_expose_key_color (GtkWidget *darea, + GdkEventExpose *event, + GdkColor *color); +static void e_meeting_time_selector_options_menu_detacher (GtkWidget *widget, + GtkMenu *menu); +static void e_meeting_time_selector_autopick_menu_detacher (GtkWidget *widget, + GtkMenu *menu); +static void e_meeting_time_selector_realize (GtkWidget *widget); +static void e_meeting_time_selector_unrealize (GtkWidget *widget); +static gint e_meeting_time_selector_expose_event (GtkWidget *widget, + GdkEventExpose *event); +static void e_meeting_time_selector_draw (GtkWidget *widget, + GdkRectangle *area); +static void e_meeting_time_selector_draw_shadow (EMeetingTimeSelector *mts); +static void e_meeting_time_selector_hadjustment_changed (GtkAdjustment *adjustment, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment, + EMeetingTimeSelector *mts); + +static void e_meeting_time_selector_on_canvas_realized (GtkWidget *widget, + EMeetingTimeSelector *mts); + +static gint e_meeting_time_selector_compare_times (EMeetingTime*time1, + EMeetingTime*time2); +static void e_meeting_time_selector_on_options_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_options_menu_position_callback (GtkMenu *menu, + gint *x, + gint *y, + gpointer user_data); +static void e_meeting_time_selector_on_zoomed_out_toggled (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_on_working_hours_toggled (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_on_invite_others_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_on_update_free_busy (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_on_autopick_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_autopick_menu_position_callback (GtkMenu *menu, + gint *x, + gint *y, + gpointer user_data); +static void e_meeting_time_selector_on_autopick_option_toggled (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_on_prev_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_on_next_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_autopick (EMeetingTimeSelector *mts, + gboolean forward); +static void e_meeting_time_selector_calculate_time_difference (EMeetingTime*start, + EMeetingTime*end, + gint *days, + gint *hours, + gint *minutes); +static void e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts, + EMeetingTime*start_time, + EMeetingTime*end_time, + gint days, gint hours, gint mins); +static void e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts, + EMeetingTime *start_time, + EMeetingTime *end_time, + gint days, gint hours, gint mins); +static void e_meeting_time_selector_adjust_time (EMeetingTime*mtstime, + gint days, gint hours, gint minutes); +static EMeetingFreeBusyPeriod* e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts, + EMeetingAttendee *attendee, + EMeetingTime *start_time, + EMeetingTime *end_time); + + +static void e_meeting_time_selector_recalc_grid (EMeetingTimeSelector *mts); +static void e_meeting_time_selector_recalc_date_format (EMeetingTimeSelector *mts); +static void e_meeting_time_selector_save_position (EMeetingTimeSelector *mts, + EMeetingTime*mtstime); +static void e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts, + EMeetingTime*mtstime); +static void e_meeting_time_selector_on_start_time_changed (GtkWidget *widget, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_on_end_time_changed (GtkWidget *widget, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_update_date_popup_menus (EMeetingTimeSelector *mts); +static void e_meeting_time_selector_on_canvas_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + EMeetingTimeSelector *mts); +static void e_meeting_time_selector_update_main_canvas_scroll_region (EMeetingTimeSelector *mts); +static gboolean e_meeting_time_selector_timeout_handler (gpointer data); +static void e_meeting_time_selector_update_start_date_edit (EMeetingTimeSelector *mts); +static void e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector *mts); +static void e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts); +static void e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts); + +static void row_count_changed_cb (ETableModel *etm, int row, int count, gpointer data); +static void sort_info_changed_cb (ETableSortInfo *info, gpointer data); + +static GtkTableClass *parent_class; + + +GtkType +e_meeting_time_selector_get_type (void) +{ + static guint e_meeting_time_selector_type = 0; + + if (!e_meeting_time_selector_type) { + GtkTypeInfo e_meeting_time_selector_info = + { + "EMeetingTimeSelector", + sizeof (EMeetingTimeSelector), + sizeof (EMeetingTimeSelectorClass), + (GtkClassInitFunc) e_meeting_time_selector_class_init, + (GtkObjectInitFunc) e_meeting_time_selector_init, + /* reserved_1 */ NULL, + /* reserved_2 */ NULL, + (GtkClassInitFunc) NULL, + }; + + e_meeting_time_selector_type = gtk_type_unique (GTK_TYPE_TABLE, + &e_meeting_time_selector_info); + } + return e_meeting_time_selector_type; +} + + +static void +e_meeting_time_selector_class_init (EMeetingTimeSelectorClass * klass) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + + parent_class = gtk_type_class (gtk_table_get_type()); + + object_class = (GtkObjectClass *) klass; + widget_class = (GtkWidgetClass *) klass; + + object_class->destroy = e_meeting_time_selector_destroy; + + widget_class->realize = e_meeting_time_selector_realize; + widget_class->unrealize = e_meeting_time_selector_unrealize; + widget_class->expose_event = e_meeting_time_selector_expose_event; + widget_class->draw = e_meeting_time_selector_draw; +} + + +static void +e_meeting_time_selector_init (EMeetingTimeSelector * mts) +{ + /* The shadow is drawn in the border so it must be >= 2 pixels. */ + gtk_container_set_border_width (GTK_CONTAINER (mts), 2); + + mts->accel_group = gtk_accel_group_new (); + + mts->working_hours_only = TRUE; + mts->day_start_hour = 9; + mts->day_start_minute = 0; + mts->day_end_hour = 18; + mts->day_end_minute = 0; + mts->zoomed_out = TRUE; + mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE; + + mts->etable = NULL; +} + + +void +e_meeting_time_selector_construct (EMeetingTimeSelector * mts, EMeetingModel *emm) +{ + GtkWidget *hbox, *vbox, *separator, *button, *label, *table; + GtkWidget *alignment, *child_hbox, *child_vbox, *arrow, *menuitem; + GSList *group; + GdkVisual *visual; + GdkColormap *colormap; + guint accel_key; + GtkAccelGroup *menu_accel_group; + time_t meeting_start_time; + struct tm *meeting_start_tm; + char *filename; + ETable *real_table; + guchar stipple_bits[] = { + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, + }; + + /* The default meeting time is the nearest half-hour interval in the + future, in working hours. */ + meeting_start_time = time (NULL); + g_date_clear (&mts->meeting_start_time.date, 1); + g_date_set_time (&mts->meeting_start_time.date, meeting_start_time); + meeting_start_tm = localtime (&meeting_start_time); + mts->meeting_start_time.hour = meeting_start_tm->tm_hour; + mts->meeting_start_time.minute = meeting_start_tm->tm_min; + + e_meeting_time_selector_find_nearest_interval (mts, &mts->meeting_start_time, + &mts->meeting_end_time, + 0, 0, 30); + + e_meeting_time_selector_update_dates_shown (mts); + + mts->meeting_positions_valid = FALSE; + + mts->row_height = 19; + mts->col_width = 50; + mts->day_width = 50 * 24 + 1; + + mts->auto_scroll_timeout_id = 0; + + vbox = gtk_vbox_new (FALSE, 0); + gtk_table_attach (GTK_TABLE (mts), + vbox, 0, 1, 0, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (vbox); + + child_vbox = gtk_vbox_new (FALSE, 0); + gtk_widget_set_usize (child_vbox, 1, mts->row_height * 2 - 6); + gtk_box_pack_start (GTK_BOX (vbox), child_vbox, FALSE, FALSE, 0); + gtk_widget_show (child_vbox); + + mts->attendees_vbox = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), mts->attendees_vbox, TRUE, TRUE, 0); + gtk_widget_show (mts->attendees_vbox); + + /* build the etable */ + filename = g_strdup_printf ("%s/config/et-header-meeting-time-sel", evolution_dir); + mts->model = emm; + + gtk_signal_connect (GTK_OBJECT (mts->model), "model_rows_inserted", + GTK_SIGNAL_FUNC (row_count_changed_cb), mts); + gtk_signal_connect (GTK_OBJECT (mts->model), "model_rows_deleted", + GTK_SIGNAL_FUNC (row_count_changed_cb), mts); + + mts->etable = GTK_WIDGET (e_meeting_model_etable_from_model (mts->model, + EVOLUTION_ETSPECDIR "/e-meeting-time-sel.etspec", + filename)); + + real_table = e_table_scrolled_get_table (E_TABLE_SCROLLED (mts->etable)); + gtk_signal_connect (GTK_OBJECT (real_table->sort_info), "sort_info_changed", + GTK_SIGNAL_FUNC (sort_info_changed_cb), mts); + + gtk_box_pack_start (GTK_BOX (mts->attendees_vbox), mts->etable, TRUE, TRUE, 2); + gtk_widget_show (mts->etable); + g_free (filename); + + /* The free/busy information */ + mts->display_top = gnome_canvas_new (); + gtk_widget_set_usize (mts->display_top, -1, mts->row_height * 3); + gnome_canvas_set_scroll_region (GNOME_CANVAS (mts->display_top), + 0, 0, + mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN, + mts->row_height * 3); + /* Add some horizontal padding for the shadow around the display. */ + gtk_table_attach (GTK_TABLE (mts), mts->display_top, + 1, 4, 0, 1, GTK_EXPAND | GTK_FILL, 0, 0, 0); + gtk_widget_show (mts->display_top); + gtk_signal_connect (GTK_OBJECT (mts->display_top), "realize", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_canvas_realized), mts); + + mts->display_main = gnome_canvas_new (); + e_meeting_time_selector_update_main_canvas_scroll_region (mts); + /* Add some horizontal padding for the shadow around the display. */ + gtk_table_attach (GTK_TABLE (mts), mts->display_main, + 1, 4, 1, 2, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (mts->display_main); + gtk_signal_connect (GTK_OBJECT (mts->display_main), "realize", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_canvas_realized), mts); + gtk_signal_connect (GTK_OBJECT (mts->display_main), "size_allocate", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_canvas_size_allocate), mts); + + mts->hscrollbar = gtk_hscrollbar_new (GTK_LAYOUT (mts->display_main)->hadjustment); + GTK_LAYOUT (mts->display_main)->hadjustment->step_increment = mts->col_width; + gtk_table_attach (GTK_TABLE (mts), mts->hscrollbar, + 1, 4, 2, 3, GTK_EXPAND | GTK_FILL, 0, 0, 0); + gtk_widget_show (mts->hscrollbar); + + mts->vscrollbar = gtk_vscrollbar_new (GTK_LAYOUT (mts->display_main)->vadjustment); + GTK_LAYOUT (mts->display_main)->vadjustment->step_increment = mts->row_height; + gtk_table_attach (GTK_TABLE (mts), mts->vscrollbar, + 4, 5, 1, 2, 0, GTK_EXPAND | GTK_FILL, 0, 0); + gtk_widget_show (mts->vscrollbar); + + /* Create the item in the top canvas. */ + gnome_canvas_item_new (GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->display_top)->root), + e_meeting_time_selector_item_get_type (), + "EMeetingTimeSelectorItem::meeting_time_selector", mts, + NULL); + + /* Create the item in the main canvas. */ + gnome_canvas_item_new (GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->display_main)->root), + e_meeting_time_selector_item_get_type (), + "EMeetingTimeSelectorItem::meeting_time_selector", mts, + NULL); + + /* Create the hbox containing the color key. */ + hbox = gtk_hbox_new (FALSE, 2); + gtk_table_attach (GTK_TABLE (mts), hbox, + 1, 4, 3, 4, GTK_FILL, 0, 0, 8); + gtk_widget_show (hbox); + + e_meeting_time_selector_add_key_color (mts, hbox, _("Tentative"), &mts->busy_colors[E_MEETING_FREE_BUSY_TENTATIVE]); + e_meeting_time_selector_add_key_color (mts, hbox, _("Busy"), &mts->busy_colors[E_MEETING_FREE_BUSY_BUSY]); + e_meeting_time_selector_add_key_color (mts, hbox, _("Out of Office"), &mts->busy_colors[E_MEETING_FREE_BUSY_OUT_OF_OFFICE]); + e_meeting_time_selector_add_key_color (mts, hbox, _("No Information"), + NULL); + + separator = gtk_hseparator_new (); + gtk_table_attach (GTK_TABLE (mts), separator, + 0, 5, 4, 5, GTK_FILL, 0, 0, 0); + gtk_widget_show (separator); + + /* Create the Invite Others & Options buttons on the left. */ + hbox = gtk_hbox_new (FALSE, 4); + gtk_table_attach (GTK_TABLE (mts), hbox, + 0, 1, 3, 4, GTK_FILL, 0, 0, 0); + gtk_widget_show (hbox); + + button = gtk_button_new_with_label (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (button)->child), + _("_Invite Others...")); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + gtk_widget_add_accelerator (button, "clicked", mts->accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_invite_others_button_clicked), mts); + + mts->options_button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (hbox), mts->options_button, TRUE, TRUE, 0); + gtk_widget_show (mts->options_button); + + gtk_signal_connect (GTK_OBJECT (mts->options_button), "clicked", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_options_button_clicked), mts); + + child_hbox = gtk_hbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (mts->options_button), child_hbox); + gtk_widget_show (child_hbox); + + label = gtk_label_new (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (label), _("_Options")); + gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + gtk_widget_add_accelerator (mts->options_button, "clicked", mts->accel_group, + accel_key, GDK_MOD1_MASK, 0); + + arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_box_pack_start (GTK_BOX (child_hbox), arrow, FALSE, FALSE, 2); + gtk_widget_show (arrow); + + /* Create the Options menu. */ + mts->options_menu = gtk_menu_new (); + gtk_menu_attach_to_widget (GTK_MENU (mts->options_menu), mts->options_button, + e_meeting_time_selector_options_menu_detacher); + menu_accel_group = gtk_menu_ensure_uline_accel_group (GTK_MENU (mts->options_menu)); + + menuitem = gtk_check_menu_item_new_with_label (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (menuitem)->child), _("Show _Only Working Hours")); + gtk_menu_append (GTK_MENU (mts->options_menu), menuitem); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menuitem), + mts->working_hours_only); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, 0, 0); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (menuitem), "toggled", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_working_hours_toggled), mts); + gtk_widget_show (menuitem); + + menuitem = gtk_check_menu_item_new_with_label (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (menuitem)->child), _("Show _Zoomed Out")); + gtk_menu_append (GTK_MENU (mts->options_menu), menuitem); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (menuitem), + mts->zoomed_out); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, 0, 0); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (menuitem), "toggled", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_zoomed_out_toggled), mts); + gtk_widget_show (menuitem); + + menuitem = gtk_menu_item_new (); + gtk_menu_append (GTK_MENU (mts->options_menu), menuitem); + gtk_widget_set_sensitive (menuitem, FALSE); + gtk_widget_show (menuitem); + + menuitem = gtk_menu_item_new_with_label (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (menuitem)->child), _("_Update Free/Busy")); + gtk_menu_append (GTK_MENU (mts->options_menu), menuitem); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, 0, 0); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (menuitem), "activate", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_update_free_busy), mts); + gtk_widget_show (menuitem); + + /* Create the 3 AutoPick buttons on the left. */ + hbox = gtk_hbox_new (FALSE, 0); + gtk_table_attach (GTK_TABLE (mts), hbox, + 0, 1, 5, 6, GTK_FILL, 0, 0, 0); + gtk_widget_show (hbox); + + button = gtk_button_new_with_label (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (button)->child), + _("_<<")); + gtk_widget_add_accelerator (button, "clicked", mts->accel_group, + accel_key, GDK_MOD1_MASK | GDK_SHIFT_MASK, 0); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_prev_button_clicked), mts); + + mts->autopick_button = gtk_button_new (); + gtk_box_pack_start (GTK_BOX (hbox), mts->autopick_button, TRUE, TRUE, 0); + gtk_widget_show (mts->autopick_button); + + child_hbox = gtk_hbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (mts->autopick_button), child_hbox); + gtk_widget_show (child_hbox); + + label = gtk_label_new (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (label), _("_Autopick")); + gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + gtk_widget_add_accelerator (mts->autopick_button, "clicked", mts->accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (mts->autopick_button), "clicked", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_autopick_button_clicked), mts); + + arrow = gtk_arrow_new (GTK_ARROW_DOWN, GTK_SHADOW_OUT); + gtk_box_pack_start (GTK_BOX (child_hbox), arrow, FALSE, FALSE, 2); + gtk_widget_show (arrow); + + button = gtk_button_new_with_label (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (button)->child), + _(">_>")); + gtk_widget_add_accelerator (button, "clicked", mts->accel_group, + accel_key, GDK_MOD1_MASK | GDK_SHIFT_MASK, 0); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_widget_show (button); + gtk_signal_connect (GTK_OBJECT (button), "clicked", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_next_button_clicked), mts); + + /* Create the Autopick menu. */ + mts->autopick_menu = gtk_menu_new (); + gtk_menu_attach_to_widget (GTK_MENU (mts->autopick_menu), mts->autopick_button, + e_meeting_time_selector_autopick_menu_detacher); + menu_accel_group = gtk_menu_ensure_uline_accel_group (GTK_MENU (mts->autopick_menu)); + + menuitem = gtk_radio_menu_item_new_with_label (NULL, ""); + mts->autopick_all_item = menuitem; + group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem)); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (menuitem)->child), _("_All People and Resources")); + gtk_menu_append (GTK_MENU (mts->autopick_menu), menuitem); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, 0, 0); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (menuitem), "toggled", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_autopick_option_toggled), mts); + gtk_widget_show (menuitem); + + menuitem = gtk_radio_menu_item_new_with_label (group, ""); + mts->autopick_all_people_one_resource_item = menuitem; + group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem)); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (menuitem)->child), _("All _People and One Resource")); + gtk_menu_append (GTK_MENU (mts->autopick_menu), menuitem); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, 0, 0); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (menuitem), "toggled", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_autopick_option_toggled), mts); + gtk_widget_show (menuitem); + + menuitem = gtk_radio_menu_item_new_with_label (group, ""); + mts->autopick_required_people_item = menuitem; + group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem)); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (menuitem)->child), _("_Required People")); + gtk_menu_append (GTK_MENU (mts->autopick_menu), menuitem); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, 0, 0); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (menuitem), "activate", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_autopick_option_toggled), mts); + gtk_widget_show (menuitem); + + menuitem = gtk_radio_menu_item_new_with_label (group, ""); + mts->autopick_required_people_one_resource_item = menuitem; + group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem)); + accel_key = gtk_label_parse_uline (GTK_LABEL (GTK_BIN (menuitem)->child), _("Required People and _One Resource")); + gtk_menu_append (GTK_MENU (mts->autopick_menu), menuitem); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, 0, 0); + gtk_widget_add_accelerator (menuitem, "activate", menu_accel_group, + accel_key, GDK_MOD1_MASK, 0); + gtk_signal_connect (GTK_OBJECT (menuitem), "activate", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_autopick_option_toggled), mts); + gtk_widget_show (menuitem); + + /* Create the date entry fields on the right. */ + alignment = gtk_alignment_new (0.5, 0.5, 0, 0); + gtk_table_attach (GTK_TABLE (mts), alignment, + 1, 4, 5, 6, GTK_FILL, 0, 0, 0); + gtk_widget_show (alignment); + + table = gtk_table_new (2, 2, FALSE); + gtk_table_set_row_spacings (GTK_TABLE (table), 4); + gtk_container_add (GTK_CONTAINER (alignment), table); + gtk_widget_show (table); + + label = gtk_label_new (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (label), + _("Meeting _start time:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, 0, 1, GTK_FILL, 0, 4, 0); + gtk_widget_show (label); + + mts->start_date_edit = e_date_edit_new (); + e_date_edit_set_show_time (E_DATE_EDIT (mts->start_date_edit), TRUE); + e_date_edit_set_use_24_hour_format (E_DATE_EDIT (mts->start_date_edit), + calendar_config_get_24_hour_format ()); + + gtk_table_attach (GTK_TABLE (table), mts->start_date_edit, + 1, 2, 0, 1, GTK_FILL, 0, 0, 0); + gtk_widget_show (mts->start_date_edit); + gtk_signal_connect (GTK_OBJECT (mts->start_date_edit), "changed", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_start_time_changed), mts); + + label = gtk_label_new (""); + accel_key = gtk_label_parse_uline (GTK_LABEL (label), + _("Meeting _end time:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, + 0, 1, 1, 2, GTK_FILL, 0, 4, 0); + gtk_widget_show (label); + + mts->end_date_edit = e_date_edit_new (); + e_date_edit_set_show_time (E_DATE_EDIT (mts->end_date_edit), TRUE); + e_date_edit_set_use_24_hour_format (E_DATE_EDIT (mts->end_date_edit), + calendar_config_get_24_hour_format ()); + + gtk_table_attach (GTK_TABLE (table), mts->end_date_edit, + 1, 2, 1, 2, GTK_FILL, 0, 0, 0); + gtk_widget_show (mts->end_date_edit); + gtk_signal_connect (GTK_OBJECT (mts->end_date_edit), "changed", + GTK_SIGNAL_FUNC (e_meeting_time_selector_on_end_time_changed), mts); + + gtk_table_set_col_spacing (GTK_TABLE (mts), 0, 4); + gtk_table_set_row_spacing (GTK_TABLE (mts), 4, 12); + + /* Allocate the colors. */ + visual = gtk_widget_get_visual (GTK_WIDGET (mts)); + colormap = gtk_widget_get_colormap (GTK_WIDGET (mts)); + mts->color_context = gdk_color_context_new (visual, colormap); + e_meeting_time_selector_alloc_named_color (mts, "gray75", &mts->bg_color); + e_meeting_time_selector_alloc_named_color (mts, "gray50", &mts->all_attendees_bg_color); + gdk_color_black (colormap, &mts->grid_color); + gdk_color_white (colormap, &mts->grid_shadow_color); + e_meeting_time_selector_alloc_named_color (mts, "gray50", &mts->grid_unused_color); + gdk_color_white (colormap, &mts->meeting_time_bg_color); + gdk_color_white (colormap, &mts->stipple_bg_color); + gdk_color_white (colormap, &mts->attendee_list_bg_color); + + e_meeting_time_selector_alloc_named_color (mts, "LightSkyBlue2", &mts->busy_colors[E_MEETING_FREE_BUSY_TENTATIVE]); + e_meeting_time_selector_alloc_named_color (mts, "blue", &mts->busy_colors[E_MEETING_FREE_BUSY_BUSY]); + e_meeting_time_selector_alloc_named_color (mts, "HotPink3", &mts->busy_colors[E_MEETING_FREE_BUSY_OUT_OF_OFFICE]); + + /* Create the stipple, for attendees with no data. */ + mts->stipple = gdk_bitmap_create_from_data (NULL, (gchar*)stipple_bits, + 8, 8); + + /* Connect handlers to the adjustments in the main canvas, so we can + scroll the other 2 canvases. */ + gtk_signal_connect (GTK_OBJECT (GTK_LAYOUT (mts->display_main)->hadjustment), "value_changed", GTK_SIGNAL_FUNC (e_meeting_time_selector_hadjustment_changed), mts); + gtk_signal_connect (GTK_OBJECT (GTK_LAYOUT (mts->display_main)->vadjustment), "value_changed", GTK_SIGNAL_FUNC (e_meeting_time_selector_vadjustment_changed), mts); + gtk_signal_connect (GTK_OBJECT (GTK_LAYOUT (mts->display_main)->vadjustment), "changed", GTK_SIGNAL_FUNC (e_meeting_time_selector_vadjustment_changed), mts); + + e_meeting_time_selector_recalc_grid (mts); + e_meeting_time_selector_ensure_meeting_time_shown (mts); + e_meeting_time_selector_update_start_date_edit (mts); + e_meeting_time_selector_update_end_date_edit (mts); + e_meeting_time_selector_update_date_popup_menus (mts); +} + + +/* This adds a color to the color key beneath the main display. If color is + NULL, it displays the No Info stipple instead. */ +static void +e_meeting_time_selector_add_key_color (EMeetingTimeSelector * mts, + GtkWidget *hbox, + gchar *label_text, GdkColor *color) +{ + GtkWidget *child_hbox, *darea, *label; + + child_hbox = gtk_hbox_new (FALSE, 4); + gtk_box_pack_start (GTK_BOX (hbox), child_hbox, TRUE, TRUE, 0); + gtk_widget_show (child_hbox); + + darea = gtk_drawing_area_new (); + gtk_box_pack_start (GTK_BOX (child_hbox), darea, FALSE, FALSE, 0); + gtk_object_set_user_data (GTK_OBJECT (darea), mts); + gtk_widget_set_usize (darea, 14, 14); + gtk_widget_show (darea); + + label = gtk_label_new (label_text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_box_pack_start (GTK_BOX (child_hbox), label, TRUE, TRUE, 0); + gtk_widget_show (label); + + gtk_signal_connect (GTK_OBJECT (darea), "expose_event", + GTK_SIGNAL_FUNC (e_meeting_time_selector_expose_key_color), + color); +} + +static gint +e_meeting_time_selector_expose_key_color (GtkWidget *darea, + GdkEventExpose *event, + GdkColor *color) +{ + EMeetingTimeSelector * mts; + GdkGC *gc; + gint width, height; + + mts = gtk_object_get_user_data (GTK_OBJECT (darea)); + gc = mts->color_key_gc; + width = darea->allocation.width; + height = darea->allocation.height; + + gtk_draw_shadow (darea->style, darea->window, GTK_STATE_NORMAL, + GTK_SHADOW_IN, 0, 0, width, height); + + if (color) { + gdk_gc_set_foreground (gc, color); + gdk_draw_rectangle (darea->window, gc, TRUE, 1, 1, + width - 2, height - 2); + } else { + gdk_gc_set_foreground (gc, &mts->grid_color); + gdk_gc_set_background (gc, &mts->stipple_bg_color); + gdk_gc_set_stipple (gc, mts->stipple); + gdk_gc_set_fill (gc, GDK_OPAQUE_STIPPLED); + gdk_draw_rectangle (darea->window, gc, TRUE, 1, 1, + width - 2, height - 2); + gdk_gc_set_fill (gc, GDK_SOLID); + } + + return TRUE; +} + + +static void +e_meeting_time_selector_alloc_named_color (EMeetingTimeSelector * mts, + const char *name, GdkColor *c) +{ + int failed; + + g_return_if_fail (name != NULL); + g_return_if_fail (c != NULL); + + gdk_color_parse (name, c); + c->pixel = 0; + c->pixel = gdk_color_context_get_pixel (mts->color_context, + c->red, c->green, c->blue, + &failed); + if (failed) + g_warning ("Failed to allocate color: %s\n", name); +} + + +static void +e_meeting_time_selector_options_menu_detacher (GtkWidget *widget, + GtkMenu *menu) +{ + EMeetingTimeSelector *mts; + + g_return_if_fail (widget != NULL); + g_return_if_fail (IS_E_MEETING_TIME_SELECTOR (widget)); + + mts = E_MEETING_TIME_SELECTOR (widget); + g_return_if_fail (mts->options_menu == (GtkWidget*) menu); + + mts->options_menu = NULL; +} + + +static void +e_meeting_time_selector_autopick_menu_detacher (GtkWidget *widget, + GtkMenu *menu) +{ + EMeetingTimeSelector *mts; + + g_return_if_fail (widget != NULL); + g_return_if_fail (IS_E_MEETING_TIME_SELECTOR (widget)); + + mts = E_MEETING_TIME_SELECTOR (widget); + g_return_if_fail (mts->autopick_menu == (GtkWidget*) menu); + + mts->autopick_menu = NULL; +} + + +GtkWidget * +e_meeting_time_selector_new (EMeetingModel *emm) +{ + GtkWidget *mts; + + mts = GTK_WIDGET (gtk_type_new (e_meeting_time_selector_get_type ())); + + e_meeting_time_selector_construct (E_MEETING_TIME_SELECTOR (mts), emm); + + return mts; +} + + +static void +e_meeting_time_selector_destroy (GtkObject *object) +{ + EMeetingTimeSelector *mts; + ETable *real_table; + char *filename; + + mts = E_MEETING_TIME_SELECTOR (object); + + e_meeting_time_selector_remove_timeout (mts); + + gdk_color_context_free (mts->color_context); + gdk_bitmap_unref (mts->stipple); + + filename = g_strdup_printf ("%s/config/et-header-meeting-time-sel", + evolution_dir); + real_table = e_table_scrolled_get_table (E_TABLE_SCROLLED (mts->etable)); + e_table_save_state (real_table, filename); + g_free (filename); + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (*GTK_OBJECT_CLASS (parent_class)->destroy)(object); +} + + +static void +e_meeting_time_selector_realize (GtkWidget *widget) +{ + EMeetingTimeSelector *mts; + + if (GTK_WIDGET_CLASS (parent_class)->realize) + (*GTK_WIDGET_CLASS (parent_class)->realize)(widget); + + mts = E_MEETING_TIME_SELECTOR (widget); + + mts->color_key_gc = gdk_gc_new (widget->window); +} + + +static void +e_meeting_time_selector_unrealize (GtkWidget *widget) +{ + EMeetingTimeSelector *mts; + + mts = E_MEETING_TIME_SELECTOR (widget); + + gdk_gc_unref (mts->color_key_gc); + mts->color_key_gc = NULL; + + if (GTK_WIDGET_CLASS (parent_class)->unrealize) + (*GTK_WIDGET_CLASS (parent_class)->unrealize)(widget); +} + +/* This draws a shadow around the top display and main display. */ +static gint +e_meeting_time_selector_expose_event (GtkWidget *widget, + GdkEventExpose *event) +{ + EMeetingTimeSelector *mts; + + mts = E_MEETING_TIME_SELECTOR (widget); + + e_meeting_time_selector_draw_shadow (mts); + + if (GTK_WIDGET_CLASS (parent_class)->expose_event) + (*GTK_WIDGET_CLASS (parent_class)->expose_event)(widget, event); + + return FALSE; +} + + +static void +e_meeting_time_selector_draw (GtkWidget *widget, + GdkRectangle *area) +{ + EMeetingTimeSelector *mts; + + mts = E_MEETING_TIME_SELECTOR (widget); + + e_meeting_time_selector_draw_shadow (mts); + + if (GTK_WIDGET_CLASS (parent_class)->draw) + (*GTK_WIDGET_CLASS (parent_class)->draw)(widget, area); +} + + +static void +e_meeting_time_selector_draw_shadow (EMeetingTimeSelector *mts) +{ + GtkWidget *widget; + gint x, y, w, h; + + widget = GTK_WIDGET (mts); + + /* Draw the shadow around the graphical displays. */ + x = mts->display_top->allocation.x - 2; + y = mts->display_top->allocation.y - 2; + w = mts->display_top->allocation.width + 4; + h = mts->display_top->allocation.height + mts->display_main->allocation.height + 4; + + gtk_draw_shadow (widget->style, widget->window, GTK_STATE_NORMAL, + GTK_SHADOW_IN, x, y, w, h); +} + + +/* When the main canvas scrolls, we scroll the other canvases. */ +static void +e_meeting_time_selector_hadjustment_changed (GtkAdjustment *adjustment, + EMeetingTimeSelector *mts) +{ + GtkAdjustment *adj; + + adj = GTK_LAYOUT (mts->display_top)->hadjustment; + if (adj->value != adjustment->value) { + adj->value = adjustment->value; + gtk_adjustment_value_changed (adj); + } +} + + +static void +e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment, + EMeetingTimeSelector *mts) +{ + GtkAdjustment *adj; + + adj = e_scroll_frame_get_vadjustment (E_SCROLL_FRAME (mts->etable)); + if (adj->value != adjustment->value) { + adj->value = adjustment->value; + gtk_adjustment_value_changed (adj); + } +} + + +void +e_meeting_time_selector_get_meeting_time (EMeetingTimeSelector *mts, + gint *start_year, + gint *start_month, + gint *start_day, + gint *start_hour, + gint *start_minute, + gint *end_year, + gint *end_month, + gint *end_day, + gint *end_hour, + gint *end_minute) +{ + *start_year = g_date_year (&mts->meeting_start_time.date); + *start_month = g_date_month (&mts->meeting_start_time.date); + *start_day = g_date_day (&mts->meeting_start_time.date); + *start_hour = mts->meeting_start_time.hour; + *start_minute = mts->meeting_start_time.minute; + + *end_year = g_date_year (&mts->meeting_end_time.date); + *end_month = g_date_month (&mts->meeting_end_time.date); + *end_day = g_date_day (&mts->meeting_end_time.date); + *end_hour = mts->meeting_end_time.hour; + *end_minute = mts->meeting_end_time.minute; +} + + +gboolean +e_meeting_time_selector_set_meeting_time (EMeetingTimeSelector *mts, + gint start_year, + gint start_month, + gint start_day, + gint start_hour, + gint start_minute, + gint end_year, + gint end_month, + gint end_day, + gint end_hour, + gint end_minute) +{ + g_return_val_if_fail (IS_E_MEETING_TIME_SELECTOR (mts), FALSE); + + /* Check the dates are valid. */ + if (!g_date_valid_dmy (start_day, start_month, start_year) + || !g_date_valid_dmy (end_day, end_month, end_year) + || start_hour < 0 || start_hour > 23 + || end_hour < 0 || end_hour > 23 + || start_minute < 0 || start_minute > 59 + || end_minute < 0 || end_minute > 59) + return FALSE; + + g_date_set_dmy (&mts->meeting_start_time.date, start_day, start_month, + start_year); + mts->meeting_start_time.hour = start_hour; + mts->meeting_start_time.minute = start_minute; + g_date_set_dmy (&mts->meeting_end_time.date, end_day, end_month, + end_year); + mts->meeting_end_time.hour = end_hour; + mts->meeting_end_time.minute = end_minute; + + mts->meeting_positions_valid = FALSE; + + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); + + /* Set the times in the GnomeDateEdit widgets. */ + e_meeting_time_selector_update_start_date_edit (mts); + e_meeting_time_selector_update_end_date_edit (mts); + + return TRUE; +} + + +void +e_meeting_time_selector_set_working_hours_only (EMeetingTimeSelector *mts, + gboolean working_hours_only) +{ + EMeetingTime saved_time; + + g_return_if_fail (IS_E_MEETING_TIME_SELECTOR (mts)); + + if (mts->working_hours_only == working_hours_only) + return; + + mts->working_hours_only = working_hours_only; + + e_meeting_time_selector_save_position (mts, &saved_time); + e_meeting_time_selector_recalc_grid (mts); + e_meeting_time_selector_restore_position (mts, &saved_time); + + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); + e_meeting_time_selector_update_date_popup_menus (mts); +} + + +void +e_meeting_time_selector_set_working_hours (EMeetingTimeSelector *mts, + gint day_start_hour, + gint day_start_minute, + gint day_end_hour, + gint day_end_minute) +{ + EMeetingTime saved_time; + + g_return_if_fail (IS_E_MEETING_TIME_SELECTOR (mts)); + + if (mts->day_start_hour == day_start_hour + && mts->day_start_minute == day_start_minute + && mts->day_end_hour == day_end_hour + && mts->day_end_minute == day_end_minute) + return; + + mts->day_start_hour = day_start_hour; + mts->day_start_minute = day_start_minute; + mts->day_end_hour = day_end_hour; + mts->day_end_minute = day_end_minute; + + e_meeting_time_selector_save_position (mts, &saved_time); + e_meeting_time_selector_recalc_grid (mts); + e_meeting_time_selector_restore_position (mts, &saved_time); + + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); + e_meeting_time_selector_update_date_popup_menus (mts); +} + + +void +e_meeting_time_selector_set_zoomed_out (EMeetingTimeSelector *mts, + gboolean zoomed_out) +{ + EMeetingTime saved_time; + + g_return_if_fail (IS_E_MEETING_TIME_SELECTOR (mts)); + + if (mts->zoomed_out == zoomed_out) + return; + + mts->zoomed_out = zoomed_out; + + e_meeting_time_selector_save_position (mts, &saved_time); + e_meeting_time_selector_recalc_grid (mts); + e_meeting_time_selector_restore_position (mts, &saved_time); + + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); +} + + +EMeetingTimeSelectorAutopickOption +e_meeting_time_selector_get_autopick_option (EMeetingTimeSelector *mts) +{ + if (GTK_CHECK_MENU_ITEM (mts->autopick_all_item)->active) + return E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES; + if (GTK_CHECK_MENU_ITEM (mts->autopick_all_people_one_resource_item)->active) + return E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE; + if (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_item)->active) + return E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE; + return E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE; +} + + +void +e_meeting_time_selector_set_autopick_option (EMeetingTimeSelector *mts, + EMeetingTimeSelectorAutopickOption autopick_option) +{ + g_return_if_fail (IS_E_MEETING_TIME_SELECTOR (mts)); + + switch (autopick_option) { + case E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES: + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_all_item), TRUE); + break; + case E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE: + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_all_people_one_resource_item), TRUE); + break; + case E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE: + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_item), TRUE); + break; + case E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE: + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (mts->autopick_required_people_one_resource_item), TRUE); + break; + } +} +#if 0 +void +e_meeting_time_selector_attendee_set_send_meeting_to (EMeetingTimeSelector *mts, + gint row, + gboolean send_meeting_to) +{ + EMeetingTimeSelectorAttendee *attendee; + + g_return_if_fail (IS_E_MEETING_TIME_SELECTOR (mts)); + g_return_if_fail (row >= 0); + g_return_if_fail (row < mts->attendees->len); + + attendee = &g_array_index (mts->attendees, + EMeetingTimeSelectorAttendee, row); + attendee->send_meeting_to = send_meeting_to; +} +#endif + +static gint +e_meeting_time_selector_compare_times (EMeetingTime*time1, + EMeetingTime*time2) +{ + gint day_comparison; + + day_comparison = g_date_compare (&time1->date, + &time2->date); + if (day_comparison != 0) + return day_comparison; + + if (time1->hour < time2->hour) + return -1; + if (time1->hour > time2->hour) + return 1; + + if (time1->minute < time2->minute) + return -1; + if (time1->minute > time2->minute) + return 1; + + /* The start times are exactly the same. */ + return 0; +} + + +/* + * DEBUGGING ROUTINES - functions to output various bits of data. + */ + +#ifdef E_MEETING_TIME_SELECTOR_DEBUG + +/* Debugging function to dump information on all attendees. */ +void +e_meeting_time_selector_dump (EMeetingTimeSelector *mts) +{ + EMeetingTimeSelectorAttendee *attendee; + EMeetingTimeSelectorPeriod *period; + gint row, period_num; + gchar buffer[128]; + + g_return_if_fail (IS_E_MEETING_TIME_SELECTOR (mts)); + + g_print ("\n\nAttendee Information:\n"); + + for (row = 0; row < mts->attendees->len; row++) { + attendee = &g_array_index (mts->attendees, + EMeetingTimeSelectorAttendee, row); + g_print ("Attendee: %s\n", attendee->name); + g_print (" Longest Busy Period: %i days\n", + attendee->longest_period_in_days); + + e_meeting_time_selector_attendee_ensure_periods_sorted (mts, attendee); +#if 1 + for (period_num = 0; + period_num < attendee->busy_periods->len; + period_num++) { + period = &g_array_index (attendee->busy_periods, + EMeetingTimeSelectorPeriod, + period_num); + + /* These are just for debugging so don't need i18n. */ + g_date_strftime (buffer, sizeof (buffer), + "%A, %B %d, %Y", &period->start.date); + g_print (" Start: %s %i:%02i\n", buffer, + period->start.hour, period->start.minute); + + g_date_strftime (buffer, sizeof (buffer), + "%A, %B %d, %Y", &period->end.date); + g_print (" End : %s %i:%02i\n", buffer, + period->end.hour, period->end.minute); + } +#endif + } + +} + + +/* This formats a EMeetingTimein a string and returns it. + Note that it uses a static buffer. */ +gchar* +e_meeting_time_selector_dump_time (EMeetingTime*mtstime) +{ + static gchar buffer[128]; + + gchar buffer2[128]; + + /* This is just for debugging so doesn't need i18n. */ + g_date_strftime (buffer, sizeof (buffer), "%A, %B %d, %Y", + &mtstime->date); + sprintf (buffer2, " at %i:%02i", (gint) mtstime->hour, + (gint) mtstime->minute); + strcat (buffer, buffer2); + + return buffer; +} + + +/* This formats a GDate in a string and returns it. + Note that it uses a static buffer. */ +gchar* +e_meeting_time_selector_dump_date (GDate *date) +{ + static gchar buffer[128]; + + /* This is just for debugging so doesn't need i18n. */ + g_date_strftime (buffer, sizeof (buffer), "%A, %B %d, %Y", date); + return buffer; +} + +#endif /* E_MEETING_TIME_SELECTOR_DEBUG */ + + +static void +e_meeting_time_selector_on_invite_others_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts) +{ + + +} + + +static void +e_meeting_time_selector_on_options_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts) +{ + gtk_menu_popup (GTK_MENU (mts->options_menu), NULL, NULL, + e_meeting_time_selector_options_menu_position_callback, + mts, 1, GDK_CURRENT_TIME); +} + + +static void +e_meeting_time_selector_options_menu_position_callback (GtkMenu *menu, + gint *x, + gint *y, + gpointer user_data) +{ + EMeetingTimeSelector *mts; + GtkRequisition menu_requisition; + gint max_x, max_y; + + mts = E_MEETING_TIME_SELECTOR (user_data); + + /* Calculate our preferred position. */ + gdk_window_get_origin (mts->options_button->window, x, y); + *y += mts->options_button->allocation.height; + + /* Now make sure we are on the screen. */ + gtk_widget_size_request (mts->options_menu, &menu_requisition); + max_x = MAX (0, gdk_screen_width () - menu_requisition.width); + max_y = MAX (0, gdk_screen_height () - menu_requisition.height); + *x = CLAMP (*x, 0, max_x); + *y = CLAMP (*y, 0, max_y); +} + +static void +e_meeting_time_selector_refresh_cb (EMeetingTimeSelector *mts) +{ + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); +} + +static void +e_meeting_time_selector_on_update_free_busy (GtkWidget *button, + EMeetingTimeSelector *mts) +{ + + /* Make sure the menu pops down, which doesn't happen by default if + keyboard accelerators are used. */ + if (GTK_WIDGET_VISIBLE (mts->options_menu)) + gtk_menu_popdown (GTK_MENU (mts->options_menu)); + + e_meeting_model_refresh_busy_periods (mts->model, e_meeting_time_selector_refresh_cb, mts); +} + + +static void +e_meeting_time_selector_on_autopick_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts) +{ + gtk_menu_popup (GTK_MENU (mts->autopick_menu), NULL, NULL, + e_meeting_time_selector_autopick_menu_position_callback, + mts, 1, GDK_CURRENT_TIME); +} + + +static void +e_meeting_time_selector_autopick_menu_position_callback (GtkMenu *menu, + gint *x, + gint *y, + gpointer user_data) +{ + EMeetingTimeSelector *mts; + GtkRequisition menu_requisition; + gint max_x, max_y; + + mts = E_MEETING_TIME_SELECTOR (user_data); + + /* Calculate our preferred position. */ + gdk_window_get_origin (mts->autopick_button->window, x, y); + *y += mts->autopick_button->allocation.height; + + /* Now make sure we are on the screen. */ + gtk_widget_size_request (mts->autopick_menu, &menu_requisition); + max_x = MAX (0, gdk_screen_width () - menu_requisition.width); + max_y = MAX (0, gdk_screen_height () - menu_requisition.height); + *x = CLAMP (*x, 0, max_x); + *y = CLAMP (*y, 0, max_y); +} + + +static void +e_meeting_time_selector_on_autopick_option_toggled (GtkWidget *button, + EMeetingTimeSelector *mts) +{ + /* Make sure the menu pops down, which doesn't happen by default if + keyboard accelerators are used. */ + if (GTK_WIDGET_VISIBLE (mts->autopick_menu)) + gtk_menu_popdown (GTK_MENU (mts->autopick_menu)); +} + + +static void +e_meeting_time_selector_on_prev_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts) +{ + e_meeting_time_selector_autopick (mts, FALSE); +} + + +static void +e_meeting_time_selector_on_next_button_clicked (GtkWidget *button, + EMeetingTimeSelector *mts) +{ + e_meeting_time_selector_autopick (mts, TRUE); +} + + +/* This tries to find the previous or next meeting time for which all + attendees will be available. */ +static void +e_meeting_time_selector_autopick (EMeetingTimeSelector *mts, + gboolean forward) +{ + EMeetingTime start_time, end_time, *resource_free; + EMeetingAttendee *attendee; + EMeetingFreeBusyPeriod *period; + EMeetingTimeSelectorAutopickOption autopick_option; + gint duration_days, duration_hours, duration_minutes, row; + gboolean meeting_time_ok, skip_optional = FALSE; + gboolean need_one_resource = FALSE, found_resource; + + /* Get the current meeting duration in days + hours + minutes. */ + e_meeting_time_selector_calculate_time_difference (&mts->meeting_start_time, &mts->meeting_end_time, &duration_days, &duration_hours, &duration_minutes); + + /* Find the first appropriate start time. */ + start_time = mts->meeting_start_time; + if (forward) + e_meeting_time_selector_find_nearest_interval (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes); + else + e_meeting_time_selector_find_nearest_interval_backward (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes); + + /* Determine if we can skip optional people and if we only need one + resource based on the autopick option. */ + autopick_option = e_meeting_time_selector_get_autopick_option (mts); + if (autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE + || autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE) + skip_optional = TRUE; + if (autopick_option == E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE + || autopick_option == E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE) + need_one_resource = TRUE; + + /* Keep moving forward or backward until we find a possible meeting + time. */ + for (;;) { + meeting_time_ok = TRUE; + found_resource = FALSE; + resource_free = NULL; + + /* Step through each attendee, checking if the meeting time + intersects one of the attendees busy periods. */ + for (row = 0; row < e_meeting_model_count_attendees (mts->model); row++) { + attendee = e_meeting_model_find_attendee_at_row (mts->model, row); + + /* Skip optional people if they don't matter. */ + if (skip_optional && e_meeting_attendee_get_atype (attendee) == E_MEETING_ATTENDEE_OPTIONAL_PERSON) + continue; + + period = e_meeting_time_selector_find_time_clash (mts, attendee, &start_time, &end_time); + + if (need_one_resource && e_meeting_attendee_get_atype (attendee) == E_MEETING_ATTENDEE_RESOURCE) { + if (period) { + /* We want to remember the closest + prev/next time that one resource is + available, in case we don't find any + free resources. */ + if (forward) { + if (!resource_free || e_meeting_time_selector_compare_times (resource_free, &period->end) > 0) + resource_free = &period->end; + } else { + if (!resource_free || e_meeting_time_selector_compare_times (resource_free, &period->start) < 0) + resource_free = &period->start; + } + + } else { + found_resource = TRUE; + } + } else if (period) { + /* Skip the period which clashed. */ + if (forward) { + start_time = period->end; + } else { + start_time = period->start; + e_meeting_time_selector_adjust_time (&start_time, -duration_days, -duration_hours, -duration_minutes); + } + meeting_time_ok = FALSE; + break; + } + } + + /* Check that we found one resource if necessary. If not, skip + to the closest time that a resource is free. Note that if + there are no resources, resource_free will never get set, + so we assume the meeting time is OK. */ + if (meeting_time_ok && need_one_resource && !found_resource + && resource_free) { + if (forward) { + start_time = *resource_free; + } else { + start_time = *resource_free; + e_meeting_time_selector_adjust_time (&start_time, -duration_days, -duration_hours, -duration_minutes); + } + meeting_time_ok = FALSE; + } + + if (meeting_time_ok) { + mts->meeting_start_time = start_time; + mts->meeting_end_time = end_time; + mts->meeting_positions_valid = FALSE; + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); + + /* Make sure the time is shown. */ + e_meeting_time_selector_ensure_meeting_time_shown (mts); + + /* Set the times in the GnomeDateEdit widgets. */ + e_meeting_time_selector_update_start_date_edit (mts); + e_meeting_time_selector_update_end_date_edit (mts); + return; + } + + /* Move forward to the next possible interval. */ + if (forward) + e_meeting_time_selector_find_nearest_interval (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes); + else + e_meeting_time_selector_find_nearest_interval_backward (mts, &start_time, &end_time, duration_days, duration_hours, duration_minutes); + } +} + + +static void +e_meeting_time_selector_calculate_time_difference (EMeetingTime*start, + EMeetingTime*end, + gint *days, + gint *hours, + gint *minutes) +{ + *days = g_date_julian (&end->date) - g_date_julian (&start->date); + *hours = end->hour - start->hour; + *minutes = end->minute - start->minute; + if (*minutes < 0) { + *minutes += 60; + *hours = *hours - 1; + } + if (*hours < 0) { + *hours += 24; + *days = *days - 1; + } +} + + +/* This moves the given time forward to the next suitable start of a meeting. + If zoomed_out is set, this means every hour. If not every half-hour. */ +static void +e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts, + EMeetingTime*start_time, + EMeetingTime*end_time, + gint days, gint hours, gint mins) +{ + gint minutes_shown; + gboolean set_to_start_of_working_day = FALSE; + + if (mts->zoomed_out) { + start_time->hour++; + start_time->minute = 0; + } else { + start_time->minute += 30; + start_time->minute -= start_time->minute % 30; + } + e_meeting_time_selector_fix_time_overflows (start_time); + + *end_time = *start_time; + e_meeting_time_selector_adjust_time (end_time, days, hours, mins); + + /* Check if the interval is less than a day as seen in the display. + If it isn't we don't worry about the working day. */ + if (!mts->working_hours_only || days > 0) + return; + minutes_shown = (mts->day_end_hour - mts->day_start_hour) * 60; + minutes_shown += mts->day_end_minute - mts->day_start_minute; + if (hours * 60 + mins > minutes_shown) + return; + + /* If the meeting time finishes past the end of the working day, move + onto the start of the next working day. If the meeting time starts + before the working day, move it on as well. */ + if (start_time->hour > mts->day_end_hour + || (start_time->hour == mts->day_end_hour + && start_time->minute > mts->day_end_minute) + || end_time->hour > mts->day_end_hour + || (end_time->hour == mts->day_end_hour + && end_time->minute > mts->day_end_minute)) { + g_date_add_days (&start_time->date, 1); + set_to_start_of_working_day = TRUE; + } else if (start_time->hour < mts->day_start_hour + || (start_time->hour == mts->day_start_hour + && start_time->minute < mts->day_start_minute)) { + set_to_start_of_working_day = TRUE; + } + + if (set_to_start_of_working_day) { + start_time->hour = mts->day_start_hour; + start_time->minute = mts->day_start_minute; + + if (mts->zoomed_out) { + if (start_time->minute > 0) { + start_time->hour++; + start_time->minute = 0; + } + } else { + start_time->minute += 29; + start_time->minute -= start_time->minute % 30; + } + e_meeting_time_selector_fix_time_overflows (start_time); + + *end_time = *start_time; + e_meeting_time_selector_adjust_time (end_time, days, hours, mins); + } +} + + +/* This moves the given time backward to the next suitable start of a meeting. + If zoomed_out is set, this means every hour. If not every half-hour. */ +static void +e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts, + EMeetingTime*start_time, + EMeetingTime*end_time, + gint days, gint hours, gint mins) +{ + gint new_hour, minutes_shown; + gboolean set_to_end_of_working_day = FALSE; + + new_hour = start_time->hour; + if (mts->zoomed_out) { + if (start_time->minute == 0) + new_hour--; + start_time->minute = 0; + } else { + if (start_time->minute == 0) { + start_time->minute = 30; + new_hour--; + } else if (start_time->minute <= 30) + start_time->minute = 0; + else + start_time->minute = 30; + } + if (new_hour < 0) { + new_hour += 24; + g_date_subtract_days (&start_time->date, 1); + } + start_time->hour = new_hour; + + *end_time = *start_time; + e_meeting_time_selector_adjust_time (end_time, days, hours, mins); + + /* Check if the interval is less than a day as seen in the display. + If it isn't we don't worry about the working day. */ + if (!mts->working_hours_only || days > 0) + return; + minutes_shown = (mts->day_end_hour - mts->day_start_hour) * 60; + minutes_shown += mts->day_end_minute - mts->day_start_minute; + if (hours * 60 + mins > minutes_shown) + return; + + /* If the meeting time finishes past the end of the working day, move + back to the end of the working day. If the meeting time starts + before the working day, move it back to the end of the previous + working day. */ + if (start_time->hour > mts->day_end_hour + || (start_time->hour == mts->day_end_hour + && start_time->minute > mts->day_end_minute) + || end_time->hour > mts->day_end_hour + || (end_time->hour == mts->day_end_hour + && end_time->minute > mts->day_end_minute)) { + set_to_end_of_working_day = TRUE; + } else if (start_time->hour < mts->day_start_hour + || (start_time->hour == mts->day_start_hour + && start_time->minute < mts->day_start_minute)) { + g_date_subtract_days (&end_time->date, 1); + set_to_end_of_working_day = TRUE; + } + + if (set_to_end_of_working_day) { + end_time->hour = mts->day_end_hour; + end_time->minute = mts->day_end_minute; + *start_time = *end_time; + e_meeting_time_selector_adjust_time (start_time, -days, -hours, -mins); + + if (mts->zoomed_out) { + start_time->minute = 0; + } else { + start_time->minute -= start_time->minute % 30; + } + + *end_time = *start_time; + e_meeting_time_selector_adjust_time (end_time, days, hours, mins); + } +} + + +/* This adds on the given days, hours & minutes to a EMeetingTimeSelectorTime. + It is used to calculate the end of a period given a start & duration. + Days, hours & minutes can be negative, to move backwards, but they should + be within normal ranges, e.g. hours should be between -23 and 23. */ +static void +e_meeting_time_selector_adjust_time (EMeetingTime*mtstime, + gint days, gint hours, gint minutes) +{ + gint new_hours, new_minutes; + + /* We have to handle negative values for hous and minutes here, since + EMeetingTimeuses guint8s to store them. */ + new_minutes = mtstime->minute + minutes; + if (new_minutes < 0) { + new_minutes += 60; + hours -= 1; + } + + new_hours = mtstime->hour + hours; + if (new_hours < 0) { + new_hours += 24; + days -= 1; + } + + g_date_add_days (&mtstime->date, days); + mtstime->hour = new_hours; + mtstime->minute = new_minutes; + + e_meeting_time_selector_fix_time_overflows (mtstime); +} + + +/* This looks for any busy period of the given attendee which clashes with + the start and end time. It uses a binary search. */ +static EMeetingFreeBusyPeriod * +e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts, + EMeetingAttendee *attendee, + EMeetingTime*start_time, + EMeetingTime*end_time) +{ + EMeetingFreeBusyPeriod *period; + const GArray *busy_periods; + gint period_num; + + busy_periods = e_meeting_attendee_get_busy_periods (attendee); + period_num = e_meeting_attendee_find_first_busy_period (attendee, &start_time->date); + + if (period_num == -1) + return NULL; + + /* Step forward through the busy periods until we find a clash or we + go past the end_time. */ + while (period_num < busy_periods->len) { + period = &g_array_index (busy_periods, EMeetingFreeBusyPeriod, period_num); + + /* If the period starts at or after the end time, there is no + clash and we are finished. The busy periods are sorted by + their start times, so all the rest will be later. */ + if (e_meeting_time_selector_compare_times (&period->start, end_time) >= 0) + return NULL; + + /* If the period ends after the start time, we have found a + clash. From the above test we already know the busy period + isn't completely after the meeting time. */ + if (e_meeting_time_selector_compare_times (&period->end, start_time) > 0) + return period; + + period_num++; + } + + return NULL; +} + +static void +e_meeting_time_selector_on_zoomed_out_toggled (GtkWidget *menuitem, + EMeetingTimeSelector *mts) +{ + /* Make sure the menu pops down, which doesn't happen by default if + keyboard accelerators are used. */ + if (GTK_WIDGET_VISIBLE (mts->options_menu)) + gtk_menu_popdown (GTK_MENU (mts->options_menu)); + + e_meeting_time_selector_set_zoomed_out (mts, GTK_CHECK_MENU_ITEM (menuitem)->active); +} + + +static void +e_meeting_time_selector_on_working_hours_toggled (GtkWidget *menuitem, + EMeetingTimeSelector *mts) +{ + /* Make sure the menu pops down, which doesn't happen by default if + keyboard accelerators are used. */ + if (GTK_WIDGET_VISIBLE (mts->options_menu)) + gtk_menu_popdown (GTK_MENU (mts->options_menu)); + + e_meeting_time_selector_set_working_hours_only (mts, GTK_CHECK_MENU_ITEM (menuitem)->active); +} + + +/* This recalculates day_width, first_hour_shown and last_hour_shown. */ +static void +e_meeting_time_selector_recalc_grid (EMeetingTimeSelector *mts) +{ + if (mts->working_hours_only) { + mts->first_hour_shown = mts->day_start_hour; + mts->last_hour_shown = mts->day_end_hour; + if (mts->day_end_minute != 0) + mts->last_hour_shown += 1; + } else { + mts->first_hour_shown = 0; + mts->last_hour_shown = 24; + } + + /* In the brief view we use the nearest hours divisible by 3. */ + if (mts->zoomed_out) { + mts->first_hour_shown -= mts->first_hour_shown % 3; + mts->last_hour_shown += 2; + mts->last_hour_shown -= mts->last_hour_shown % 3; + } + + mts->day_width = mts->col_width * (mts->last_hour_shown - mts->first_hour_shown); + if (mts->zoomed_out) + mts->day_width /= 3; + + /* Add one pixel for the extra vertical grid line. */ + mts->day_width++; + + gnome_canvas_set_scroll_region (GNOME_CANVAS (mts->display_top), + 0, 0, + mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN, + mts->row_height * 3); + e_meeting_time_selector_update_main_canvas_scroll_region (mts); + + e_meeting_time_selector_recalc_date_format (mts); + mts->meeting_positions_valid = FALSE; +} + + +/* This saves the first visible time in the given EMeetingTimeSelectorTime. */ +static void +e_meeting_time_selector_save_position (EMeetingTimeSelector *mts, + EMeetingTime*mtstime) +{ + gint scroll_x, scroll_y; + + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main), + &scroll_x, &scroll_y); + e_meeting_time_selector_calculate_time (mts, scroll_x, mtstime); +} + + +/* This restores a saved position. */ +static void +e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts, + EMeetingTime*mtstime) +{ + gint scroll_x, scroll_y, new_scroll_x; + + new_scroll_x = e_meeting_time_selector_calculate_time_position (mts, + mtstime); + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main), + &scroll_x, &scroll_y); + gnome_canvas_scroll_to (GNOME_CANVAS (mts->display_main), + new_scroll_x, scroll_y); +} + + +/* This returns the x pixel coords of the meeting time in the entire scroll + region. It recalculates them if they have been marked as invalid. + If it returns FALSE then no meeting time is set or the meeting time is + not visible in the current scroll area. */ +gboolean +e_meeting_time_selector_get_meeting_time_positions (EMeetingTimeSelector *mts, + gint *start_x, + gint *end_x) +{ + if (mts->meeting_positions_valid) { + if (mts->meeting_positions_in_scroll_area) { + *start_x = mts->meeting_start_x; + *end_x = mts->meeting_end_x; + return TRUE; + } else { + return FALSE; + } + } + + mts->meeting_positions_valid = TRUE; + + /* Check if the days aren't in our current range. */ + if (g_date_compare (&mts->meeting_start_time.date, &mts->last_date_shown) > 0 + || g_date_compare (&mts->meeting_end_time.date, &mts->first_date_shown) < 0) { + mts->meeting_positions_in_scroll_area = FALSE; + return FALSE; + } + + mts->meeting_positions_in_scroll_area = TRUE; + *start_x = mts->meeting_start_x = e_meeting_time_selector_calculate_time_position (mts, &mts->meeting_start_time); + *end_x = mts->meeting_end_x = e_meeting_time_selector_calculate_time_position (mts, &mts->meeting_end_time); + + return TRUE; +} + + +/* This recalculates the date format to used, by computing the width of the + longest date strings in the widget's font and seeing if they fit. */ +static void +e_meeting_time_selector_recalc_date_format (EMeetingTimeSelector *mts) +{ + /* An array of dates, one for each month in the year 2000. They must + all be Sundays. */ + static const int days[12] = { 23, 20, 19, 23, 21, 18, + 23, 20, 17, 22, 19, 24 }; + GDate date; + gint max_date_width, longest_weekday_width, longest_month_width, width; + gint day, longest_weekday, month, longest_month; + gchar buffer[128]; + GdkFont *font; + + font = GTK_WIDGET (mts)->style->font; + + /* Calculate the maximum date width we can fit into the display. */ + max_date_width = mts->day_width - 2; + + /* Find the biggest full weekday name. We start on a particular + Monday and go through seven days. */ + g_date_clear (&date, 1); + g_date_set_dmy (&date, 3, 1, 2000); /* Monday 3rd Jan 2000. */ + longest_weekday_width = 0; + longest_weekday = G_DATE_MONDAY; + for (day = G_DATE_MONDAY; day <= G_DATE_SUNDAY; day++) { + g_date_strftime (buffer, sizeof (buffer), "%A", &date); + width = gdk_string_width (font, buffer); + if (width > longest_weekday_width) { + longest_weekday = day; + longest_weekday_width = width; + } + g_date_add_days (&date, 1); + } + + /* Now find the biggest month name. */ + longest_month_width = 0; + longest_month = G_DATE_JANUARY; + for (month = G_DATE_JANUARY; month <= G_DATE_DECEMBER; month++) { + g_date_set_month (&date, month); + g_date_strftime (buffer, sizeof (buffer), "%B", &date); + width = gdk_string_width (font, buffer); + if (width > longest_month_width) { + longest_month = month; + longest_month_width = width; + } + } + + /* See if we can use the full date. We want to use a date with a + month day > 20 and also the longest weekday. We use a + pre-calculated array of days for each month and add on the + weekday (which is 1 (Mon) to 7 (Sun). */ + g_date_set_dmy (&date, days[longest_month - 1] + longest_weekday, + longest_month, 2000); + /* This is a strftime() format string %A = full weekday name, + %B = full month name, %d = month day, %Y = full year. */ + g_date_strftime (buffer, sizeof (buffer), _("%A, %B %d, %Y"), &date); + + g_print ("longest_month: %i longest_weekday: %i date: %s\n", + longest_month, longest_weekday, buffer); + + if (gdk_string_width (font, buffer) < max_date_width) { + mts->date_format = E_MEETING_TIME_SELECTOR_DATE_FULL; + return; + } + + /* Now try it with abbreviated weekday names. */ + longest_weekday_width = 0; + longest_weekday = G_DATE_MONDAY; + g_date_set_dmy (&date, 3, 1, 2000); /* Monday 3rd Jan 2000. */ + for (day = G_DATE_MONDAY; day <= G_DATE_SUNDAY; day++) { + g_date_strftime (buffer, sizeof (buffer), "%a", &date); + width = gdk_string_width (font, buffer); + if (width > longest_weekday_width) { + longest_weekday = day; + longest_weekday_width = width; + } + g_date_add_days (&date, 1); + } + + g_date_set_dmy (&date, days[longest_month - 1] + longest_weekday, + longest_month, 2000); + /* This is a strftime() format string %a = abbreviated weekday name, + %m = month number, %d = month day, %Y = full year. */ + g_date_strftime (buffer, sizeof (buffer), _("%a %m/%d/%Y"), &date); + + g_print ("longest_month: %i longest_weekday: %i date: %s\n", + longest_month, longest_weekday, buffer); + + if (gdk_string_width (font, buffer) < max_date_width) + mts->date_format = E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY; + else + mts->date_format = E_MEETING_TIME_SELECTOR_DATE_SHORT; +} + + +/* Turn off the background of the canvas windows. This reduces flicker + considerably when scrolling. (Why isn't it in GnomeCanvas?). */ +static void +e_meeting_time_selector_on_canvas_realized (GtkWidget *widget, + EMeetingTimeSelector *mts) +{ + gdk_window_set_back_pixmap (GTK_LAYOUT (widget)->bin_window, + NULL, FALSE); +} + + +/* This is called when the meeting start time GnomeDateEdit is changed, + either via the "date_changed". "time_changed" or "activate" signals on one + of the GtkEntry widgets. So don't use the widget parameter since it may be + one of the child GtkEntry widgets. */ +static void +e_meeting_time_selector_on_start_time_changed (GtkWidget *widget, + EMeetingTimeSelector *mts) +{ + gint duration_days, duration_hours, duration_minutes; + EMeetingTime mtstime; + time_t newtime; + struct tm *newtime_tm; + + newtime = e_date_edit_get_time (E_DATE_EDIT (mts->start_date_edit)); + newtime_tm = localtime (&newtime); + g_date_clear (&mtstime.date, 1); + g_date_set_time (&mtstime.date, newtime); + mtstime.hour = newtime_tm->tm_hour; + mtstime.minute = newtime_tm->tm_min; + + /* If the time hasn't changed, just return. */ + if (e_meeting_time_selector_compare_times (&mtstime, &mts->meeting_start_time) == 0) + return; + + /* Calculate the current meeting duration. */ + e_meeting_time_selector_calculate_time_difference (&mts->meeting_start_time, &mts->meeting_end_time, &duration_days, &duration_hours, &duration_minutes); + + /* Set the new start time. */ + mts->meeting_start_time = mtstime; + + /* Update the end time so the meeting duration stays the same. */ + mts->meeting_end_time = mts->meeting_start_time; + e_meeting_time_selector_adjust_time (&mts->meeting_end_time, duration_days, duration_hours, duration_minutes); + e_meeting_time_selector_update_end_date_edit (mts); + + mts->meeting_positions_valid = FALSE; + e_meeting_time_selector_ensure_meeting_time_shown (mts); + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); +} + + +/* This is called when the meeting end time GnomeDateEdit is changed, + either via the "date_changed", "time_changed" or "activate" signals on one + of the GtkEntry widgets. So don't use the widget parameter since it may be + one of the child GtkEntry widgets. */ +static void +e_meeting_time_selector_on_end_time_changed (GtkWidget *widget, + EMeetingTimeSelector *mts) +{ + EMeetingTime mtstime; + time_t newtime; + struct tm *newtime_tm; + + newtime = e_date_edit_get_time (E_DATE_EDIT (mts->end_date_edit)); + newtime_tm = localtime (&newtime); + g_date_clear (&mtstime.date, 1); + g_date_set_time (&mtstime.date, newtime); + mtstime.hour = newtime_tm->tm_hour; + mtstime.minute = newtime_tm->tm_min; + + /* If the time hasn't changed, just return. */ + if (e_meeting_time_selector_compare_times (&mtstime, &mts->meeting_end_time) == 0) + return; + + /* Set the new end time. */ + mts->meeting_end_time = mtstime; + + /* If the start time is after the end time, set it to the same time. */ + if (e_meeting_time_selector_compare_times (&mtstime, &mts->meeting_start_time) < 0) { + /* We set it first, before updating the widget, so the signal + handler will just return. */ + mts->meeting_start_time = mtstime; + e_meeting_time_selector_update_start_date_edit (mts); + } + + mts->meeting_positions_valid = FALSE; + e_meeting_time_selector_ensure_meeting_time_shown (mts); + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); +} + + +/* This updates the ranges shown in the GnomeDateEdit popup menus, according + to working_hours_only etc. */ +static void +e_meeting_time_selector_update_date_popup_menus (EMeetingTimeSelector *mts) +{ + EDateEdit *start_edit, *end_edit; + gint low_hour, high_hour; + + start_edit = E_DATE_EDIT (mts->start_date_edit); + end_edit = E_DATE_EDIT (mts->end_date_edit); + + if (mts->working_hours_only) { + low_hour = mts->day_start_hour; + high_hour = mts->day_end_hour; + } else { + low_hour = 0; + high_hour = 23; + } + + e_date_edit_set_time_popup_range (start_edit, low_hour, high_hour); + e_date_edit_set_time_popup_range (end_edit, low_hour, high_hour); +} + + +static void +e_meeting_time_selector_on_canvas_size_allocate (GtkWidget *widget, + GtkAllocation *allocation, + EMeetingTimeSelector *mts) +{ + e_meeting_time_selector_update_main_canvas_scroll_region (mts); + + e_meeting_time_selector_ensure_meeting_time_shown (mts); +} + +/* This updates the canvas scroll regions according to the number of attendees. + If the total height needed is less than the height of the canvas, we must + use the height of the canvas, or it causes problems. */ +static void +e_meeting_time_selector_update_main_canvas_scroll_region (EMeetingTimeSelector *mts) +{ + gint height, canvas_height; + + height = mts->row_height * (e_meeting_model_count_attendees (mts->model) + 1); + canvas_height = GTK_WIDGET (mts->display_main)->allocation.height; + + height = MAX (height, canvas_height); + + gnome_canvas_set_scroll_region (GNOME_CANVAS (mts->display_main), + 0, 0, + mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN, + height); +} + + +/* This changes the meeting time based on the given x coordinate and whether + we are dragging the start or end bar. It returns the new position, which + will be swapped if the start bar is dragged past the end bar or vice versa. + It make sure the meeting time is never dragged outside the visible canvas + area. */ +void +e_meeting_time_selector_drag_meeting_time (EMeetingTimeSelector *mts, + gint x) +{ + EMeetingTime first_time, last_time, drag_time, *time_to_set; + gint scroll_x, scroll_y, canvas_width; + gboolean set_both_times = FALSE; + + /* Get the x coords of visible part of the canvas. */ + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main), + &scroll_x, &scroll_y); + canvas_width = mts->display_main->allocation.width; + + /* Save the x coordinate for the timeout handler. */ + mts->last_drag_x = (x < scroll_x) ? x - scroll_x + : x - scroll_x - canvas_width + 1; + + /* Check if the mouse is off the edge of the canvas. */ + if (x < scroll_x || x > scroll_x + canvas_width) { + /* If we haven't added a timeout function, add one. */ + if (mts->auto_scroll_timeout_id == 0) { + mts->auto_scroll_timeout_id = g_timeout_add (60, e_meeting_time_selector_timeout_handler, mts); + mts->scroll_count = 0; + + /* Call the handler to start scrolling now. */ + e_meeting_time_selector_timeout_handler (mts); + return; + } + } else { + e_meeting_time_selector_remove_timeout (mts); + } + + /* Calculate the minimum & maximum times we can use, based on the + scroll offsets and whether zoomed_out is set. */ + e_meeting_time_selector_calculate_time (mts, scroll_x, &first_time); + e_meeting_time_selector_calculate_time (mts, scroll_x + canvas_width - 1, + &last_time); + if (mts->zoomed_out) { + if (first_time.minute > 30) + first_time.hour++; + first_time.minute = 0; + last_time.minute = 0; + } else { + first_time.minute += 15; + first_time.minute -= first_time.minute % 30; + last_time.minute -= last_time.minute % 30; + } + e_meeting_time_selector_fix_time_overflows (&first_time); + e_meeting_time_selector_fix_time_overflows (&last_time); + + /* Calculate the time from x coordinate. */ + e_meeting_time_selector_calculate_time (mts, x, &drag_time); + + /* Calculate the nearest half-hour or hour, depending on whether + zoomed_out is set. */ + if (mts->zoomed_out) { + if (drag_time.minute > 30) + drag_time.hour++; + drag_time.minute = 0; + } else { + drag_time.minute += 15; + drag_time.minute -= drag_time.minute % 30; + } + e_meeting_time_selector_fix_time_overflows (&drag_time); + + /* Now make sure we are between first_time & last_time. */ + if (e_meeting_time_selector_compare_times (&drag_time, &first_time) < 0) + drag_time = first_time; + if (e_meeting_time_selector_compare_times (&drag_time, &last_time) > 0) + drag_time = last_time; + + /* Set the meeting start or end time to drag_time. */ + if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START) + time_to_set = &mts->meeting_start_time; + else + time_to_set = &mts->meeting_end_time; + + /* If the time is unchanged, just return. */ + if (e_meeting_time_selector_compare_times (time_to_set, &drag_time) == 0) + return; + + *time_to_set = drag_time; + + /* Check if the start time and end time need to be switched. */ + if (e_meeting_time_selector_compare_times (&mts->meeting_start_time, + &mts->meeting_end_time) > 0) { + drag_time = mts->meeting_start_time; + mts->meeting_start_time = mts->meeting_end_time; + mts->meeting_end_time = drag_time; + + if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START) + mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_END; + else + mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_START; + + set_both_times = TRUE; + } + + /* Mark the calculated positions as invalid. */ + mts->meeting_positions_valid = FALSE; + + /* Redraw the canvases. */ + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); + + /* Set the times in the GnomeDateEdit widgets. */ + if (set_both_times + || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START) + e_meeting_time_selector_update_start_date_edit (mts); + + if (set_both_times + || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END) + e_meeting_time_selector_update_end_date_edit (mts); +} + + +/* This is the timeout function which handles auto-scrolling when the user is + dragging one of the meeting time vertical bars outside the left or right + edge of the canvas. */ +static gboolean +e_meeting_time_selector_timeout_handler (gpointer data) +{ + EMeetingTimeSelector *mts; + EMeetingTime drag_time, *time_to_set; + gint scroll_x, max_scroll_x, scroll_y, canvas_width; + gint scroll_speed, scroll_offset; + gboolean set_both_times = FALSE; + + mts = E_MEETING_TIME_SELECTOR (data); + + GDK_THREADS_ENTER (); + + /* Return if we don't need to scroll yet. */ + if (mts->scroll_count-- > 0) { + GDK_THREADS_LEAVE (); + return TRUE; + } + + /* Get the x coords of visible part of the canvas. */ + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main), + &scroll_x, &scroll_y); + canvas_width = mts->display_main->allocation.width; + + /* Calculate the scroll delay, between 0 and MAX_SCROLL_SPEED. */ + scroll_speed = abs (mts->last_drag_x / E_MEETING_TIME_SELECTOR_SCROLL_INCREMENT_WIDTH); + scroll_speed = MIN (scroll_speed, + E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED); + + /* Reset the scroll count. */ + mts->scroll_count = E_MEETING_TIME_SELECTOR_MAX_SCROLL_SPEED - scroll_speed; + + /* Calculate how much we need to scroll. */ + if (mts->last_drag_x >= 0) + scroll_offset = mts->col_width; + else + scroll_offset = -mts->col_width; + + scroll_x += scroll_offset; + max_scroll_x = (mts->day_width * E_MEETING_TIME_SELECTOR_DAYS_SHOWN) + - canvas_width; + scroll_x = CLAMP (scroll_x, 0, max_scroll_x); + + /* Calculate the minimum or maximum visible time in the canvas, which + we will now set the dragged time to. */ + if (scroll_offset > 0) { + e_meeting_time_selector_calculate_time (mts, + scroll_x + canvas_width - 1, + &drag_time); + if (mts->zoomed_out) { + drag_time.minute = 0; + } else { + drag_time.minute -= drag_time.minute % 30; + } + } else { + e_meeting_time_selector_calculate_time (mts, scroll_x, + &drag_time); + if (mts->zoomed_out) { + if (drag_time.minute > 30) + drag_time.hour++; + drag_time.minute = 0; + } else { + drag_time.minute += 15; + drag_time.minute -= drag_time.minute % 30; + } + } + e_meeting_time_selector_fix_time_overflows (&drag_time); + + /* Set the meeting start or end time to drag_time. */ + if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START) + time_to_set = &mts->meeting_start_time; + else + time_to_set = &mts->meeting_end_time; + + /* If the time is unchanged, just return. */ + if (e_meeting_time_selector_compare_times (time_to_set, &drag_time) == 0) { + GDK_THREADS_LEAVE (); + return TRUE; + } + + *time_to_set = drag_time; + + /* Check if the start time and end time need to be switched. */ + if (e_meeting_time_selector_compare_times (&mts->meeting_start_time, &mts->meeting_end_time) > 0) { + drag_time = mts->meeting_start_time; + mts->meeting_start_time = mts->meeting_end_time; + mts->meeting_end_time = drag_time; + + if (mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START) + mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_END; + else + mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_START; + + set_both_times = TRUE; + } + + /* Mark the calculated positions as invalid. */ + mts->meeting_positions_valid = FALSE; + + /* Set the times in the GnomeDateEdit widgets. */ + if (set_both_times + || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_START) + e_meeting_time_selector_update_start_date_edit (mts); + + if (set_both_times + || mts->dragging_position == E_MEETING_TIME_SELECTOR_POS_END) + e_meeting_time_selector_update_end_date_edit (mts); + + /* Redraw the canvases. We freeze and thaw the layouts so that they + get redrawn completely. Otherwise the pixels get scrolled left or + right which is not good for us (since our vertical bars have been + moved) and causes flicker. */ + gtk_layout_freeze (GTK_LAYOUT (mts->display_main)); + gtk_layout_freeze (GTK_LAYOUT (mts->display_top)); + gnome_canvas_scroll_to (GNOME_CANVAS (mts->display_main), + scroll_x, scroll_y); + gnome_canvas_scroll_to (GNOME_CANVAS (mts->display_top), + scroll_x, scroll_y); + gtk_layout_thaw (GTK_LAYOUT (mts->display_main)); + gtk_layout_thaw (GTK_LAYOUT (mts->display_top)); + + GDK_THREADS_LEAVE (); + return TRUE; +} + + +/* This removes our auto-scroll timeout function, if we have one installed. */ +void +e_meeting_time_selector_remove_timeout (EMeetingTimeSelector *mts) +{ + if (mts->auto_scroll_timeout_id) { + g_source_remove (mts->auto_scroll_timeout_id); + mts->auto_scroll_timeout_id = 0; + } +} + + +/* This updates the GnomeDateEdit widget displaying the meeting start time. */ +static void +e_meeting_time_selector_update_start_date_edit (EMeetingTimeSelector *mts) +{ + struct tm start_tm; + time_t start_time_t; + + g_date_to_struct_tm (&mts->meeting_start_time.date, &start_tm); + start_tm.tm_hour = mts->meeting_start_time.hour; + start_tm.tm_min = mts->meeting_start_time.minute; + start_time_t = mktime (&start_tm); + e_date_edit_set_time (E_DATE_EDIT (mts->start_date_edit), + start_time_t); +} + + +/* This updates the GnomeDateEdit widget displaying the meeting end time. */ +static void +e_meeting_time_selector_update_end_date_edit (EMeetingTimeSelector *mts) +{ + struct tm end_tm; + time_t end_time_t; + + g_date_to_struct_tm (&mts->meeting_end_time.date, &end_tm); + end_tm.tm_hour = mts->meeting_end_time.hour; + end_tm.tm_min = mts->meeting_end_time.minute; + end_time_t = mktime (&end_tm); + e_date_edit_set_time (E_DATE_EDIT (mts->end_date_edit), + end_time_t); +} + + +/* This ensures that the meeting time is shown on screen, by scrolling the + canvas and possibly by changing the range of dates shown in the canvas. */ +static void +e_meeting_time_selector_ensure_meeting_time_shown (EMeetingTimeSelector *mts) +{ + gint start_x, end_x, scroll_x, scroll_y, canvas_width; + gint new_scroll_x; + gboolean fits_in_canvas; + + /* Check if we need to change the range of dates shown. */ + if (g_date_compare (&mts->meeting_start_time.date, + &mts->first_date_shown) < 0 + || g_date_compare (&mts->meeting_end_time.date, + &mts->last_date_shown) > 0) { + e_meeting_time_selector_update_dates_shown (mts); + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); + } + + /* If all of the meeting time is visible, just return. */ + e_meeting_time_selector_get_meeting_time_positions (mts, &start_x, + &end_x); + gnome_canvas_get_scroll_offsets (GNOME_CANVAS (mts->display_main), + &scroll_x, &scroll_y); + canvas_width = mts->display_main->allocation.width; + if (start_x > scroll_x && end_x <= scroll_x + canvas_width) + return; + + fits_in_canvas = end_x - start_x < canvas_width ? TRUE : FALSE; + + /* If the meeting is not entirely visible, either center it if it is + smaller than the canvas, or show the start of it if it is big. */ + if (fits_in_canvas) { + new_scroll_x = (start_x + end_x - canvas_width) / 2; + } else { + new_scroll_x = start_x; + } + gnome_canvas_scroll_to (GNOME_CANVAS (mts->display_main), + new_scroll_x, scroll_y); +} + + +/* This updates the range of dates shown in the canvas, to make sure that the + currently selected meeting time is in the range. */ +static void +e_meeting_time_selector_update_dates_shown (EMeetingTimeSelector *mts) +{ + mts->first_date_shown = mts->meeting_start_time.date; + g_date_subtract_days (&mts->first_date_shown, 60); + + mts->last_date_shown = mts->first_date_shown; + g_date_add_days (&mts->last_date_shown, E_MEETING_TIME_SELECTOR_DAYS_SHOWN - 1); +} + + +/* This checks if the time's hour is over 24 or its minute is over 60 and if + so it updates the day/hour appropriately. Note that hours and minutes are + stored in guint8's so they can't overflow by much. */ +void +e_meeting_time_selector_fix_time_overflows (EMeetingTime*mtstime) +{ + gint hours_to_add, days_to_add; + + hours_to_add = mtstime->minute / 60; + if (hours_to_add > 0) { + mtstime->minute -= hours_to_add * 60; + mtstime->hour += hours_to_add; + } + + days_to_add = mtstime->hour / 24; + if (days_to_add > 0) { + mtstime->hour -= days_to_add * 24; + g_date_add_days (&mtstime->date, days_to_add); + } +} + +/* + * CONVERSION ROUTINES - functions to convert between different coordinate + * spaces and dates. + */ + +/* This takes an x pixel coordinate within the entire canvas scroll region and + returns the date in which it falls. If day_position is not NULL it also + returns the x coordinate within the date, relative to the visible part of + the canvas. It is used when painting the days in the item_draw function. + Note that it must handle negative x coordinates in case we are dragging off + the edge of the canvas. */ +void +e_meeting_time_selector_calculate_day_and_position (EMeetingTimeSelector *mts, + gint x, + GDate *date, + gint *day_position) +{ + gint days_from_first_shown; + + *date = mts->first_date_shown; + + if (x >= 0) { + days_from_first_shown = x / mts->day_width; + g_date_add_days (date, days_from_first_shown); + if (day_position) + *day_position = - x % mts->day_width; + } else { + days_from_first_shown = -x / mts->day_width + 1; + g_date_subtract_days (date, days_from_first_shown); + if (day_position) + *day_position = -mts->day_width - x % mts->day_width; + } +} + + +/* This takes an x pixel coordinate within a day, and converts it to hours + and minutes, depending on working_hours_only and zoomed_out. */ +void +e_meeting_time_selector_convert_day_position_to_hours_and_mins (EMeetingTimeSelector *mts, gint day_position, guint8 *hours, guint8 *minutes) +{ + if (mts->zoomed_out) + day_position *= 3; + + /* Calculate the hours & minutes from the first displayed. */ + *hours = day_position / mts->col_width; + *minutes = (day_position % mts->col_width) * 60 / mts->col_width; + + /* Now add on the first hour shown. */ + *hours += mts->first_hour_shown; +} + + +/* This takes an x pixel coordinate within the entire canvas scroll region and + returns the time in which it falls. Note that it won't be extremely + accurate since hours may only be a few pixels wide in the display. + With zoomed_out set each pixel may represent 5 minutes or more, depending + on how small the font is. */ +void +e_meeting_time_selector_calculate_time (EMeetingTimeSelector *mts, + gint x, + EMeetingTime*time) +{ + gint day_position; + + /* First get the day and the x position within the day. */ + e_meeting_time_selector_calculate_day_and_position (mts, x, &time->date, + NULL); + + /* Now convert the day_position into an hour and minute. */ + if (x >= 0) + day_position = x % mts->day_width; + else + day_position = mts->day_width + x % mts->day_width; + + e_meeting_time_selector_convert_day_position_to_hours_and_mins (mts, day_position, &time->hour, &time->minute); +} + + +/* This takes a EMeetingTime and calculates the x pixel coordinate + within the entire canvas scroll region. It is used to draw the selected + meeting time and all the busy periods. */ +gint +e_meeting_time_selector_calculate_time_position (EMeetingTimeSelector *mts, + EMeetingTime *mtstime) +{ + gint x, date_offset, day_offset; + + /* Calculate the number of days since the first date shown in the + entire canvas scroll region. */ + date_offset = g_date_julian (&mtstime->date) - g_date_julian (&mts->first_date_shown); + + /* Calculate the x pixel coordinate of the start of the day. */ + x = date_offset * mts->day_width; + + /* Add on the hours and minutes, depending on whether zoomed_out and + working_hours_only are set. */ + day_offset = (mtstime->hour - mts->first_hour_shown) * 60 + + mtstime->minute; + /* The day width includes an extra vertical grid line so subtract 1. */ + day_offset *= (mts->day_width - 1); + day_offset /= (mts->last_hour_shown - mts->first_hour_shown) * 60; + + /* Clamp the day_offset in case the time isn't actually visible. */ + x += CLAMP (day_offset, 0, mts->day_width); + + return x; +} + +static void +row_count_changed_cb (ETableModel *etm, int row, int count, gpointer data) +{ + EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data); + + /* Update the scroll region. */ + e_meeting_time_selector_update_main_canvas_scroll_region (mts); + + /* Redraw */ + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); +} + +static void +sort_info_changed_cb (ETableSortInfo *info, gpointer data) +{ + EMeetingTimeSelector *mts = E_MEETING_TIME_SELECTOR (data); + + /* Redraw */ + gtk_widget_queue_draw (mts->display_top); + gtk_widget_queue_draw (mts->display_main); +} diff --git a/calendar/gui/e-meeting-time-sel.etspec b/calendar/gui/e-meeting-time-sel.etspec new file mode 100644 index 0000000000..c0b5c721dd --- /dev/null +++ b/calendar/gui/e-meeting-time-sel.etspec @@ -0,0 +1,19 @@ +<ETableSpecification click-to-add="true" click-to-add-end="true" _click-to-add-message="Click here to add an attendee" draw-grid="true"> + <ETableColumn model_col= "0" _title="Attendee" expansion="2.0" minimum_width="10" resizable="true" cell="string" compare="string"/> + <ETableColumn model_col= "1" _title="Member" expansion="2.0" minimum_width="10" resizable="true" cell="string" compare="string"/> + <ETableColumn model_col= "2" _title="Type" expansion="1.0" minimum_width="10" resizable="true" cell="typeedit" compare="string"/> + <ETableColumn model_col= "3" _title="Role" expansion="1.0" minimum_width="10" resizable="true" cell="roleedit" compare="string"/> + <ETableColumn model_col= "4" _title="RSVP" expansion="1.0" minimum_width="10" resizable="true" cell="rsvpedit" compare="string"/> + <ETableColumn model_col= "5" _title="Delegated To" expansion="2.0" minimum_width="10" resizable="true" cell="string" compare="string"/> + <ETableColumn model_col= "6" _title="Delegated From" expansion="2.0" minimum_width="10" resizable="true" cell="string" compare="string"/> + <ETableColumn model_col= "7" _title="Status" expansion="1.0" minimum_width="10" resizable="true" cell="statusedit" compare="string"/> + <ETableColumn model_col= "8" _title="Common Name" expansion="2.0" minimum_width="10" resizable="true" cell="string" compare="string"/> + <ETableColumn model_col= "9" _title="Language" expansion="2.0" minimum_width="10" resizable="true" cell="string" compare="string"/> + + <ETableState> + <column source="0"/> + <column source="2"/> + <column source="7"/> + <grouping></grouping> + </ETableState> +</ETableSpecification> diff --git a/calendar/gui/e-meeting-time-sel.h b/calendar/gui/e-meeting-time-sel.h new file mode 100644 index 0000000000..a0a3cd7434 --- /dev/null +++ b/calendar/gui/e-meeting-time-sel.h @@ -0,0 +1,375 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ + +/* + * Author : + * Damon Chaplin <damon@gtk.org> + * + * Copyright 1999, 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 + */ +#ifndef _E_MEETING_TIME_SELECTOR_H_ +#define _E_MEETING_TIME_SELECTOR_H_ + +#include <glib.h> +#include <gtk/gtkaccelgroup.h> +#include <gtk/gtktable.h> +#include <gtk/gtkwidget.h> +#include <libgnomeui/gnome-canvas.h> +#include <gal/e-text/e-text.h> +#include <gal/e-table/e-table-model.h> +#include <gal/e-table/e-table.h> +#include "e-meeting-model.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* + * EMeetingTimeSelector displays a list of attendees for a meeting and a + * graphical summary of the times which they are free and busy, allowing the + * user to select an appropriate time for a meeting. + */ + +/* Define this to include the debugging functions. */ +#undef E_MEETING_TIME_SELECTOR_DEBUG + +/* This is the width of the icon column in the attendees list. */ +#define E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH 24 + +#define E_MEETING_TIME_SELECTOR_TEXT_Y_PAD 3 +#define E_MEETING_TIME_SELECTOR_TEXT_X_PAD 2 + + +/* This is used to specify the format used when displaying the dates. + The full format is like 'Sunday, September 12, 1999'. The abbreviated format + is like 'Sun 12/9/99'. The short format is like '12/9/99'. The actual + format used is determined in e_meeting_time_selector_style_set(), once we + know the font being used. */ +typedef enum +{ + E_MEETING_TIME_SELECTOR_DATE_FULL, + E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY, + E_MEETING_TIME_SELECTOR_DATE_SHORT +} EMeetingTimeSelectorDateFormat; + + +/* This is used to specify a position regarding the vertical bars around the + current meeting time, so we know which one is being dragged. */ +typedef enum +{ + E_MEETING_TIME_SELECTOR_POS_NONE, + E_MEETING_TIME_SELECTOR_POS_START, + E_MEETING_TIME_SELECTOR_POS_END +} EMeetingTimeSelectorPosition; + + +/* This is used to specify the autopick option, which determines how we choose + the previous/next appropriate meeting time. */ +typedef enum +{ + E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_RESOURCES, + E_MEETING_TIME_SELECTOR_ALL_PEOPLE_AND_ONE_RESOURCE, + E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE, + E_MEETING_TIME_SELECTOR_REQUIRED_PEOPLE_AND_ONE_RESOURCE +} EMeetingTimeSelectorAutopickOption; + +/* An array of hour strings, "0:00" .. "23:00". */ +extern const gchar *EMeetingTimeSelectorHours[24]; + + +#define E_MEETING_TIME_SELECTOR(obj) GTK_CHECK_CAST (obj, e_meeting_time_selector_get_type (), EMeetingTimeSelector) +#define E_MEETING_TIME_SELECTOR_CLASS(klass) GTK_CHECK_CLASS_CAST (klass, e_meeting_time_selector_get_type (), EMeetingTimeSelectorClass) +#define IS_E_MEETING_TIME_SELECTOR(obj) GTK_CHECK_TYPE (obj, e_meeting_time_selector_get_type ()) + + +typedef struct _EMeetingTimeSelector EMeetingTimeSelector; +typedef struct _EMeetingTimeSelectorClass EMeetingTimeSelectorClass; + +struct _EMeetingTimeSelector +{ + /* We subclass a GtkTable which makes it easy to add extra widgets + if neccesary. */ + GtkTable table; + + /* + * User Interface stuff - widgets, colors etc. + */ + + /* This contains our keyboard accelerators, which need to be added to + the toplevel window. */ + GtkAccelGroup *accel_group; + + /* The vbox in the top-left corner, containing the 'All Attendees' + title bar packed at the end. Extra widgets can be added here + with PACK_START if necessary. */ + GtkWidget *attendees_vbox; + + /* The etable and model */ + EMeetingModel *model; + GtkWidget *etable; + + /* The canvas displaying the dates, times, and the summary + 'All Attendees' free/busy display. */ + GtkWidget *display_top; + + /* The canvas containing the free/busy displays of individual + attendees. This is separate from display_top since it also scrolls + vertically. */ + GtkWidget *display_main; + + /* This is the 'Options' button & menu. */ + GtkWidget *options_button; + GtkWidget *options_menu; + + /* This is the 'Autopick' button, menu & radio menu items. */ + GtkWidget *autopick_button; + GtkWidget *autopick_menu; + GtkWidget *autopick_all_item; + GtkWidget *autopick_all_people_one_resource_item; + GtkWidget *autopick_required_people_item; + GtkWidget *autopick_required_people_one_resource_item; + + /* The horizontal scrollbar which scrolls display_top & display_main.*/ + GtkWidget *hscrollbar; + + /* The vertical scrollbar which scrolls attendees & display_main. */ + GtkWidget *vscrollbar; + + /* The 2 GnomeDateEdit widgets for the meeting start & end times. */ + GtkWidget *start_date_edit; + GtkWidget *end_date_edit; + + /* Colors. */ + GdkColorContext *color_context; + GdkColor bg_color; + GdkColor all_attendees_bg_color; + GdkColor meeting_time_bg_color; + GdkColor stipple_bg_color; + GdkColor attendee_list_bg_color; + GdkColor grid_color; + GdkColor grid_shadow_color; + GdkColor grid_unused_color; + GdkColor busy_colors[E_MEETING_FREE_BUSY_LAST]; + + /* The stipple used for attendees with no data. */ + GdkPixmap *stipple; + + /* GC for drawing the color key. */ + GdkGC *color_key_gc; + + /* Width of the hours strings (e.g. "1:00") in the current font. */ + gint hour_widths[24]; + + /* Whether we are using the full, abbreviated or short date format. */ + EMeetingTimeSelectorDateFormat date_format; + + /* + * Option Settings. + */ + + /* If this is TRUE we only show hours between day_start_hour and + day_end_hour, defaults to TRUE (9am-6pm). */ + gboolean working_hours_only; + gint day_start_hour; + gint day_start_minute; + gint day_end_hour; + gint day_end_minute; + + /* If TRUE, view is compressed, with one cell for every 3 hours rather + than every hour. Defaults to FALSE. */ + gboolean zoomed_out; + + + /* + * Internal Data. + */ + + /* These are the first & last dates shown in the current scroll area. + We show E_MEETING_TIME_SELECTOR_DAYS_SHOWN days at a time. */ + GDate first_date_shown; + GDate last_date_shown; + + /* This is the current selection of the meeting time. */ + EMeetingTime meeting_start_time; + EMeetingTime meeting_end_time; + + /* These are the x pixel coordinates in the entire scroll region of + the start and end times. Set to meeting_positions_valid to FALSE to + invalidate. They will then be recomputed when needed. Always access + with e_meeting_time_selector_get_meeting_time_positions(). */ + gint meeting_positions_valid; + gint meeting_positions_in_scroll_area; + gint meeting_start_x; + gint meeting_end_x; + + /* These are the width and height of the cells, including the grid + lines which are displayed on the right and top or bottom of cells.*/ + gint row_height; + gint col_width; + + /* This is the width of a day in the display, which depends on + col_width, working_hours_only and zoomed_out. */ + gint day_width; + + /* These are the first and last hour of each day we display, depending + on working_hours_only and zoomed_out. */ + gint first_hour_shown; + gint last_hour_shown; + + /* The id of the source function for auto-scroll timeouts. */ + guint auto_scroll_timeout_id; + + /* This specifies if we are dragging one of the vertical bars around + the meeting time. */ + EMeetingTimeSelectorPosition dragging_position; + + /* The last x coordinate of the mouse, relative to either the left or + right edge of the canvas. Used in the auto_scroll_timeout function + to determine which way to scroll and how fast. */ + gint last_drag_x; + + /* This is used to determine the delay between scrolls. */ + gint scroll_count; +}; + + +struct _EMeetingTimeSelectorClass +{ + GtkTableClass parent_class; +}; + + +/* + * PUBLIC INTERFACE - note that this interface will probably change, when I + * know where the data is coming from. This is mainly just for testing for now. + */ + +GtkType e_meeting_time_selector_get_type (void); +GtkWidget* e_meeting_time_selector_new (EMeetingModel *emm); +void e_meeting_time_selector_construct (EMeetingTimeSelector * mts, EMeetingModel *emm); + +/* This returns the currently selected meeting time. + Note that months are 1-12 and days are 1-31. The start time is guaranteed to + be before or equal to the end time. You may want to check if they are equal + if that if it is a problem. */ +void e_meeting_time_selector_get_meeting_time (EMeetingTimeSelector *mts, + gint *start_year, + gint *start_month, + gint *start_day, + gint *start_hour, + gint *start_minute, + gint *end_year, + gint *end_month, + gint *end_day, + gint *end_hour, + gint *end_minute); + +/* This sets the meeting time, returning TRUE if it is valid. */ +gboolean e_meeting_time_selector_set_meeting_time (EMeetingTimeSelector *mts, + gint start_year, + gint start_month, + gint start_day, + gint start_hour, + gint start_minute, + gint end_year, + gint end_month, + gint end_day, + gint end_hour, + gint end_minute); + +void e_meeting_time_selector_set_working_hours_only (EMeetingTimeSelector *mts, + gboolean working_hours_only); +void e_meeting_time_selector_set_working_hours (EMeetingTimeSelector *mts, + gint day_start_hour, + gint day_start_minute, + gint day_end_hour, + gint day_end_minute); + +void e_meeting_time_selector_set_zoomed_out (EMeetingTimeSelector *mts, + gboolean zoomed_out); + +EMeetingTimeSelectorAutopickOption e_meeting_time_selector_get_autopick_option (EMeetingTimeSelector *mts); +void e_meeting_time_selector_set_autopick_option (EMeetingTimeSelector *mts, + EMeetingTimeSelectorAutopickOption autopick_option); + +void e_meeting_time_selector_attendee_set_send_meeting_to (EMeetingTimeSelector *mts, + gint row, + gboolean send_meeting_to); + +/* Clears all busy times for the given attendee. */ +void e_meeting_time_selector_attendee_clear_busy_periods (EMeetingTimeSelector *mts, + gint row); +/* Adds one busy time for the given attendee. */ +gboolean e_meeting_time_selector_attendee_add_busy_period (EMeetingTimeSelector *mts, + gint row, + gint start_year, + gint start_month, + gint start_day, + gint start_hour, + gint start_minute, + gint end_year, + gint end_month, + gint end_day, + gint end_hour, + gint end_minute, + EMeetingFreeBusyType busy_type); + + + +/* + * INTERNAL ROUTINES - functions to communicate with the canvas items within + * the EMeetingTimeSelector. + */ + +/* This returns the x pixel coordinates of the meeting start and end times, + in the entire canvas scroll area. If it returns FALSE, then the meeting + time isn't in the current scroll area (which shouldn't really happen). */ +gboolean e_meeting_time_selector_get_meeting_time_positions (EMeetingTimeSelector *mts, + gint *start_x, + gint *end_x); + +void e_meeting_time_selector_drag_meeting_time (EMeetingTimeSelector *mts, + gint x); + +void e_meeting_time_selector_remove_timeout (EMeetingTimeSelector *mts); + +void e_meeting_time_selector_fix_time_overflows (EMeetingTime*mtstime); + +void e_meeting_time_selector_calculate_day_and_position (EMeetingTimeSelector *mts, + gint x, + GDate *date, + gint *day_position); +void e_meeting_time_selector_convert_day_position_to_hours_and_mins (EMeetingTimeSelector *mts, gint day_position, guint8 *hours, guint8 *minutes); +void e_meeting_time_selector_calculate_time (EMeetingTimeSelector *mts, + gint x, + EMeetingTime*time); +gint e_meeting_time_selector_calculate_time_position (EMeetingTimeSelector *mts, + EMeetingTime *mtstime); + +/* Debugging function to dump information on all attendees. */ +#ifdef E_MEETING_TIME_SELECTOR_DEBUG +void e_meeting_time_selector_dump (EMeetingTimeSelector *mts); +gchar* e_meeting_time_selector_dump_time (EMeetingTime*mtstime); +gchar* e_meeting_time_selector_dump_date (GDate *date); +#endif /* E_MEETING_TIME_SELECTOR_DEBUG */ + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _E_MEETING_TIME_SELECTOR_H_ */ diff --git a/calendar/gui/e-meeting-types.h b/calendar/gui/e-meeting-types.h new file mode 100644 index 0000000000..667252cc95 --- /dev/null +++ b/calendar/gui/e-meeting-types.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* itip-attendee.h + * + * Copyright (C) 2001 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. + * + * Author: JP Rosevear + */ + +#ifndef _E_MEETING_TYPES_H_ +#define _E_MEETING_TYPES_H_ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <glib.h> +#include "e-meeting-types.h" + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus */ + + + +typedef struct _EMeetingTime EMeetingTime; +typedef struct _EMeetingFreeBusyPeriod EMeetingFreeBusyPeriod; + +/* These are used to specify whether an attendee is free or busy at a + particular time. We'll probably replace this with a global calendar type. + These should be ordered in increasing order of preference. Higher precedence + busy periods will be painted over lower precedence ones. These are also + used as for loop counters, so they should start at 0 and be ordered. */ +typedef enum +{ + E_MEETING_FREE_BUSY_TENTATIVE = 0, + E_MEETING_FREE_BUSY_OUT_OF_OFFICE = 1, + E_MEETING_FREE_BUSY_BUSY = 2, + + E_MEETING_FREE_BUSY_LAST = 3 +} EMeetingFreeBusyType; + +/* This is our representation of a time. We use a GDate to store the day, + and guint8s for the hours and minutes. */ +struct _EMeetingTime +{ + GDate date; + guint8 hour; + guint8 minute; +}; + +/* This represents a busy period. */ +struct _EMeetingFreeBusyPeriod +{ + EMeetingTime start; + EMeetingTime end; + EMeetingFreeBusyType busy_type; +}; + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* _E_MEETING_TYPES_H_ */ |