aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--ChangeLog4
-rw-r--r--configure.in1
-rw-r--r--widgets/ChangeLog17
-rw-r--r--widgets/Makefile.am4
-rw-r--r--widgets/e-table/ChangeLog17
-rw-r--r--widgets/e-table/Makefile.am4
-rw-r--r--widgets/meeting-time-sel/.cvsignore7
-rw-r--r--widgets/meeting-time-sel/Makefile.am30
-rw-r--r--widgets/meeting-time-sel/e-meeting-time-sel-item.c924
-rw-r--r--widgets/meeting-time-sel/e-meeting-time-sel-item.h78
-rw-r--r--widgets/meeting-time-sel/e-meeting-time-sel-list-item.c303
-rw-r--r--widgets/meeting-time-sel/e-meeting-time-sel-list-item.h74
-rw-r--r--widgets/meeting-time-sel/e-meeting-time-sel-mail.xpm135
-rw-r--r--widgets/meeting-time-sel/e-meeting-time-sel-no-mail.xpm123
-rw-r--r--widgets/meeting-time-sel/e-meeting-time-sel.c3065
-rw-r--r--widgets/meeting-time-sel/e-meeting-time-sel.h496
-rw-r--r--widgets/meeting-time-sel/test-meeting-time-sel.c236
17 files changed, 5518 insertions, 0 deletions
diff --git a/ChangeLog b/ChangeLog
index 1d5aeda4db..78c5a8f025 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+1999-12-19 Damon Chaplin <damon@karuna.freeserve.co.uk>
+
+ * configure.in (AC_OUTPUT): added widgets/meeting-time-sel/Makefile
+
1999-12-19 bertrand <Bertrand.Guiheneuf@aful.org>
* camel/camel-folder-utils.c: include camel-log.h
diff --git a/configure.in b/configure.in
index d81d21c411..2d7c33111a 100644
--- a/configure.in
+++ b/configure.in
@@ -108,4 +108,5 @@ devel-docs/camel/Makefile
tests/Makefile
tests/ui-tests/Makefile
widgets/Makefile
+widgets/meeting-time-sel/Makefile
])
diff --git a/widgets/ChangeLog b/widgets/ChangeLog
index 305d0e9d48..7e75163aae 100644
--- a/widgets/ChangeLog
+++ b/widgets/ChangeLog
@@ -1,3 +1,20 @@
+1999-12-19 Damon Chaplin <damon@karuna.freeserve.co.uk>
+
+ * Makefile.am (SUBDIRS): created SUBDIRS with meeting-time-sel.
+
+ * meeting-time-sel/Makefile.am:
+ * meeting-time-sel/.cvsignore:
+ * meeting-time-sel/e-meeting-time-sel.h:
+ * meeting-time-sel/e-meeting-time-sel.c:
+ * meeting-time-sel/e-meeting-time-sel-item.h:
+ * meeting-time-sel/e-meeting-time-sel-item.c:
+ * meeting-time-sel/e-meeting-time-sel-list-item.h:
+ * meeting-time-sel/e-meeting-time-sel-list-item.c:
+ * meeting-time-sel/e-meeting-time-sel-mail.xpm:
+ * meeting-time-sel/e-meeting-time-sel-no-mail.xpm:
+ * meeting-time-sel/test-meeting-time-sel.c: new files implementing
+ the meeting time selector.
+
1999-12-12 Miguel de Icaza <miguel@helixcode.com>
* e-table-item.c (eti_class_init): X and Y arguments are now
diff --git a/widgets/Makefile.am b/widgets/Makefile.am
index e59c03a18d..73bd54ee2c 100644
--- a/widgets/Makefile.am
+++ b/widgets/Makefile.am
@@ -1,3 +1,7 @@
+
+SUBDIRS =
+ meeting-time-sel
+
INCLUDES = \
-I$(top_srcdir)/camel \
-I$(top_builddir)/camel \
diff --git a/widgets/e-table/ChangeLog b/widgets/e-table/ChangeLog
index 305d0e9d48..7e75163aae 100644
--- a/widgets/e-table/ChangeLog
+++ b/widgets/e-table/ChangeLog
@@ -1,3 +1,20 @@
+1999-12-19 Damon Chaplin <damon@karuna.freeserve.co.uk>
+
+ * Makefile.am (SUBDIRS): created SUBDIRS with meeting-time-sel.
+
+ * meeting-time-sel/Makefile.am:
+ * meeting-time-sel/.cvsignore:
+ * meeting-time-sel/e-meeting-time-sel.h:
+ * meeting-time-sel/e-meeting-time-sel.c:
+ * meeting-time-sel/e-meeting-time-sel-item.h:
+ * meeting-time-sel/e-meeting-time-sel-item.c:
+ * meeting-time-sel/e-meeting-time-sel-list-item.h:
+ * meeting-time-sel/e-meeting-time-sel-list-item.c:
+ * meeting-time-sel/e-meeting-time-sel-mail.xpm:
+ * meeting-time-sel/e-meeting-time-sel-no-mail.xpm:
+ * meeting-time-sel/test-meeting-time-sel.c: new files implementing
+ the meeting time selector.
+
1999-12-12 Miguel de Icaza <miguel@helixcode.com>
* e-table-item.c (eti_class_init): X and Y arguments are now
diff --git a/widgets/e-table/Makefile.am b/widgets/e-table/Makefile.am
index e59c03a18d..73bd54ee2c 100644
--- a/widgets/e-table/Makefile.am
+++ b/widgets/e-table/Makefile.am
@@ -1,3 +1,7 @@
+
+SUBDIRS =
+ meeting-time-sel
+
INCLUDES = \
-I$(top_srcdir)/camel \
-I$(top_builddir)/camel \
diff --git a/widgets/meeting-time-sel/.cvsignore b/widgets/meeting-time-sel/.cvsignore
new file mode 100644
index 0000000000..21ce3ebb95
--- /dev/null
+++ b/widgets/meeting-time-sel/.cvsignore
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.lo
+*.la
+test-meeting-time-selector
diff --git a/widgets/meeting-time-sel/Makefile.am b/widgets/meeting-time-sel/Makefile.am
new file mode 100644
index 0000000000..fdf6e90b67
--- /dev/null
+++ b/widgets/meeting-time-sel/Makefile.am
@@ -0,0 +1,30 @@
+
+INCLUDES = \
+ $(GNOME_INCLUDEDIR)
+
+noinst_LIBRARIES = \
+ libevolutionmtsel.a
+
+libevolutionmtsel_a_SOURCES = \
+ e-meeting-time-sel.c \
+ e-meeting-time-sel.h \
+ e-meeting-time-sel-item.c \
+ e-meeting-time-sel-item.h \
+ e-meeting-time-sel-list-item.c \
+ e-meeting-time-sel-list-item.h
+
+noinst_PROGRAMS = \
+ test-meeting-time-selector
+
+test_meeting_time_selector_SOURCES = \
+ test-meeting-time-sel.c
+
+test_meeting_time_selector_LDADD = \
+ libevolutionmtsel.a
+
+test_meeting_time_selector_LDFLAGS = `gnome-config --libs gnomeui`
+
+EXTRA_DIST = \
+ e-meeting-time-sel-mail.xpm \
+ e-meeting-time-sel-no-mail.xpm
+
diff --git a/widgets/meeting-time-sel/e-meeting-time-sel-item.c b/widgets/meeting-time-sel/e-meeting-time-sel-item.c
new file mode 100644
index 0000000000..cb716d6f08
--- /dev/null
+++ b/widgets/meeting-time-sel/e-meeting-time-sel-item.c
@@ -0,0 +1,924 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ * Damon Chaplin <damon@gtk.org>
+ *
+ * Copyright 1999, Helix Code, 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.
+ */
+
+#include <config.h>
+#include <time.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, EMeetingTimeSelectorBusyType busy_type);
+
+static EMeetingTimeSelectorPosition e_meeting_time_selector_item_get_drag_position (EMeetingTimeSelectorItem *mts_item, gint x, gint y);
+
+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;
+
+ /* Create the cursors. */
+ mts_item->normal_cursor = gdk_cursor_new (GDK_TOP_LEFT_ARROW);
+ 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);
+}
+
+
+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;
+
+ 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;
+ EMeetingTimeSelectorAttendee *attendee;
+ gint day_x, meeting_start_x, meeting_end_x, bar_y, bar_height;
+ gint row, row_y;
+ GDate date, last_date, current_date;
+ gboolean is_display_top, show_meeting_time;
+ GdkGC *gc;
+
+ mts_item = E_MEETING_TIME_SELECTOR_ITEM (item);
+ mts = mts_item->mts;
+ g_return_if_fail (mts != NULL);
+ gc = mts_item->main_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, 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_background (gc, &mts->stipple_bg_color);
+ gdk_gc_set_stipple (gc, mts->stipple);
+ gnome_canvas_set_stipple_origin (item->canvas, gc);
+ gdk_gc_set_fill (gc, GDK_OPAQUE_STIPPLED);
+ row = y / mts->row_height;
+ row_y = row * mts->row_height - y;
+ while (row < mts->attendees->len && row_y < height) {
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+ if (!attendee->has_calendar_info) {
+ gdk_draw_rectangle (drawable, 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];
+ 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,
+ 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)
+ g_date_strftime (buffer, 128, "%A, %B %d, %Y", date);
+ else if (mts->date_format == E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY)
+ g_date_strftime (buffer, 128, "%a %x", date);
+ else
+ g_date_strftime (buffer, 128, "%x", 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;
+ EMeetingTimeSelectorBusyType 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, mts->attendees->len);
+ for (row = 0; row < mts->attendees->len; 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_TIME_SELECTOR_BUSY_LAST;
+ busy_type++) {
+ gdk_gc_set_foreground (gc, &mts->busy_colors[busy_type]);
+ for (row = 0; row < mts->attendees->len; 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 < mts->attendees->len) {
+ 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 = (mts->attendees->len * 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;
+ EMeetingTimeSelectorBusyType busy_type;
+ gint row, y, first_period;
+ GdkGC *gc;
+
+ mts = mts_item->mts;
+ gc = mts_item->main_gc;
+
+ /* 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 < mts->attendees->len) {
+
+ /* Find the first visible busy period. */
+ first_period = e_meeting_time_selector_item_find_first_busy_period (mts_item, date, 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_TIME_SELECTOR_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, 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;
+ EMeetingTimeSelectorAttendee *attendee;
+ EMeetingTimeSelectorPeriod *period;
+ gint period_num;
+
+ mts = mts_item->mts;
+
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+
+ period_num = e_meeting_time_selector_find_first_busy_period (mts, attendee, date);
+ if (period_num == -1)
+ return -1;
+
+ /* Check if the period starts after the end of the current canvas
+ scroll area. */
+ period = &g_array_index (attendee->busy_periods,
+ EMeetingTimeSelectorPeriod, 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, EMeetingTimeSelectorBusyType busy_type)
+{
+ EMeetingTimeSelector *mts;
+ EMeetingTimeSelectorAttendee *attendee;
+ EMeetingTimeSelectorPeriod *period;
+ GdkGC *gc;
+ gint period_num, x1, x2, x2_within_day, x2_within_col;
+
+ mts = mts_item->mts;
+ gc = mts_item->main_gc;
+
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+
+ for (period_num = first_period;
+ period_num < attendee->busy_periods->len;
+ period_num++) {
+ period = &g_array_index (attendee->busy_periods,
+ EMeetingTimeSelectorPeriod, 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;
+ EMeetingTimeSelectorTime 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;
+}
+
diff --git a/widgets/meeting-time-sel/e-meeting-time-sel-item.h b/widgets/meeting-time-sel/e-meeting-time-sel-item.h
new file mode 100644
index 0000000000..c2bd2872ce
--- /dev/null
+++ b/widgets/meeting-time-sel/e-meeting-time-sel-item.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ * Damon Chaplin <damon@gtk.org>
+ *
+ * Copyright 1999, Helix Code, 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;
+
+ /* 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/widgets/meeting-time-sel/e-meeting-time-sel-list-item.c b/widgets/meeting-time-sel/e-meeting-time-sel-list-item.c
new file mode 100644
index 0000000000..ac4b7b9009
--- /dev/null
+++ b/widgets/meeting-time-sel/e-meeting-time-sel-list-item.c
@@ -0,0 +1,303 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ * Damon Chaplin <damon@gtk.org>
+ *
+ * Copyright 1999, Helix Code, 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
+ */
+
+/*
+ * EMeetingTimeSelectorListItem - A GnomeCanvasItem covering the entire attendee
+ * list. It just draws the grid lines between the rows and after the icon
+ * column. It probably won't be needed when we switch to Miguel's new editable
+ * GtkList-like widget.
+ */
+
+#include <config.h>
+#include <time.h>
+#include "e-meeting-time-sel-list-item.h"
+#include "e-meeting-time-sel.h"
+
+/* This is the size of our icons. */
+#define E_MEETING_TIME_SELECTOR_ICON_WIDTH 19
+#define E_MEETING_TIME_SELECTOR_ICON_HEIGHT 16
+
+#include "e-meeting-time-sel-mail.xpm"
+#include "e-meeting-time-sel-no-mail.xpm"
+
+static void e_meeting_time_selector_list_item_class_init (EMeetingTimeSelectorListItemClass *mtsl_item_class);
+static void e_meeting_time_selector_list_item_init (EMeetingTimeSelectorListItem *mtsl_item);
+static void e_meeting_time_selector_list_item_destroy (GtkObject *object);
+
+static void e_meeting_time_selector_list_item_set_arg (GtkObject *o, GtkArg *arg,
+ guint arg_id);
+static void e_meeting_time_selector_list_item_realize (GnomeCanvasItem *item);
+static void e_meeting_time_selector_list_item_unrealize (GnomeCanvasItem *item);
+static void e_meeting_time_selector_list_item_update (GnomeCanvasItem *item,
+ double *affine,
+ ArtSVP *clip_path, int flags);
+static void e_meeting_time_selector_list_item_draw (GnomeCanvasItem *item,
+ GdkDrawable *drawable,
+ int x, int y,
+ int width, int height);
+static double e_meeting_time_selector_list_item_point (GnomeCanvasItem *item,
+ double x, double y,
+ int cx, int cy,
+ GnomeCanvasItem **actual_item);
+
+
+static GnomeCanvasItemClass *e_meeting_time_selector_list_item_parent_class;
+
+/* The arguments we take */
+enum {
+ ARG_0,
+ ARG_MEETING_TIME_SELECTOR
+};
+
+
+GtkType
+e_meeting_time_selector_list_item_get_type (void)
+{
+ static GtkType e_meeting_time_selector_list_item_type = 0;
+
+ if (!e_meeting_time_selector_list_item_type) {
+ GtkTypeInfo e_meeting_time_selector_list_item_info = {
+ "EMeetingTimeSelectorListItem",
+ sizeof (EMeetingTimeSelectorListItem),
+ sizeof (EMeetingTimeSelectorListItemClass),
+ (GtkClassInitFunc) e_meeting_time_selector_list_item_class_init,
+ (GtkObjectInitFunc) e_meeting_time_selector_list_item_init,
+ NULL, /* reserved_1 */
+ NULL, /* reserved_2 */
+ (GtkClassInitFunc) NULL
+ };
+
+ e_meeting_time_selector_list_item_type = gtk_type_unique (gnome_canvas_item_get_type (), &e_meeting_time_selector_list_item_info);
+ }
+
+ return e_meeting_time_selector_list_item_type;
+}
+
+
+static void
+e_meeting_time_selector_list_item_class_init (EMeetingTimeSelectorListItemClass *mtsl_item_class)
+{
+ GtkObjectClass *object_class;
+ GnomeCanvasItemClass *item_class;
+
+ e_meeting_time_selector_list_item_parent_class = gtk_type_class (gnome_canvas_item_get_type());
+
+ object_class = (GtkObjectClass *) mtsl_item_class;
+ item_class = (GnomeCanvasItemClass *) mtsl_item_class;
+
+ gtk_object_add_arg_type ("EMeetingTimeSelectorListItem::meeting_time_selector",
+ GTK_TYPE_POINTER, GTK_ARG_WRITABLE,
+ ARG_MEETING_TIME_SELECTOR);
+
+ object_class->destroy = e_meeting_time_selector_list_item_destroy;
+ object_class->set_arg = e_meeting_time_selector_list_item_set_arg;
+
+ /* GnomeCanvasItem method overrides */
+ item_class->realize = e_meeting_time_selector_list_item_realize;
+ item_class->unrealize = e_meeting_time_selector_list_item_unrealize;
+ item_class->update = e_meeting_time_selector_list_item_update;
+ item_class->draw = e_meeting_time_selector_list_item_draw;
+ item_class->point = e_meeting_time_selector_list_item_point;
+#if 0
+ item_class->event = e_meeting_time_selector_list_item_event;
+#endif
+}
+
+
+static void
+e_meeting_time_selector_list_item_init (EMeetingTimeSelectorListItem *mtsl_item)
+{
+ GnomeCanvasItem *item = GNOME_CANVAS_ITEM (mtsl_item);
+ GdkColormap *colormap;
+
+ mtsl_item->mts = NULL;
+
+ colormap = gtk_widget_get_default_colormap ();
+ mtsl_item->mail_icon = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mtsl_item->mail_icon_mask, NULL, e_meeting_time_sel_mail_xpm);
+ mtsl_item->no_mail_icon = gdk_pixmap_colormap_create_from_xpm_d (NULL, colormap, &mtsl_item->no_mail_icon_mask, NULL, e_meeting_time_sel_no_mail_xpm);
+
+ item->x1 = 0;
+ item->y1 = 0;
+ item->x2 = 0;
+ item->y2 = 0;
+}
+
+
+static void
+e_meeting_time_selector_list_item_destroy (GtkObject *object)
+{
+ EMeetingTimeSelectorListItem *mtsl_item;
+
+ mtsl_item = E_MEETING_TIME_SELECTOR_LIST_ITEM (object);
+
+ gdk_pixmap_unref (mtsl_item->mail_icon);
+ gdk_pixmap_unref (mtsl_item->no_mail_icon);
+ gdk_bitmap_unref (mtsl_item->mail_icon_mask);
+ gdk_bitmap_unref (mtsl_item->mail_icon_mask);
+
+ if (GTK_OBJECT_CLASS (e_meeting_time_selector_list_item_parent_class)->destroy)
+ (*GTK_OBJECT_CLASS (e_meeting_time_selector_list_item_parent_class)->destroy)(object);
+}
+
+
+static void
+e_meeting_time_selector_list_item_set_arg (GtkObject *o, GtkArg *arg, guint arg_id)
+{
+ GnomeCanvasItem *item;
+ EMeetingTimeSelectorListItem *mtsl_item;
+
+ item = GNOME_CANVAS_ITEM (o);
+ mtsl_item = E_MEETING_TIME_SELECTOR_LIST_ITEM (o);
+
+ switch (arg_id){
+ case ARG_MEETING_TIME_SELECTOR:
+ mtsl_item->mts = GTK_VALUE_POINTER (*arg);
+ break;
+ }
+}
+
+
+static void
+e_meeting_time_selector_list_item_realize (GnomeCanvasItem *item)
+{
+ GnomeCanvas *canvas;
+ GdkWindow *window;
+ EMeetingTimeSelectorListItem *mtsl_item;
+
+ if (GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_list_item_parent_class)->realize)
+ (*GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_list_item_parent_class)->realize)(item);
+
+ mtsl_item = E_MEETING_TIME_SELECTOR_LIST_ITEM (item);
+
+ canvas = item->canvas;
+ window = GTK_WIDGET (canvas)->window;
+
+ mtsl_item->main_gc = gdk_gc_new (window);
+}
+
+
+static void
+e_meeting_time_selector_list_item_unrealize (GnomeCanvasItem *item)
+{
+ EMeetingTimeSelectorListItem *mtsl_item;
+
+ mtsl_item = E_MEETING_TIME_SELECTOR_LIST_ITEM (item);
+
+ gdk_gc_unref (mtsl_item->main_gc);
+ mtsl_item->main_gc = NULL;
+
+ if (GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_list_item_parent_class)->unrealize)
+ (*GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_list_item_parent_class)->unrealize)(item);
+}
+
+
+static void
+e_meeting_time_selector_list_item_update (GnomeCanvasItem *item, double *affine, ArtSVP *clip_path, int flags)
+{
+ if (GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_list_item_parent_class)->update)
+ (* GNOME_CANVAS_ITEM_CLASS (e_meeting_time_selector_list_item_parent_class)->update) (item, affine, clip_path, flags);
+
+ /* The item 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_list_item_draw (GnomeCanvasItem *item, GdkDrawable *drawable, int x, int y, int width, int height)
+{
+ EMeetingTimeSelectorListItem *mtsl_item;
+ EMeetingTimeSelector *mts;
+ EMeetingTimeSelectorAttendee *attendee;
+ GdkGC *gc;
+ gint row, row_y, icon_x, icon_y;
+ GdkPixmap *pixmap;
+ GdkBitmap *mask;
+
+ mtsl_item = E_MEETING_TIME_SELECTOR_LIST_ITEM (item);
+ mts = mtsl_item->mts;
+ gc = mtsl_item->main_gc;
+
+ gdk_gc_set_foreground (gc, &mts->attendee_list_bg_color);
+ gdk_draw_rectangle (drawable, gc, TRUE, 0, 0, width, height);
+
+ gdk_gc_set_foreground (gc, &mts->grid_unused_color);
+ gdk_draw_line (drawable, gc, 24 - x, 0, 24 - x, height);
+
+ /* Draw the grid line across the top of the row. */
+ row = y / mts->row_height;
+ row_y = row * mts->row_height - y;
+ while (row_y < height) {
+ gdk_draw_line (drawable, gc, 0, row_y, width, row_y);
+ row_y += mts->row_height;
+ }
+
+ row = y / mts->row_height;
+ row_y = row * mts->row_height - y;
+ icon_x = (E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH - E_MEETING_TIME_SELECTOR_ICON_WIDTH + 1) / 2 - x;
+ icon_y = row_y + (mts->row_height - E_MEETING_TIME_SELECTOR_ICON_HEIGHT + 1) / 2;
+ while (row < mts->attendees->len && row_y < height) {
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+
+ gdk_gc_set_clip_origin (gc, icon_x, icon_y);
+
+ if (attendee->send_meeting_to) {
+ pixmap = mtsl_item->mail_icon;
+ mask = mtsl_item->mail_icon_mask;
+ } else {
+ pixmap = mtsl_item->no_mail_icon;
+ mask = mtsl_item->no_mail_icon_mask;
+ }
+
+ gdk_gc_set_clip_mask (gc, mask);
+ gdk_draw_pixmap (drawable, gc, pixmap, 0, 0,
+ icon_x, icon_y, 24, 24);
+
+ row++;
+ row_y += mts->row_height;
+ icon_y += mts->row_height;
+ }
+ gdk_gc_set_clip_mask (gc, NULL);
+}
+
+
+/* This is supposed to return the nearest item the the point and the distance.
+ Since we cover the entire canvas 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_list_item_point (GnomeCanvasItem *item,
+ double x, double y,
+ int cx, int cy,
+ GnomeCanvasItem **actual_item)
+{
+ *actual_item = item;
+ return 0.0;
+}
diff --git a/widgets/meeting-time-sel/e-meeting-time-sel-list-item.h b/widgets/meeting-time-sel/e-meeting-time-sel-list-item.h
new file mode 100644
index 0000000000..9ef295ebb2
--- /dev/null
+++ b/widgets/meeting-time-sel/e-meeting-time-sel-list-item.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ * Damon Chaplin <damon@gtk.org>
+ *
+ * Copyright 1999, Helix Code, 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
+ */
+
+/*
+ * MeetingTimeSelectorListItem - A GnomeCanvasItem covering the entire attendee
+ * list. It just draws the grid lines between the rows and after the icon
+ * column. It probably won't be needed when we switch to Miguel's new editable
+ * GtkList-like widget.
+ */
+
+#ifndef _E_MEETING_TIME_SELECTOR_LIST_ITEM_H_
+#define _E_MEETING_TIME_SELECTOR_LIST_ITEM_H_
+
+#include "e-meeting-time-sel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+#define E_MEETING_TIME_SELECTOR_LIST_ITEM(obj) (GTK_CHECK_CAST((obj), e_meeting_time_selector_list_item_get_type (), EMeetingTimeSelectorListItem))
+#define E_MEETING_TIME_SELECTOR_LIST_ITEM_CLASS(k) (GTK_CHECK_CLASS_CAST ((k), e_meeting_time_selector_list_item_get_type (), EMeetingTimeSelectorListItemClass))
+#define IS_E_MEETING_TIME_SELECTOR_LIST_ITEM(o) (GTK_CHECK_TYPE((o), e_meeting_time_selector_list_item_get_type ()))
+
+
+typedef struct _EMeetingTimeSelectorListItem EMeetingTimeSelectorListItem;
+typedef struct _EMeetingTimeSelectorListItemClass EMeetingTimeSelectorListItemClass;
+
+struct _EMeetingTimeSelectorListItem
+{
+ 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;
+
+ GdkPixmap *mail_icon, *no_mail_icon;
+ GdkBitmap *mail_icon_mask, *no_mail_icon_mask;
+};
+
+
+struct _EMeetingTimeSelectorListItemClass
+{
+ GnomeCanvasItemClass parent_class;
+};
+
+GtkType e_meeting_time_selector_list_item_get_type (void);
+
+
+#endif /* _E_MEETING_TIME_SELECTOR_LIST_ITEM_H_ */
diff --git a/widgets/meeting-time-sel/e-meeting-time-sel-mail.xpm b/widgets/meeting-time-sel/e-meeting-time-sel-mail.xpm
new file mode 100644
index 0000000000..5a03c7ea8a
--- /dev/null
+++ b/widgets/meeting-time-sel/e-meeting-time-sel-mail.xpm
@@ -0,0 +1,135 @@
+/* XPM */
+static char * e_meeting_time_sel_mail_xpm[] = {
+"19 16 116 2",
+" c None",
+". c #000000",
+"+ c #7B7B78",
+"@ c #C7C7C3",
+"# c #F1F0EB",
+"$ c #BFBFBC",
+"% c #5F5E5E",
+"& c #E3E3DF",
+"* c #F3F3EF",
+"= c #F2F2EE",
+"- c #F1F1EC",
+"; c #F0EFEB",
+"> c #D2D2CD",
+", c #969592",
+"' c #616060",
+") c #B5B5B4",
+"! c #F8F8F6",
+"~ c #F7F7F5",
+"{ c #F6F6F3",
+"] c #F5F5F2",
+"^ c #F4F4F0",
+"/ c #F2F1ED",
+"( c #F1F0EC",
+"_ c #F0EFEA",
+": c #EFEEE9",
+"< c #4F4F4D",
+"[ c #BDBCB8",
+"} c #A7A6A3",
+"| c #C6C6C4",
+"1 c #7C7C7B",
+"2 c #525251",
+"3 c #DBDBD9",
+"4 c #F5F4F1",
+"5 c #F4F3F0",
+"6 c #F3F2EE",
+"7 c #999894",
+"8 c #62625F",
+"9 c #BCBCB6",
+"0 c #EBE9E3",
+"a c #838381",
+"b c #D7D6D4",
+"c c #A8A8A6",
+"d c #515150",
+"e c #7E7D7C",
+"f c #DAD9D5",
+"g c #EFEEE8",
+"h c #EEEDE7",
+"i c #4F4E4C",
+"j c #BCBBB6",
+"k c #EBE9E2",
+"l c #EAE8E1",
+"m c #F6F5F2",
+"n c #F4F3EF",
+"o c #B5B4B1",
+"p c #9B9A97",
+"q c #646361",
+"r c #92918E",
+"s c #EEEDE8",
+"t c #EDECE6",
+"u c #4E4E4C",
+"v c #797976",
+"w c #797874",
+"x c #E9E8E1",
+"y c #E8E7DF",
+"z c #B4B3AF",
+"A c #D0D0CD",
+"B c #F2F2ED",
+"C c #BFBEBA",
+"D c #BEBDB9",
+"E c #7A7A77",
+"F c #979691",
+"G c #EAE9E2",
+"H c #959590",
+"I c #787773",
+"J c #B8B7B0",
+"K c #E6E4DC",
+"L c #A9A9A6",
+"M c #626260",
+"N c #ECEBE4",
+"O c #EBEAE3",
+"P c #E9E7E0",
+"Q c #E8E6DF",
+"R c #E7E5DD",
+"S c #777671",
+"T c #93918C",
+"U c #BEBDB8",
+"V c #989793",
+"W c #ECEAE4",
+"X c #E8E6DE",
+"Y c #E6E4DB",
+"Z c #E4E3DA",
+"` c #75746F",
+" . c #91908A",
+".. c #EEECE7",
+"+. c #62615F",
+"@. c #EBEAE4",
+"#. c #E7E6DE",
+"$. c #E6E5DC",
+"%. c #E5E4DB",
+"&. c #E4E2DA",
+"*. c #CCCBC4",
+"=. c #A3A29D",
+"-. c #B6B5B2",
+";. c #BCBCB7",
+">. c #CDCCC6",
+",. c #959490",
+"'. c #ECEBE5",
+"). c #61615E",
+"!. c #E9E8E0",
+"~. c #CECDC7",
+"{. c #797875",
+"]. c #969590",
+"^. c #CFCEC8",
+"/. c #AEADA8",
+"(. c #585754",
+"_. c #7B7A76",
+" . . . . ",
+" . . . . . + @ # $ . ",
+" . . . . . % $ & * = - ; > , . ",
+". ' ) ! ~ { ] ^ * / ( _ : < [ } . ",
+". | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ",
+". a b c d e f / # _ g h i j k l . ",
+" . m 4 n o p q r s t u v w x y z . ",
+" . A * B ( ; C D E u F G H I J K . ",
+" . L / ( _ : M t N O l P Q R S T . ",
+" . # _ g U V W 0 l P X R Y Z ` .. ",
+" . ; g ..+.@.G x y #.$.%.&.*.=.. . ",
+" . -.t ;.F G x y #.K >.,.. . . ",
+" . '.).G !.Q ~.H . . . ",
+" . {.].^./.. . . ",
+" . (._.. . ",
+" . . "};
diff --git a/widgets/meeting-time-sel/e-meeting-time-sel-no-mail.xpm b/widgets/meeting-time-sel/e-meeting-time-sel-no-mail.xpm
new file mode 100644
index 0000000000..747202b3e2
--- /dev/null
+++ b/widgets/meeting-time-sel/e-meeting-time-sel-no-mail.xpm
@@ -0,0 +1,123 @@
+/* XPM */
+static char * e_meeting_time_sel_no_mail_xpm[] = {
+"19 16 104 2",
+" c None",
+". c #000000",
+"+ c #7B7B78",
+"@ c #C7C7C3",
+"# c #F1F0EB",
+"$ c #BFBFBC",
+"% c #5F5E5E",
+"& c #E3E3DF",
+"* c #F3F3EF",
+"= c #F2F2EE",
+"- c #F1F1EC",
+"; c #F0EFEB",
+"> c #D2D2CD",
+", c #969592",
+"' c #616060",
+") c #B5B5B4",
+"! c #F8F8F6",
+"~ c #F7F7F5",
+"{ c #F6F6F3",
+"] c #F5F5F2",
+"^ c #F4F4F0",
+"/ c #F2F1ED",
+"( c #F1F0EC",
+"_ c #F0EFEA",
+": c #EFEEE9",
+"< c #4F4F4D",
+"[ c #BDBCB8",
+"} c #A7A6A3",
+"| c #C6C6C4",
+"1 c #7C7C7B",
+"2 c #525251",
+"3 c #DBDBD9",
+"4 c #F5F4F1",
+"5 c #F4F3F0",
+"6 c #F3F2EE",
+"7 c #999894",
+"8 c #62625F",
+"9 c #BCBCB6",
+"0 c #EBE9E3",
+"a c #838381",
+"b c #D7D6D4",
+"c c #A8A8A6",
+"d c #515150",
+"e c #7E7D7C",
+"f c #DAD9D5",
+"g c #D16069",
+"h c #EEEDE7",
+"i c #4F4E4C",
+"j c #BCBBB6",
+"k c #F6F5F2",
+"l c #F4F3EF",
+"m c #B5B4B1",
+"n c #9B9A97",
+"o c #646361",
+"p c #92918E",
+"q c #9F3C44",
+"r c #797976",
+"s c #B5444E",
+"t c #B4B3AF",
+"u c #D0D0CD",
+"v c #F2F2ED",
+"w c #BFBEBA",
+"x c #BEBDB9",
+"y c #BB565F",
+"z c #812F36",
+"A c #E6E4DC",
+"B c #A9A9A6",
+"C c #626260",
+"D c #EDECE6",
+"E c #ECEBE4",
+"F c #BB555D",
+"G c #90353D",
+"H c #777671",
+"I c #93918C",
+"J c #EFEEE8",
+"K c #BEBDB8",
+"L c #989793",
+"M c #ECEAE4",
+"N c #E6E4DB",
+"O c #E4E3DA",
+"P c #75746F",
+"Q c #91908A",
+"R c #EEECE7",
+"S c #62615F",
+"T c #EBEAE4",
+"U c #EAE9E2",
+"V c #BD5A62",
+"W c #E4E2DA",
+"X c #CCCBC4",
+"Y c #A3A29D",
+"Z c #B6B5B2",
+"` c #BCBCB7",
+" . c #979691",
+".. c #BC5760",
+"+. c #A13C45",
+"@. c #ECEBE5",
+"#. c #61615E",
+"$. c #C0636B",
+"%. c #A83F48",
+"&. c #797875",
+"*. c #969590",
+"=. c #CFCEC8",
+"-. c #585754",
+";. c #7B7A76",
+" . . . . ",
+" . . . . . + @ # $ . ",
+" . . . . . % $ & * = - ; > , . ",
+". ' ) ! ~ { ] ^ * / ( _ : < [ } . ",
+". | 1 2 3 4 5 6 / # _ : 7 8 9 0 . ",
+". a b c d e f / # g g h i j g g . ",
+" . k 4 l m n o p g q g r g s g t . ",
+" . u * v ( ; w x g s g g y z g A . ",
+" . B / ( _ : C D E g s F G g H I . ",
+" . # _ J K L M 0 g s s g N O P Q . ",
+" . ; J R S T U g V s s g W X Y . . ",
+" . Z D ` .U g ..G g g +.g . . ",
+" . @.#.U g $.G g . g %.g ",
+" . &.*.=.g g g . g g ",
+" . -.;.. . ",
+" . . "};
diff --git a/widgets/meeting-time-sel/e-meeting-time-sel.c b/widgets/meeting-time-sel/e-meeting-time-sel.c
new file mode 100644
index 0000000000..86f81cc80e
--- /dev/null
+++ b/widgets/meeting-time-sel/e-meeting-time-sel.c
@@ -0,0 +1,3065 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ * Damon Chaplin <damon@gtk.org>
+ *
+ * Copyright 1999, Helix Code, 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
+ */
+
+#include <config.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+#include <gtk/gtkalignment.h>
+#include <gtk/gtkarrow.h>
+#include <gtk/gtkbutton.h>
+#include <gtk/gtkdrawingarea.h>
+#include <gtk/gtkentry.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 "e-meeting-time-sel.h"
+#include "e-meeting-time-sel-item.h"
+#include "e-meeting-time-sel-list-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
+
+#define E_MEETING_TIME_SELECTOR_ENTRY_INNER_BORDER 2
+
+/* 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 gint e_meeting_time_selector_expose_title_bar (GtkWidget *darea,
+ GdkEventExpose *event,
+ gpointer data);
+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 void e_meeting_time_selector_style_set (GtkWidget *widget,
+ GtkStyle *previous_style);
+static gint e_meeting_time_selector_expose_event (GtkWidget *widget,
+ GdkEventExpose *event);
+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_period_starts (const void *arg1,
+ const void *arg2);
+#if 0
+static gint e_meeting_time_selector_compare_periods (const void *arg1,
+ const void *arg2);
+#endif
+static gint e_meeting_time_selector_compare_times (EMeetingTimeSelectorTime *time1,
+ EMeetingTimeSelectorTime *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 (EMeetingTimeSelectorTime *start,
+ EMeetingTimeSelectorTime *end,
+ gint *days,
+ gint *hours,
+ gint *minutes);
+static void e_meeting_time_selector_find_nearest_interval (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorTime *start_time,
+ EMeetingTimeSelectorTime *end_time,
+ gint days, gint hours, gint mins);
+static void e_meeting_time_selector_find_nearest_interval_backward (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorTime *start_time,
+ EMeetingTimeSelectorTime *end_time,
+ gint days, gint hours, gint mins);
+static void e_meeting_time_selector_adjust_time (EMeetingTimeSelectorTime *mtstime,
+ gint days, gint hours, gint minutes);
+static EMeetingTimeSelectorPeriod* e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorAttendee *attendee,
+ EMeetingTimeSelectorTime *start_time,
+ EMeetingTimeSelectorTime *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,
+ EMeetingTimeSelectorTime *mtstime);
+static void e_meeting_time_selector_restore_position (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorTime *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_attendees_list_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation,
+ 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 void e_meeting_time_selector_update_attendees_list_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 e_meeting_time_selector_update_attendees_list_positions (EMeetingTimeSelector *mts);
+
+static GtkTableClass *e_meeting_time_selector_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;
+
+ e_meeting_time_selector_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->style_set = e_meeting_time_selector_style_set;
+ widget_class->expose_event = e_meeting_time_selector_expose_event;
+}
+
+
+static void
+e_meeting_time_selector_init (EMeetingTimeSelector * mts)
+{
+ GtkWidget *hbox, *separator, *button, *label, *table;
+ GtkWidget *alignment, *child_hbox, *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;
+ guchar stipple_bits[] = {
+ 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80,
+ };
+
+ mts->accel_group = gtk_accel_group_new ();
+
+ mts->attendees = g_array_new (FALSE, FALSE,
+ sizeof (EMeetingTimeSelectorAttendee));
+
+ 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 = FALSE;
+ mts->dragging_position = E_MEETING_TIME_SELECTOR_POS_NONE;
+
+ /* 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 = 30;
+ mts->col_width = 50;
+ mts->day_width = 50 * 24 + 1;
+
+ mts->auto_scroll_timeout_id = 0;
+
+
+ mts->attendees_title_bar_vbox = gtk_vbox_new (FALSE, 2);
+ /* Add some horizontal padding for the shadow around the display. */
+ gtk_table_attach (GTK_TABLE (mts),
+ mts->attendees_title_bar_vbox,
+ 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 2, 0);
+ gtk_widget_show (mts->attendees_title_bar_vbox);
+
+ mts->attendees_title_bar = gtk_drawing_area_new ();
+ gtk_box_pack_end (GTK_BOX (mts->attendees_title_bar_vbox),
+ mts->attendees_title_bar, FALSE, FALSE, 0);
+ gtk_widget_show (mts->attendees_title_bar);
+ gtk_signal_connect (GTK_OBJECT (mts->attendees_title_bar),
+ "expose_event",
+ GTK_SIGNAL_FUNC (e_meeting_time_selector_expose_title_bar), mts);
+
+ mts->attendees_list = gnome_canvas_new ();
+ /* Add some horizontal padding for the shadow around the display. */
+ gtk_table_attach (GTK_TABLE (mts), mts->attendees_list,
+ 0, 1, 1, 2,
+ GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 2, 0);
+ gtk_widget_show (mts->attendees_list);
+ gtk_signal_connect (GTK_OBJECT (mts->attendees_list), "realize",
+ GTK_SIGNAL_FUNC (e_meeting_time_selector_on_canvas_realized), mts);
+ gtk_signal_connect (GTK_OBJECT (mts->attendees_list), "size_allocate",
+ GTK_SIGNAL_FUNC (e_meeting_time_selector_on_attendees_list_size_allocate), mts);
+
+ /* Create the item in the list canvas. */
+ gnome_canvas_item_new (GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->attendees_list)->root),
+ e_meeting_time_selector_list_item_get_type (),
+ "EMeetingTimeSelectorListItem::meeting_time_selector", mts,
+ NULL);
+
+ mts->display_top = gnome_canvas_new ();
+ 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, 2, 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, 2, 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_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_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_TIME_SELECTOR_BUSY_TENTATIVE]);
+ e_meeting_time_selector_add_key_color (mts, hbox, _("Busy"), &mts->busy_colors[E_MEETING_TIME_SELECTOR_BUSY_BUSY]);
+ e_meeting_time_selector_add_key_color (mts, hbox, _("Out of Office"), &mts->busy_colors[E_MEETING_TIME_SELECTOR_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 = gnome_date_edit_new (0, TRUE, TRUE);
+ /* I don't like the 'Calendar' label. */
+ gtk_widget_hide (GNOME_DATE_EDIT (mts->start_date_edit)->cal_label);
+ 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), "date_changed",
+ GTK_SIGNAL_FUNC (e_meeting_time_selector_on_start_time_changed), mts);
+ gtk_signal_connect (GTK_OBJECT (mts->start_date_edit), "time_changed",
+ GTK_SIGNAL_FUNC (e_meeting_time_selector_on_start_time_changed), mts);
+ gtk_signal_connect (GTK_OBJECT (GNOME_DATE_EDIT (mts->start_date_edit)->date_entry), "activate", GTK_SIGNAL_FUNC (e_meeting_time_selector_on_start_time_changed), mts);
+ gtk_signal_connect (GTK_OBJECT (GNOME_DATE_EDIT (mts->start_date_edit)->time_entry), "activate", GTK_SIGNAL_FUNC (e_meeting_time_selector_on_start_time_changed), mts);
+ gtk_widget_add_accelerator (GNOME_DATE_EDIT (mts->start_date_edit)->date_entry,
+ "grab_focus", mts->accel_group,
+ accel_key, GDK_MOD1_MASK, 0);
+
+ 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 = gnome_date_edit_new (0, TRUE, TRUE);
+ gtk_widget_hide (GNOME_DATE_EDIT (mts->end_date_edit)->cal_label);
+ 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), "date_changed",
+ GTK_SIGNAL_FUNC (e_meeting_time_selector_on_end_time_changed), mts);
+ gtk_signal_connect (GTK_OBJECT (mts->end_date_edit), "time_changed",
+ GTK_SIGNAL_FUNC (e_meeting_time_selector_on_end_time_changed), mts);
+ gtk_signal_connect (GTK_OBJECT (GNOME_DATE_EDIT (mts->end_date_edit)->date_entry), "activate", GTK_SIGNAL_FUNC (e_meeting_time_selector_on_end_time_changed), mts);
+ gtk_signal_connect (GTK_OBJECT (GNOME_DATE_EDIT (mts->end_date_edit)->time_entry), "activate", GTK_SIGNAL_FUNC (e_meeting_time_selector_on_end_time_changed), mts);
+ gtk_widget_add_accelerator (GNOME_DATE_EDIT (mts->end_date_edit)->date_entry,
+ "grab_focus", mts->accel_group,
+ accel_key, GDK_MOD1_MASK, 0);
+
+ 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_TIME_SELECTOR_BUSY_TENTATIVE]);
+ e_meeting_time_selector_alloc_named_color (mts, "blue", &mts->busy_colors[E_MEETING_TIME_SELECTOR_BUSY_BUSY]);
+ e_meeting_time_selector_alloc_named_color (mts, "HotPink3", &mts->busy_colors[E_MEETING_TIME_SELECTOR_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);
+
+ 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_title_bar (GtkWidget *widget,
+ GdkEventExpose *event,
+ gpointer data)
+{
+ EMeetingTimeSelector * mts;
+ GdkFont *font;
+
+ mts = E_MEETING_TIME_SELECTOR (data);
+
+ gtk_draw_shadow (widget->style, widget->window, GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT, 0, 0,
+ E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH + 1,
+ widget->allocation.height);
+ gtk_draw_shadow (widget->style, widget->window, GTK_STATE_NORMAL,
+ GTK_SHADOW_OUT,
+ E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH + 1, 0,
+ widget->allocation.width - E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH - 1,
+ widget->allocation.height);
+
+ font = widget->style->font;
+ gdk_draw_string (widget->window, font,
+ widget->style->fg_gc[GTK_STATE_NORMAL],
+ E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH + 4,
+ 4 + font->ascent, _("All Attendees"));
+
+ return FALSE;
+}
+
+
+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 (void)
+{
+ GtkWidget *mts;
+
+ mts = GTK_WIDGET (gtk_type_new (e_meeting_time_selector_get_type ()));
+
+ return mts;
+}
+
+
+static void
+e_meeting_time_selector_destroy (GtkObject *object)
+{
+ EMeetingTimeSelector *mts;
+ EMeetingTimeSelectorAttendee *attendee;
+ gint row;
+
+ 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);
+
+ for (row = 0; row < mts->attendees->len; row++) {
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+ g_free (attendee->name);
+ g_array_free (attendee->busy_periods, TRUE);
+ }
+
+ g_array_free (mts->attendees, TRUE);
+
+ if (GTK_OBJECT_CLASS (e_meeting_time_selector_parent_class)->destroy)
+ (*GTK_OBJECT_CLASS (e_meeting_time_selector_parent_class)->destroy)(object);
+}
+
+
+static void
+e_meeting_time_selector_realize (GtkWidget *widget)
+{
+ EMeetingTimeSelector *mts;
+
+ if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->realize)
+ (*GTK_WIDGET_CLASS (e_meeting_time_selector_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 (e_meeting_time_selector_parent_class)->unrealize)
+ (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->unrealize)(widget);
+}
+
+
+static void
+e_meeting_time_selector_style_set (GtkWidget *widget,
+ GtkStyle *previous_style)
+{
+ EMeetingTimeSelector *mts;
+ EMeetingTimeSelectorTime saved_time;
+ GdkFont *font;
+ gint hour, max_hour_width;
+
+ if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->style_set)
+ (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->style_set)(widget, previous_style);
+
+ mts = E_MEETING_TIME_SELECTOR (widget);
+ font = widget->style->font;
+
+ /* Calculate the widths of the hour strings in the style's font. */
+ max_hour_width = 0;
+ for (hour = 0; hour < 24; hour++) {
+ mts->hour_widths[hour] = gdk_string_width (font, EMeetingTimeSelectorHours[hour]);
+ max_hour_width = MAX (max_hour_width, mts->hour_widths[hour]);
+ }
+
+ /* The row height really depends on the requested height of the
+ GtkEntry widgets in the list on the left, so we really need to
+ call size_request on them, AFTER their style has been set. */
+ /* FIXME: This uses the default style ythickness of 2, though it won't
+ be needed when we switch to Miguel's new editable GtkCList widget
+ so I won't worry about it. */
+ mts->row_height = font->ascent + font->descent
+ + E_MEETING_TIME_SELECTOR_ENTRY_INNER_BORDER * 2
+ + 2 * 2;
+ mts->col_width = max_hour_width + 4;
+
+ 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_set_usize (mts->display_top, -1, mts->row_height * 3);
+ gtk_widget_set_usize (mts->attendees_title_bar, -1, mts->row_height);
+
+ GTK_LAYOUT (mts->display_main)->hadjustment->step_increment = mts->col_width;
+ GTK_LAYOUT (mts->display_main)->vadjustment->step_increment = mts->row_height;
+}
+
+
+/* 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;
+ gint x, y, w, h;
+
+ mts = E_MEETING_TIME_SELECTOR (widget);
+
+ /* Draw the shadow around the attendees title bar and list. */
+ x = mts->attendees_title_bar->allocation.x - 2;
+ y = mts->attendees_title_bar->allocation.y - 2;
+ w = mts->attendees_title_bar->allocation.width + 4;
+ h = mts->attendees_title_bar->allocation.height + mts->attendees_list->allocation.height + 4;
+
+ gtk_draw_shadow (widget->style, widget->window, GTK_STATE_NORMAL, GTK_SHADOW_IN, x, y, w, h);
+
+ /* 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);
+
+ if (GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->expose_event)
+ (*GTK_WIDGET_CLASS (e_meeting_time_selector_parent_class)->expose_event)(widget, event);
+
+ return FALSE;
+}
+
+
+/* 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_signal_emit_by_name (GTK_OBJECT (adj), "value_changed");
+ }
+}
+
+
+static void
+e_meeting_time_selector_vadjustment_changed (GtkAdjustment *adjustment,
+ EMeetingTimeSelector *mts)
+{
+ GtkAdjustment *adj;
+
+ adj = GTK_LAYOUT (mts->attendees_list)->vadjustment;
+ if (adj->value != adjustment->value) {
+ adj->value = adjustment->value;
+ gtk_signal_emit_by_name (GTK_OBJECT (adj), "value_changed");
+ }
+}
+
+
+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)
+{
+ EMeetingTimeSelectorTime 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)
+{
+ EMeetingTimeSelectorTime 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)
+{
+ EMeetingTimeSelectorTime 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;
+ }
+}
+
+
+/* Adds an attendee to the list, returning the row. The data is meant for
+ something like an address book id, though if the user edits the name this
+ will become invalid. We'll probably have to handle address book lookup
+ ourself. */
+gint
+e_meeting_time_selector_attendee_add (EMeetingTimeSelector *mts,
+ gchar *attendee_name,
+ gpointer data)
+{
+ EMeetingTimeSelectorAttendee attendee;
+ GtkWidget *entry;
+ gint list_width, item_width;
+
+ g_return_val_if_fail (IS_E_MEETING_TIME_SELECTOR (mts), -1);
+ g_return_val_if_fail (attendee_name != NULL, -1);
+
+ attendee.name = g_strdup (attendee_name);
+ attendee.type = E_MEETING_TIME_SELECTOR_REQUIRED_PERSON;
+ attendee.has_calendar_info = FALSE;
+ attendee.send_meeting_to = TRUE;
+ attendee.busy_periods = g_array_new (FALSE, FALSE,
+ sizeof (EMeetingTimeSelectorPeriod));
+ attendee.busy_periods_sorted = TRUE;
+ attendee.longest_period_in_days = 0;
+ attendee.data = data;
+
+ /* Add to the list on the left. */
+ entry = gtk_entry_new ();
+ gtk_entry_set_text (GTK_ENTRY (entry), attendee_name);
+ gtk_widget_show (entry);
+ list_width = GTK_WIDGET (mts->attendees_list)->allocation.width;
+ item_width = MAX (1, list_width - E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH);
+ attendee.text_item = gnome_canvas_item_new (GNOME_CANVAS_GROUP (GNOME_CANVAS (mts->attendees_list)->root),
+ gnome_canvas_widget_get_type (),
+ "GnomeCanvasWidget::widget", entry,
+ "GnomeCanvasWidget::size_pixels", TRUE,
+ "GnomeCanvasWidget::x", (gdouble) E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH,
+ "GnomeCanvasWidget::y", (gdouble) (mts->attendees->len * mts->row_height),
+ "GnomeCanvasWidget::width", (gdouble) item_width,
+ "GnomeCanvasWidget::height", (gdouble) mts->row_height,
+ NULL);
+ gnome_canvas_item_hide (attendee.text_item);
+
+ g_array_append_val (mts->attendees, attendee);
+
+ /* Update the scroll region. */
+ e_meeting_time_selector_update_attendees_list_scroll_region (mts);
+ e_meeting_time_selector_update_main_canvas_scroll_region (mts);
+
+ /* Redraw the canvases. */
+ gtk_widget_queue_draw (mts->display_top);
+ gtk_widget_queue_draw (mts->display_main);
+
+
+ return mts->attendees->len - 1;
+}
+
+
+gint
+e_meeting_time_selector_attendee_find_by_name (EMeetingTimeSelector *mts,
+ gchar *attendee_name,
+ gint start_row)
+{
+ EMeetingTimeSelectorAttendee *attendee;
+ gint row;
+
+ g_return_val_if_fail (IS_E_MEETING_TIME_SELECTOR (mts), -1);
+ g_return_val_if_fail (start_row >= 0, -1);
+ g_return_val_if_fail (start_row < mts->attendees->len, -1);
+
+ for (row = start_row; row < mts->attendees->len; row++) {
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+ if (!strcmp (attendee->name, attendee_name))
+ return row;
+ }
+
+ return -1;
+}
+
+
+gint
+e_meeting_time_selector_attendee_find_by_data (EMeetingTimeSelector *mts,
+ gpointer data,
+ gint start_row)
+{
+ EMeetingTimeSelectorAttendee *attendee;
+ gint row;
+
+ g_return_val_if_fail (IS_E_MEETING_TIME_SELECTOR (mts), -1);
+ g_return_val_if_fail (start_row >= 0, -1);
+ g_return_val_if_fail (start_row < mts->attendees->len, -1);
+
+ for (row = start_row; row < mts->attendees->len; row++) {
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+ if (attendee->data == data)
+ return row;
+ }
+
+ return -1;
+}
+
+
+void
+e_meeting_time_selector_attendee_remove (EMeetingTimeSelector *mts,
+ gint row)
+{
+ 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);
+
+ g_free (attendee->name);
+ g_array_free (attendee->busy_periods, TRUE);
+
+ /* Destroy the GtkEntry in the list. */
+ gtk_object_destroy (GTK_OBJECT (attendee->text_item));
+
+ g_array_remove_index (mts->attendees, row);
+
+ /* Update the positions of all the other GtkEntry widgets. */
+ e_meeting_time_selector_update_attendees_list_positions (mts);
+
+ e_meeting_time_selector_update_attendees_list_scroll_region (mts);
+ e_meeting_time_selector_update_main_canvas_scroll_region (mts);
+ gtk_widget_queue_draw (mts->display_top);
+ gtk_widget_queue_draw (mts->display_main);
+}
+
+
+void
+e_meeting_time_selector_attendee_set_type (EMeetingTimeSelector *mts,
+ gint row,
+ EMeetingTimeSelectorAttendeeType type)
+{
+ 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->type = type;
+}
+
+
+void
+e_meeting_time_selector_attendee_set_has_calendar_info (EMeetingTimeSelector *mts,
+ gint row,
+ gboolean has_calendar_info)
+{
+ 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->has_calendar_info = has_calendar_info;
+}
+
+
+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;
+}
+
+
+/* Clears all busy times for the given attendee. */
+void
+e_meeting_time_selector_attendee_clear_busy_periods (EMeetingTimeSelector *mts,
+ gint row)
+{
+ 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);
+
+ g_array_set_size (attendee->busy_periods, 0);
+ attendee->busy_periods_sorted = TRUE;
+ attendee->longest_period_in_days = 0;
+}
+
+
+/* Adds one busy time for the given attendee. It returns FALSE if the date
+ or time is invalid. Months and days count from 1. */
+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,
+ EMeetingTimeSelectorBusyType busy_type)
+{
+ EMeetingTimeSelectorAttendee *attendee;
+ EMeetingTimeSelectorPeriod period;
+ gint period_in_days;
+
+ g_return_val_if_fail (IS_E_MEETING_TIME_SELECTOR (mts), FALSE);
+ g_return_val_if_fail (row >= 0, FALSE);
+ g_return_val_if_fail (row < mts->attendees->len, FALSE);
+ g_return_val_if_fail (busy_type >= 0, FALSE);
+ g_return_val_if_fail (busy_type < E_MEETING_TIME_SELECTOR_BUSY_LAST, FALSE);
+
+ /* 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 (e_meeting_time_selector_compare_times (&period.start, &period.end) > 0)
+ return FALSE;
+
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+ g_array_append_val (attendee->busy_periods, period);
+ attendee->has_calendar_info = TRUE;
+ attendee->busy_periods_sorted = FALSE;
+
+ period_in_days = g_date_julian (&period.end.date) - g_date_julian (&period.start.date) + 1;
+ attendee->longest_period_in_days = MAX (attendee->longest_period_in_days, period_in_days);
+
+ return TRUE;
+}
+
+
+void
+e_meeting_time_selector_attendee_ensure_periods_sorted (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorAttendee *attendee)
+{
+ if (attendee->busy_periods_sorted)
+ return;
+
+ qsort (attendee->busy_periods->data, attendee->busy_periods->len,
+ sizeof (EMeetingTimeSelectorPeriod),
+ e_meeting_time_selector_compare_period_starts);
+ attendee->busy_periods_sorted = TRUE;
+}
+
+
+/* This compares two time periods, using their end times. */
+static gint
+e_meeting_time_selector_compare_period_starts (const void *arg1,
+ const void *arg2)
+{
+ EMeetingTimeSelectorPeriod *period1, *period2;
+
+ period1 = (EMeetingTimeSelectorPeriod *) arg1;
+ period2 = (EMeetingTimeSelectorPeriod *) arg2;
+
+ return e_meeting_time_selector_compare_times (&period1->start,
+ &period2->start);
+}
+
+
+/* This compares two time periods, using start and end times, mainly to see if
+ they overlap at all. If they overlap it returns 0. Or -1 if arg1 < arg2.
+ Or 1 if arg1 > arg2. */
+/* Currently unused. */
+#if 0
+static gint
+e_meeting_time_selector_compare_periods (const void *arg1,
+ const void *arg2)
+{
+ EMeetingTimeSelectorPeriod *period1, *period2;
+
+ period1 = (EMeetingTimeSelectorPeriod *) arg1;
+ period2 = (EMeetingTimeSelectorPeriod *) arg2;
+
+ /* If period 2 starts after period 1 ends, return 1. */
+ if (e_meeting_time_selector_compare_times (&period2->start, &period1->end) >= 0)
+ return 1;
+
+ /* If period 1 starts after period 2 ends, return -1. */
+ if (e_meeting_time_selector_compare_times (&period1->start, &period2->end) >= 0)
+ return -1;
+
+ /* They must overlap so return 0. */
+ return 0;
+}
+#endif
+
+
+static gint
+e_meeting_time_selector_compare_times (EMeetingTimeSelectorTime *time1,
+ EMeetingTimeSelectorTime *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);
+
+ g_date_strftime (buffer, 128, "%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, 128, "%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 EMeetingTimeSelectorTime in a string and returns it.
+ Note that it uses a static buffer. */
+gchar*
+e_meeting_time_selector_dump_time (EMeetingTimeSelectorTime *mtstime)
+{
+ static gchar buffer[128];
+
+ gchar buffer2[128];
+
+ g_date_strftime (buffer, 128, "%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];
+
+ g_date_strftime (buffer, 128, "%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_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));
+}
+
+
+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)
+{
+ EMeetingTimeSelectorTime start_time, end_time, *resource_free;
+ EMeetingTimeSelectorAttendee *attendee;
+ EMeetingTimeSelectorPeriod *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 < mts->attendees->len; row++) {
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee,
+ row);
+
+ /* Skip optional people if they don't matter. */
+ if (skip_optional && attendee->type == E_MEETING_TIME_SELECTOR_OPTIONAL_PERSON)
+ continue;
+
+ period = e_meeting_time_selector_find_time_clash (mts, attendee, &start_time, &end_time);
+
+ if (need_one_resource && attendee->type == E_MEETING_TIME_SELECTOR_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 (EMeetingTimeSelectorTime *start,
+ EMeetingTimeSelectorTime *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,
+ EMeetingTimeSelectorTime *start_time,
+ EMeetingTimeSelectorTime *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,
+ EMeetingTimeSelectorTime *start_time,
+ EMeetingTimeSelectorTime *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 (EMeetingTimeSelectorTime *mtstime,
+ gint days, gint hours, gint minutes)
+{
+ gint new_hours, new_minutes;
+
+ /* We have to handle negative values for hous and minutes here, since
+ EMeetingTimeSelectorTime uses 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 EMeetingTimeSelectorPeriod*
+e_meeting_time_selector_find_time_clash (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorAttendee *attendee,
+ EMeetingTimeSelectorTime *start_time,
+ EMeetingTimeSelectorTime *end_time)
+{
+ EMeetingTimeSelectorPeriod *period;
+ gint period_num;
+
+ period_num = e_meeting_time_selector_find_first_busy_period (mts, 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 < attendee->busy_periods->len) {
+ period = &g_array_index (attendee->busy_periods,
+ EMeetingTimeSelectorPeriod,
+ 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;
+}
+
+
+/* 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. */
+gint
+e_meeting_time_selector_find_first_busy_period (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorAttendee *attendee,
+ GDate *date)
+{
+ EMeetingTimeSelectorPeriod *period;
+ gint lower, upper, middle, cmp = 0;
+ GDate tmp_date;
+
+ /* Make sure the busy periods have been sorted. */
+ e_meeting_time_selector_attendee_ensure_periods_sorted (mts, attendee);
+
+ /* 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, attendee->longest_period_in_days);
+
+ /* We want the first busy period which starts on tmp_date. */
+ lower = 0;
+ upper = attendee->busy_periods->len;
+
+ if (upper == 0)
+ return -1;
+
+ while (lower < upper) {
+ middle = (lower + upper) >> 1;
+
+ period = &g_array_index (attendee->busy_periods,
+ EMeetingTimeSelectorPeriod, 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 (attendee->busy_periods,
+ EMeetingTimeSelectorPeriod, 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 (attendee->busy_periods->len <= middle)
+ return -1;
+ }
+
+ return middle;
+}
+
+
+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,
+ EMeetingTimeSelectorTime *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,
+ EMeetingTimeSelectorTime *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)
+{
+ GDate date;
+ gint max_date_width, base_width, max_weekday_width, max_month_width;
+ gint weekday, month;
+ gchar buffer[128];
+ GdkFont *font;
+
+ font = GTK_WIDGET (mts)->style->font;
+
+ /* Check if we will be able to display the full date strings. */
+ mts->date_format = E_MEETING_TIME_SELECTOR_DATE_SHORT;
+ g_date_clear (&date, 1);
+ g_date_set_dmy (&date, 20, 1, 2000);
+
+ /* Calculate the maximum date width we can fit into the display. */
+ max_date_width = mts->day_width - 2;
+
+ /* First compute the width of the date string without the day or
+ month names. */
+ g_date_strftime (buffer, 128, ", %d, %Y", &date);
+ base_width = gdk_string_width (font, buffer);
+
+ /* If that doesn't fit just return. We have to use the short format.
+ If that doesn't fit it will just be clipped. */
+ if (gdk_string_width (font, buffer) > max_date_width)
+ return;
+
+ /* Now find the biggest weekday name. We start on any day and just
+ go through seven days. */
+ max_weekday_width = 0;
+ for (weekday = 1; weekday <= 7; weekday++) {
+ g_date_strftime (buffer, 128, "%A", &date);
+ max_weekday_width = MAX (max_weekday_width,
+ gdk_string_width (font, buffer));
+ g_date_add_days (&date, 1);
+ }
+
+ /* Now find the biggest month name. */
+ max_month_width = 0;
+ for (month = 1; month <= 12; month++) {
+ g_date_set_month (&date, month);
+ g_date_strftime (buffer, 128, "%B", &date);
+ max_month_width = MAX (max_month_width,
+ gdk_string_width (font, buffer));
+ }
+
+ /* See if we can use the full date. */
+ if (base_width + max_month_width + max_weekday_width <= max_date_width) {
+ mts->date_format = E_MEETING_TIME_SELECTOR_DATE_FULL;
+ return;
+ }
+
+ /* Now try it with abbreviated weekday names. */
+ g_date_strftime (buffer, 128, " %x", &date);
+ base_width = gdk_string_width (font, buffer);
+
+ max_weekday_width = 0;
+ for (weekday = 1; weekday <= 7; weekday++) {
+ g_date_strftime (buffer, 128, "%a", &date);
+ max_weekday_width = MAX (max_weekday_width,
+ gdk_string_width (font, buffer));
+ g_date_add_days (&date, 1);
+ }
+
+ if (base_width + max_weekday_width <= max_date_width)
+ mts->date_format = E_MEETING_TIME_SELECTOR_DATE_ABBREVIATED_DAY;
+}
+
+
+/* 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;
+ EMeetingTimeSelectorTime mtstime;
+ time_t newtime;
+ struct tm *newtime_tm;
+
+ newtime = gnome_date_edit_get_date (GNOME_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)
+{
+ EMeetingTimeSelectorTime mtstime;
+ time_t newtime;
+ struct tm *newtime_tm;
+
+ newtime = gnome_date_edit_get_date (GNOME_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)
+{
+ GnomeDateEdit *start_edit, *end_edit;
+ gint low_hour, high_hour;
+
+ start_edit = GNOME_DATE_EDIT (mts->start_date_edit);
+ end_edit = GNOME_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;
+ }
+
+ gnome_date_edit_set_popup_range (start_edit, low_hour, high_hour);
+ gnome_date_edit_set_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);
+}
+
+
+static void
+e_meeting_time_selector_on_attendees_list_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation,
+ EMeetingTimeSelector *mts)
+{
+ e_meeting_time_selector_update_attendees_list_scroll_region (mts);
+ e_meeting_time_selector_update_attendees_list_positions (mts);
+}
+
+
+/* This updates the list canvas scroll region 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_attendees_list_scroll_region (EMeetingTimeSelector *mts)
+{
+ gint height, canvas_width, canvas_height;
+
+ height = mts->row_height * mts->attendees->len;
+ canvas_width = GTK_WIDGET (mts->attendees_list)->allocation.width;
+ canvas_height = GTK_WIDGET (mts->attendees_list)->allocation.height;
+
+ height = MAX (height, canvas_height);
+
+ gnome_canvas_set_scroll_region (GNOME_CANVAS (mts->attendees_list),
+ 0, 0,
+ canvas_width,
+ height);
+}
+
+
+/* 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, list_width;
+
+ height = mts->row_height * mts->attendees->len;
+ canvas_height = GTK_WIDGET (mts->display_main)->allocation.height;
+ list_width = GTK_WIDGET (mts->attendees_list)->allocation.width;
+
+ height = MAX (height, canvas_height);
+
+ gnome_canvas_set_scroll_region (GNOME_CANVAS (mts->attendees_list),
+ 0, 0,
+ list_width,
+ 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)
+{
+ EMeetingTimeSelectorTime 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;
+ EMeetingTimeSelectorTime 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);
+ gnome_date_edit_set_time (GNOME_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);
+ gnome_date_edit_set_time (GNOME_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 (EMeetingTimeSelectorTime *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);
+ }
+}
+
+
+static void
+e_meeting_time_selector_update_attendees_list_positions (EMeetingTimeSelector *mts)
+{
+ EMeetingTimeSelectorAttendee *attendee;
+ gint list_width, item_width;
+ gint row;
+
+ list_width = GTK_WIDGET (mts->attendees_list)->allocation.width;
+ item_width = MAX (1, list_width - E_MEETING_TIME_SELECTOR_ICON_COLUMN_WIDTH);
+ for (row = 0; row < mts->attendees->len; row++) {
+ attendee = &g_array_index (mts->attendees,
+ EMeetingTimeSelectorAttendee, row);
+
+ gnome_canvas_item_set (attendee->text_item,
+ "GnomeCanvasWidget::y", (gdouble) (row * mts->row_height),
+ "GnomeCanvasWidget::width", (gdouble) item_width,
+ "GnomeCanvasWidget::height", (gdouble) (mts->row_height),
+ NULL);
+
+ gnome_canvas_item_show (attendee->text_item);
+ }
+
+}
+
+
+/*
+ * 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,
+ EMeetingTimeSelectorTime *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 EMeetingTimeSelectorTime 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,
+ EMeetingTimeSelectorTime *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;
+}
+
+
diff --git a/widgets/meeting-time-sel/e-meeting-time-sel.h b/widgets/meeting-time-sel/e-meeting-time-sel.h
new file mode 100644
index 0000000000..6df15fe54c
--- /dev/null
+++ b/widgets/meeting-time-sel/e-meeting-time-sel.h
@@ -0,0 +1,496 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ * Damon Chaplin <damon@gtk.org>
+ *
+ * Copyright 1999, Helix Code, 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 <gtk/gtktable.h>
+#include <libgnomeui/gnome-canvas.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
+
+
+/* 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_TIME_SELECTOR_REQUIRED_PERSON. */
+typedef enum
+{
+ E_MEETING_TIME_SELECTOR_REQUIRED_PERSON,
+ E_MEETING_TIME_SELECTOR_OPTIONAL_PERSON,
+ E_MEETING_TIME_SELECTOR_RESOURCE
+} EMeetingTimeSelectorAttendeeType;
+
+
+/* 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_TIME_SELECTOR_BUSY_TENTATIVE = 0,
+ E_MEETING_TIME_SELECTOR_BUSY_OUT_OF_OFFICE = 1,
+ E_MEETING_TIME_SELECTOR_BUSY_BUSY = 2,
+
+ E_MEETING_TIME_SELECTOR_BUSY_LAST = 3
+} EMeetingTimeSelectorBusyType;
+
+
+/* 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;
+
+
+/* This is our representation of a time. We use a GDate to store the day,
+ and guint8s for the hours and minutes. */
+typedef struct _EMeetingTimeSelectorTime EMeetingTimeSelectorTime;
+struct _EMeetingTimeSelectorTime
+{
+ GDate date;
+ guint8 hour;
+ guint8 minute;
+};
+
+
+/* This represents a busy period. */
+typedef struct _EMeetingTimeSelectorPeriod EMeetingTimeSelectorPeriod;
+struct _EMeetingTimeSelectorPeriod
+{
+ EMeetingTimeSelectorTime start;
+ EMeetingTimeSelectorTime end;
+ EMeetingTimeSelectorBusyType busy_type;
+};
+
+
+/* This contains information on one attendee. */
+typedef struct _EMeetingTimeSelectorAttendee EMeetingTimeSelectorAttendee;
+struct _EMeetingTimeSelectorAttendee
+{
+ gchar *name;
+
+ /* The type of attendee, e.g. a person or a resource. */
+ EMeetingTimeSelectorAttendeeType type;
+
+ /* This is TRUE if the attendee has calendar information available.
+ It is set to TRUE when a busy period is added, but can also be set
+ to TRUE explicitly to indicate that the attendee has calendar
+ information available, but no current busy periods. If it is FALSE
+ then a diagonal stipple pattern is used to fill the entire row in
+ the main graphical display. */
+ gboolean has_calendar_info;
+
+ /* This is TRUE if the meeting request is sent to this attendee. */
+ gboolean send_meeting_to;
+
+ /* This is an array of EMeetingTimeSelectorPeriod elements. When it is
+ updated busy_periods_sorted is set to FALSE, and if a function
+ needs them sorted, it should call this to re-sort them if needed:
+ e_meeting_time_selector_attendee_ensure_periods_sorted(). Note that
+ they are sorted by the start times. */
+ GArray *busy_periods;
+ gboolean busy_periods_sorted;
+
+ /* This holds the length of the longest busy period in days, rounded
+ up. It is used to determine where to start looking in the
+ busy_periods array. If we didn't use this we'd have to go through
+ most of the busy_periods array every time we wanted to paint part
+ of the display. */
+ gint longest_period_in_days;
+
+ /* This is the canvas text item where the name is edited. */
+ GnomeCanvasItem *text_item;
+
+ /* This is supposed to be something like an address book id. */
+ gpointer data;
+};
+
+/* 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_title_bar_vbox;
+
+ /* The 'All Attendees' title bar above the list of attendees. */
+ GtkWidget *attendees_title_bar;
+
+ /* The list of attendees. */
+ GtkWidget *attendees_list;
+
+ /* 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_TIME_SELECTOR_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;
+
+
+ /*
+ * Attendee Data.
+ */
+
+ /* This is an array of EMeetingTimeSelectorAttendee elements. */
+ GArray *attendees;
+
+
+ /*
+ * 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. */
+ EMeetingTimeSelectorTime meeting_start_time;
+ EMeetingTimeSelectorTime 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 (void);
+
+/* 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);
+
+/* Adds an attendee to the list, returning the row. The data is meant for
+ something like an address book id, though if the user edits the name this
+ will become invalid. We'll probably have to handle address book lookup
+ ourself. */
+gint e_meeting_time_selector_attendee_add (EMeetingTimeSelector *mts,
+ gchar *attendee_name,
+ gpointer data);
+gint e_meeting_time_selector_attendee_find_by_name (EMeetingTimeSelector *mts,
+ gchar *attendee_name,
+ gint start_row);
+gint e_meeting_time_selector_attendee_find_by_data (EMeetingTimeSelector *mts,
+ gpointer data,
+ gint start_row);
+void e_meeting_time_selector_attendee_remove (EMeetingTimeSelector *mts,
+ gint row);
+
+void e_meeting_time_selector_attendee_set_type (EMeetingTimeSelector *mts,
+ gint row,
+ EMeetingTimeSelectorAttendeeType type);
+void e_meeting_time_selector_attendee_set_has_calendar_info (EMeetingTimeSelector *mts,
+ gint row,
+ gboolean has_calendar_info);
+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,
+ EMeetingTimeSelectorBusyType 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 (EMeetingTimeSelectorTime *mtstime);
+
+gint e_meeting_time_selector_find_first_busy_period (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorAttendee *attendee,
+ GDate *date);
+
+/* Makes sure the busy periods are sorted, so we can do binary searches. */
+void e_meeting_time_selector_attendee_ensure_periods_sorted (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorAttendee *attendee);
+
+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,
+ EMeetingTimeSelectorTime *time);
+gint e_meeting_time_selector_calculate_time_position (EMeetingTimeSelector *mts,
+ EMeetingTimeSelectorTime *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 (EMeetingTimeSelectorTime *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/widgets/meeting-time-sel/test-meeting-time-sel.c b/widgets/meeting-time-sel/test-meeting-time-sel.c
new file mode 100644
index 0000000000..debe4e8f40
--- /dev/null
+++ b/widgets/meeting-time-sel/test-meeting-time-sel.c
@@ -0,0 +1,236 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+
+/*
+ * Author :
+ * Damon Chaplin <damon@gtk.org>
+ *
+ * Copyright 1999, Helix Code, 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
+ */
+
+#include <config.h>
+#include <gnome.h>
+#include "e-meeting-time-sel.h"
+
+void add_random_attendee_test_data (EMeetingTimeSelector *mts);
+void add_simple_attendee_test_data (EMeetingTimeSelector *mts);
+gint get_random_int (gint max);
+
+int
+main (int argc, char *argv[])
+{
+ GtkWidget *window, *mts;
+ gint i;
+
+ gnome_init ("test-meeting-time-selector", "0.1", argc, argv);
+
+ gtk_widget_push_visual (gdk_imlib_get_visual ());
+ gtk_widget_push_colormap (gdk_imlib_get_colormap ());
+
+ window = gnome_dialog_new ("Plan a Meeting", "Make Meeting",
+ GNOME_STOCK_BUTTON_CLOSE, NULL);
+ gtk_window_set_default_size (GTK_WINDOW (window), 600, 400);
+ gtk_window_set_policy (GTK_WINDOW (window), FALSE, TRUE, FALSE);
+
+ mts = e_meeting_time_selector_new ();
+ gtk_container_add (GTK_CONTAINER (GNOME_DIALOG (window)->vbox), mts);
+ gtk_window_add_accel_group (GTK_WINDOW (window),
+ E_MEETING_TIME_SELECTOR (mts)->accel_group);
+ gtk_widget_show (mts);
+
+ gtk_widget_pop_visual ();
+ gtk_widget_pop_colormap ();
+
+ i = 0;
+#if 1
+ for (i = 0; i < 20; i++) {
+ add_random_attendee_test_data (E_MEETING_TIME_SELECTOR (mts));
+ }
+#else
+ for (i = 0; i < 1; i++) {
+ add_simple_attendee_test_data (E_MEETING_TIME_SELECTOR (mts));
+ }
+#endif
+
+#if 0
+ e_meeting_time_selector_dump (E_MEETING_TIME_SELECTOR (mts));
+#endif
+
+ gnome_dialog_run (GNOME_DIALOG (window));
+
+ gtk_main ();
+ return 0;
+}
+
+
+/* Adds an attendee and a lot of random busy periods. The periods start 60
+ days before the current date and extend over 365 days, to match the range
+ that EMeetingTimeSelector currently displays. We generate a time_t and an
+ interval and then convert them into a struct tm which provides everything
+ we need. */
+void
+add_random_attendee_test_data (EMeetingTimeSelector *mts)
+{
+ gchar buffer[128], *name;
+ gint row, num_periods, busy_period, random_num, duration;
+ EMeetingTimeSelectorAttendeeType type;
+ EMeetingTimeSelectorBusyType busy_type;
+ time_t range_start;
+ time_t period_start;
+ time_t period_end;
+ struct tm *tmp_tm;
+ struct tm tm1;
+ struct tm tm2;
+
+ /* Determine the type of attendee. */
+ random_num = get_random_int (10);
+ if (random_num < 4) {
+ type = E_MEETING_TIME_SELECTOR_REQUIRED_PERSON;
+ name = "Req. Attendee";
+ } else if (random_num < 7) {
+ type = E_MEETING_TIME_SELECTOR_OPTIONAL_PERSON;
+ name = "Opt. Attendee";
+ } else {
+ type = E_MEETING_TIME_SELECTOR_RESOURCE;
+ name = "Resource";
+ }
+
+ sprintf (buffer, "%s %i", name, mts->attendees->len + 1);
+ row = e_meeting_time_selector_attendee_add (mts, buffer, NULL);
+ e_meeting_time_selector_attendee_set_type (mts, row, type);
+
+ /* Don't send the meeting request to some attendees. */
+ if (get_random_int (10) <= 2)
+ e_meeting_time_selector_attendee_set_send_meeting_to (mts, row,
+ FALSE);
+
+ /* Some attendees have no calendar information. */
+ if (get_random_int (10) == 2)
+ return;
+
+ range_start = time (NULL) - 61 * 24 * 60 * 60;
+ num_periods = get_random_int (1000);
+#if 0
+ g_print ("num_periods: %i\n", num_periods);
+#endif
+ for (busy_period = 0; busy_period < num_periods; busy_period++) {
+
+ period_start = range_start + get_random_int (365 * 24 * 60 * 60);
+
+ /* Make busy periods mainly 30 mins to a few hours, with a
+ couple of week/fortnight periods as well. */
+ random_num = get_random_int (10000);
+ if (random_num < 2000)
+ duration = 30;
+ else if (random_num < 5000)
+ duration = 60;
+ else if (random_num < 7500)
+ duration = 90;
+ else if (random_num < 9995)
+ duration = 120;
+ else if (random_num < 9998)
+ duration = 60 * 24 * 7;
+ else
+ duration = 60 * 24 * 14;
+#if 0
+ g_print ("random_num: %i, duration: %i\n",
+ random_num, duration);
+#endif
+ period_end = period_start + duration * 60;
+
+ tmp_tm = localtime (&period_start);
+ tm1 = *tmp_tm;
+ tmp_tm = localtime (&period_end);
+ tm2 = *tmp_tm;
+
+ /* A hack to avoid daylight-saving time problems. */
+ if (tm2.tm_hour == tm1.tm_hour && tm2.tm_min < tm1.tm_min)
+ tm2.tm_hour++;
+
+ busy_type = get_random_int (E_MEETING_TIME_SELECTOR_BUSY_LAST);
+
+ if (!e_meeting_time_selector_attendee_add_busy_period (mts, row, tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday, tm1.tm_hour, tm1.tm_min, tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday, tm2.tm_hour, tm2.tm_min, busy_type))
+ {
+ g_print ("Invalid busy period %i/%i/%i %i:%i to %i/%i/%i %i:%i\n", tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday, tm1.tm_hour, tm1.tm_min, tm2.tm_year + 1900, tm2.tm_mon + 1, tm2.tm_mday, tm2.tm_hour, tm2.tm_min);
+ g_print ("random_num: %i, duration: %i\n",
+ random_num, duration);
+ }
+ }
+}
+
+
+/* Returns a random integer between 0 and max - 1. */
+gint
+get_random_int (gint max)
+{
+ gint random_num;
+
+ random_num = (int) (max * (rand () / (RAND_MAX + 1.0)));
+#if 0
+ g_print ("Random num (%i): %i\n", max, random_num);
+#endif
+ return random_num;
+}
+
+
+void
+add_simple_attendee_test_data (EMeetingTimeSelector *mts)
+{
+ gint row;
+
+ row = e_meeting_time_selector_attendee_add (mts, "John Smith", NULL);
+ if (!e_meeting_time_selector_attendee_add_busy_period (mts, row,
+ 1999, 11, 7, 14, 30,
+ 1999, 11, 7, 16, 30,
+ E_MEETING_TIME_SELECTOR_BUSY_BUSY))
+ g_warning ("Invalid busy period");
+
+ e_meeting_time_selector_attendee_add_busy_period (mts, row,
+ 1999, 11, 7, 10, 30,
+ 1999, 11, 7, 11, 30,
+ E_MEETING_TIME_SELECTOR_BUSY_OUT_OF_OFFICE);
+ e_meeting_time_selector_attendee_add_busy_period (mts, row,
+ 1999, 11, 4, 10, 30,
+ 1999, 11, 7, 11, 30,
+ E_MEETING_TIME_SELECTOR_BUSY_BUSY);
+ row = e_meeting_time_selector_attendee_add (mts, "Dave Jones", NULL);
+ e_meeting_time_selector_attendee_add_busy_period (mts, row,
+ 1999, 11, 7, 15, 30,
+ 1999, 11, 7, 18, 30,
+ E_MEETING_TIME_SELECTOR_BUSY_TENTATIVE);
+ e_meeting_time_selector_attendee_add_busy_period (mts, row,
+ 1999, 11, 7, 11, 00,
+ 1999, 11, 7, 12, 00,
+ E_MEETING_TIME_SELECTOR_BUSY_BUSY);
+
+ row = e_meeting_time_selector_attendee_add (mts, "Andrew Carlisle", NULL);
+ e_meeting_time_selector_attendee_set_send_meeting_to (mts, row, FALSE);
+
+ row = e_meeting_time_selector_attendee_add (mts, "Michael Cain", NULL);
+ e_meeting_time_selector_attendee_add_busy_period (mts, row,
+ 1999, 11, 7, 15, 30,
+ 1999, 11, 7, 18, 30,
+ E_MEETING_TIME_SELECTOR_BUSY_TENTATIVE);
+ e_meeting_time_selector_attendee_add_busy_period (mts, row,
+ 1999, 11, 7, 12, 30,
+ 1999, 11, 7, 13, 30,
+ E_MEETING_TIME_SELECTOR_BUSY_OUT_OF_OFFICE);
+ e_meeting_time_selector_attendee_add_busy_period (mts, row,
+ 1999, 11, 7, 11, 00,
+ 1999, 11, 7, 12, 00,
+ E_MEETING_TIME_SELECTOR_BUSY_TENTATIVE);
+}