aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJP Rosevear <jpr@ximian.com>2001-09-19 12:33:33 +0800
committerJP Rosevear <jpr@src.gnome.org>2001-09-19 12:33:33 +0800
commitd639a620a6438ea93787cedfe9f3eb87014275a3 (patch)
tree602d9156e8e33197fc27752d8fb976cce99d896c
parentd284de85386149adba1e94f1b05b6b2d631e8585 (diff)
downloadgsoc2013-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
-rw-r--r--calendar/ChangeLog57
-rw-r--r--calendar/gui/Makefile.am14
-rw-r--r--calendar/gui/dialogs/Makefile.am3
-rw-r--r--calendar/gui/dialogs/comp-editor.c89
-rw-r--r--calendar/gui/dialogs/comp-editor.h1
-rw-r--r--calendar/gui/dialogs/event-editor.c51
-rw-r--r--calendar/gui/dialogs/meeting-page.c843
-rw-r--r--calendar/gui/dialogs/meeting-page.etspec2
-rw-r--r--calendar/gui/dialogs/meeting-page.h5
-rw-r--r--calendar/gui/dialogs/schedule-page.c402
-rw-r--r--calendar/gui/dialogs/schedule-page.glade36
-rw-r--r--calendar/gui/dialogs/schedule-page.h60
-rw-r--r--calendar/gui/dialogs/task-editor.c8
-rw-r--r--calendar/gui/e-itip-control.c4
-rw-r--r--calendar/gui/e-meeting-attendee.c998
-rw-r--r--calendar/gui/e-meeting-attendee.h164
-rw-r--r--calendar/gui/e-meeting-model.c1321
-rw-r--r--calendar/gui/e-meeting-model.h91
-rw-r--r--calendar/gui/e-meeting-time-sel-item.c1005
-rw-r--r--calendar/gui/e-meeting-time-sel-item.h79
-rw-r--r--calendar/gui/e-meeting-time-sel.c2622
-rw-r--r--calendar/gui/e-meeting-time-sel.etspec19
-rw-r--r--calendar/gui/e-meeting-time-sel.h375
-rw-r--r--calendar/gui/e-meeting-types.h79
24 files changed, 7539 insertions, 789 deletions
diff --git a/calendar/ChangeLog b/calendar/ChangeLog
index e3712ea18f..acb7df00c0 100644
--- a/calendar/ChangeLog
+++ b/calendar/ChangeLog
@@ -1,3 +1,60 @@
+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
+
2001-09-18 Federico Mena Quintero <federico@ximian.com>
Fixes bug #6350.
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, &current_date, day_x, y, width, height);
+ else
+ e_meeting_time_selector_item_paint_day (mts_item, drawable, &current_date, day_x, y, width, height);
+
+ day_x += mts_item->mts->day_width;
+ if (g_date_compare (&current_date, &last_date) == 0)
+ break;
+ g_date_add_days (&current_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_ */