/* Full day widget for gncal
 *
 * Copyright (C) 1998 The Free Software Foundation
 *
 * Authors: Federico Mena <quartic@gimp.org>
 *          Miguel de Icaza <miguel@kernel.org>
 */

#include <string.h>
#include <gdk/gdkkeysyms.h>
#include <gtk/gtkdrawingarea.h>
#include <gtk/gtktext.h>
#include "eventedit.h"
#include "gncal-full-day.h"
#include "view-utils.h"
#include "main.h"

#define TEXT_BORDER 2
#define HANDLE_SIZE 8
#define MIN_WIDTH 300
#define XOR_RECT_WIDTH 2
#define UNSELECT_TIMEOUT 150 /* ms */


typedef struct {
	iCalObject *ico;
	GtkWidget  *widget;
	GdkWindow  *window;
	int         lower_row; /* zero is first displayed row */
	int         rows_used;
	int         x;         /* coords of child's window */
	int         y;
	int         width;
	int         height;
	time_t      start, end;
} Child;

struct layout_row {
	int  intersections;
	int *slots;
};

struct drag_info {
	enum {
		DRAG_NONE,
		DRAG_SELECT,		/* selecting a range in the main window */
		DRAG_MOVE,		/* moving a child */
		DRAG_SIZE_TOP,		/* resizing a child */
		DRAG_SIZE_BOTTOM
	} drag_mode;

	Child *child;
	int child_click_y;
	int child_start_row;
	int child_rows_used;

	int sel_click_row;
	int sel_start_row;
	int sel_rows_used;
	guint32 click_time;
};

struct menu_item {
	char *text;
	GtkSignalFunc callback;
	gpointer data;
	int sensitive;
};


enum {
	RANGE_ACTIVATED,
	LAST_SIGNAL
};


static void gncal_full_day_class_init     (GncalFullDayClass *class);
static void gncal_full_day_init           (GncalFullDay      *fullday);
static void gncal_full_day_destroy        (GtkObject         *object);
static void gncal_full_day_map            (GtkWidget         *widget);
static void gncal_full_day_unmap          (GtkWidget         *widget);
static void gncal_full_day_realize        (GtkWidget         *widget);
static void gncal_full_day_unrealize      (GtkWidget         *widget);
static void gncal_full_day_draw           (GtkWidget         *widget,
					   GdkRectangle      *area);
static void gncal_full_day_draw_focus     (GtkWidget         *widget);
static void gncal_full_day_size_request   (GtkWidget         *widget,
					   GtkRequisition    *requisition);
static void gncal_full_day_size_allocate  (GtkWidget         *widget,
					   GtkAllocation     *allocation);
static gint gncal_full_day_button_press   (GtkWidget         *widget,
					   GdkEventButton    *event);
static gint gncal_full_day_button_release (GtkWidget         *widget,
					   GdkEventButton    *event);
static gint gncal_full_day_motion         (GtkWidget         *widget,
					   GdkEventMotion    *event);
static gint gncal_full_day_expose         (GtkWidget         *widget,
					   GdkEventExpose    *event);
static gint gncal_full_day_key_press      (GtkWidget         *widget,
					   GdkEventKey       *event);
static gint gncal_full_day_focus_in       (GtkWidget         *widget,
					   GdkEventFocus     *event);
static gint gncal_full_day_focus_out      (GtkWidget         *widget,
					   GdkEventFocus     *event);
static void gncal_full_day_foreach        (GtkContainer      *container,
					   GtkCallback        callback,
					   gpointer           callback_data);

static void range_activated (GncalFullDay *fullday);


static GtkContainerClass *parent_class;

static int fullday_signals[LAST_SIGNAL] = { 0 };


static void
get_tm_range (GncalFullDay *fullday,
	      time_t time_lower, time_t time_upper,
	      struct tm *lower, struct tm *upper,
	      int *lower_row, int *rows_used)
{
	struct tm tm_lower, tm_upper;
	int lmin, umin;
	int lrow;

	/* Lower */

	tm_lower = *localtime (&time_lower);

	if ((tm_lower.tm_min % fullday->interval) != 0) {
		tm_lower.tm_min -= tm_lower.tm_min % fullday->interval; /* round down */
		mktime (&tm_lower);
	}

	/* Upper */

	tm_upper = *localtime (&time_upper);

	if ((tm_upper.tm_min % fullday->interval) != 0) {
		tm_upper.tm_min += fullday->interval - (tm_upper.tm_min % fullday->interval); /* round up */
		mktime (&tm_upper);
	}

	if (lower)
		*lower = tm_lower;

	if (upper)
		*upper = tm_upper;

	lmin = 60 * tm_lower.tm_hour + tm_lower.tm_min;
	umin = 60 * tm_upper.tm_hour + tm_upper.tm_min;

	if (umin == 0) /* midnight of next day? */
		umin = 60 * 24;

	lrow = lmin / fullday->interval;

	if (lower_row)
		*lower_row = lrow;

	if (rows_used)
		*rows_used = (umin - lmin) / fullday->interval;
}

static void
child_map (GncalFullDay *fullday, Child *child)
{
	gdk_window_show (child->window);
	gtk_widget_show (child->widget); /* OK, not just a map... */
}

static void
child_unmap (GncalFullDay *fullday, Child *child)
{
	gdk_window_hide (child->window);

	if (GTK_WIDGET_MAPPED (child->widget))
		gtk_widget_unmap (child->widget);
}

static void
child_set_text_pos (Child *child)
{
	GtkAllocation allocation;
	int has_focus;

	has_focus = GTK_WIDGET_HAS_FOCUS (child->widget);

	allocation.x = HANDLE_SIZE;
	allocation.y = has_focus ? HANDLE_SIZE : 0;
	allocation.width = child->width - HANDLE_SIZE;
	allocation.height = child->height - (has_focus ? (2 * HANDLE_SIZE) : 0);

	gtk_widget_size_request (child->widget, &child->widget->requisition); /* FIXME: is this needed? */
	gtk_widget_size_allocate (child->widget, &allocation);
}

static void
child_realize (GncalFullDay *fullday, Child *child)
{
	GdkWindowAttr attributes;
	gint attributes_mask;
	GtkWidget *widget;

	widget = GTK_WIDGET (fullday);

	attributes.window_type = GDK_WINDOW_CHILD;
	attributes.x = child->x;
	attributes.y = child->y;
	attributes.width = child->width;
	attributes.height = child->height;
	attributes.wclass = GDK_INPUT_OUTPUT;
	attributes.visual = gtk_widget_get_visual (widget);
	attributes.colormap = gtk_widget_get_colormap (widget);
	attributes.cursor = fullday->up_down_cursor;
	attributes.event_mask = (GDK_EXPOSURE_MASK
				 | GDK_BUTTON_PRESS_MASK
				 | GDK_BUTTON_RELEASE_MASK
				 | GDK_BUTTON_MOTION_MASK
				 | GDK_POINTER_MOTION_HINT_MASK
				 | GDK_KEY_PRESS_MASK);

	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP | GDK_WA_CURSOR;

	child->window = gdk_window_new (widget->window, &attributes, attributes_mask);
	gdk_window_set_user_data (child->window, widget);

	gtk_style_set_background (widget->style, child->window, GTK_STATE_NORMAL);

	gtk_widget_set_parent_window (child->widget, child->window);

	child_set_text_pos (child);
}

static void
child_unrealize (GncalFullDay *fullday, Child *child)
{
	gdk_window_set_user_data (child->window, NULL);
	gdk_window_destroy (child->window);
	child->window = NULL;
}

static void
child_draw (GncalFullDay *fullday, Child *child, GdkRectangle *area, int draw_child)
{
	GdkRectangle arect, rect, dest;
	int has_focus;

	if (!area) {
		arect.x = 0;
		arect.y = 0;

		arect.width = child->width;
		arect.height = child->height;

		area = &arect;
	}

	has_focus = GTK_WIDGET_HAS_FOCUS (child->widget);

	/* Left handle */

	rect.x = 0;
	rect.y = has_focus ? HANDLE_SIZE : 0;
	rect.width = HANDLE_SIZE;
	rect.height = has_focus ? (child->height - 2 * HANDLE_SIZE) : child->height;

	if (gdk_rectangle_intersect (&rect, area, &dest))
		view_utils_draw_textured_frame (GTK_WIDGET (fullday), child->window, &rect, GTK_SHADOW_OUT);

	if (has_focus) {
		/* Top handle */

		rect.x = 0;
		rect.y = 0;
		rect.width = child->width;
		rect.height = HANDLE_SIZE;

		if (gdk_rectangle_intersect (&rect, area, &dest))
			view_utils_draw_textured_frame (GTK_WIDGET (fullday), child->window, &rect, GTK_SHADOW_OUT);

		/* Bottom handle */

		rect.y = child->height - HANDLE_SIZE;

		if (gdk_rectangle_intersect (&rect, area, &dest))
			view_utils_draw_textured_frame (GTK_WIDGET (fullday), child->window, &rect, GTK_SHADOW_OUT);
	}

	if (draw_child) {
		area->x -= HANDLE_SIZE;
		area->y -= has_focus ? HANDLE_SIZE : 0;
		gtk_widget_draw (child->widget, area);
	}
}

static void
child_range_changed (GncalFullDay *fullday, Child *child)
{
	struct tm start, end;
	int lower_row, rows_used;
	int f_lower_row;

	/* Calc display range for event */

	get_tm_range (fullday, child->start, child->end, &start, &end, &lower_row, &rows_used);
	get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, &f_lower_row, NULL);

	child->lower_row = lower_row - f_lower_row;
	child->rows_used = rows_used;
}

static void
popup_menu (struct menu_item *items, int nitems, guint32 time)
{
	GtkWidget *menu;
	GtkWidget *item;
	int i;

	menu = gtk_menu_new (); /* FIXME: this baby is never freed */

	for (i = 0; i < nitems; i++) {
		if (items[i].text) {
			item = gtk_menu_item_new_with_label (_(items[i].text));
			gtk_signal_connect (GTK_OBJECT (item), "activate",
					    items[i].callback,
					    items[i].data);
			gtk_widget_set_sensitive (item, items[i].sensitive);
		} else
			item = gtk_menu_item_new ();

		gtk_widget_show (item);
		gtk_menu_append (GTK_MENU (menu), item);
	}

	gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 3, time);
}

static void
new_appointment (GtkWidget *widget, gpointer data)
{
	GncalFullDay *fullday;
	GtkWidget *ee;
	iCalObject *ico;
	
	fullday = GNCAL_FULL_DAY (data);

	ico = ical_new ("", user_name, "");
	ico->new = 1;
	
	gncal_full_day_selection_range (fullday, &ico->dtstart, &ico->dtend);
	ee = event_editor_new (fullday->calendar, ico);
	gtk_widget_show (ee);
}

static void
edit_appointment (GtkWidget *widget, gpointer data)
{
	Child *child;
	GtkWidget *ee;

	child = data;

	ee = event_editor_new (GNCAL_FULL_DAY (child->widget->parent)->calendar, child->ico);
	gtk_widget_show (ee);
}

static void
delete_appointment (GtkWidget *widget, gpointer data)
{
	Child *child;
	GncalFullDay *fullday;

	child = data;

	fullday = GNCAL_FULL_DAY (child->widget->parent);

	gnome_calendar_remove_object (fullday->calendar, child->ico);
}

static void
child_popup_menu (GncalFullDay *fullday, Child *child, guint32 event_time)
{
	int sensitive;

	static struct menu_item child_items[] = {
		{ N_("Edit this appointment..."), (GtkSignalFunc) edit_appointment, NULL, TRUE },
		{ N_("Delete this appointment"), (GtkSignalFunc) delete_appointment, NULL, TRUE },
		{ NULL, NULL, NULL, TRUE },
		{ N_("New appointment..."), (GtkSignalFunc) new_appointment, NULL, TRUE }
	};

	child_items[0].data = child;
	child_items[1].data = child;
	child_items[3].data = fullday;

	sensitive = (child->ico->user_data == NULL);

	child_items[0].sensitive = sensitive;
	child_items[1].sensitive = sensitive;

	popup_menu (child_items, sizeof (child_items) / sizeof (child_items[0]), event_time);
}

static void
child_realized_setup (GtkWidget *widget, gpointer data)
{
	Child *child;
	GncalFullDay *fullday;

	child = data;
	fullday = GNCAL_FULL_DAY (widget->parent);

	gdk_window_set_cursor (widget->window, fullday->beam_cursor);

	gtk_text_insert (GTK_TEXT (widget), NULL, NULL, NULL,
			 child->ico->summary,
			 strlen (child->ico->summary));
}

static void
child_set_pos (GncalFullDay *fullday, Child *child, int x, int y, int width, int height)
{
	child->x = x;
	child->y = y;
	child->width = width;
	child->height = height;

	if (!child->window) /* realized? */
		return;

	child_set_text_pos (child);
	gdk_window_move_resize (child->window, x, y, width, height);
}

static int
calc_row_height (GncalFullDay *fullday)
{
	int f_rows;
	GtkWidget *widget;

	get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows);

	widget = GTK_WIDGET (fullday);

	return (widget->allocation.height - 2 * widget->style->klass->ythickness) / f_rows;
}

static void
child_set_size (Child *child)
{
	int row_height;
	int x, y, width, height;
	GncalFullDay *fullday;

	fullday = GNCAL_FULL_DAY (child->widget->parent);

	row_height = calc_row_height (fullday);

	x = child->x;
	y = child->lower_row * row_height + GTK_WIDGET (fullday)->style->klass->ythickness;
	width = child->width;
	height = child->rows_used * row_height;

	if (GTK_WIDGET_HAS_FOCUS (child->widget)) {
		y -= HANDLE_SIZE;
		height += 2 * HANDLE_SIZE;
	}

	child_set_pos (fullday, child, x, y, width, height);
}

static gint
child_focus_in (GtkWidget *widget, GdkEventFocus *event, gpointer data)
{
	Child *child;

	child = data;

	child_set_size (child);

	return FALSE;
}

static gint
child_focus_out (GtkWidget *widget, GdkEventFocus *event, gpointer data)
{
	Child *child;
	GncalFullDay *fullday;

	child = data;

	/* Update summary in calendar object */

	if (child->ico->summary)
		g_free (child->ico->summary);

	child->ico->summary = gtk_editable_get_chars (GTK_EDITABLE (widget), 0, -1);

	child_set_size (child);

	/* Notify calendar of change */

	fullday = GNCAL_FULL_DAY (widget->parent);

	gnome_calendar_object_changed (fullday->calendar, child->ico, CHANGE_SUMMARY);

	return FALSE;
}

static gint
child_key_press (GtkWidget *widget, GdkEventKey *event, gpointer data)
{
	if (event->keyval != GDK_Escape)
		return FALSE;

	/* If user pressed Esc, un-focus the child by focusing the fullday widget */

	gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "key_press_event");
	gtk_widget_grab_focus (widget->parent);

	return FALSE;
}

static gint
child_button_press (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
	Child *child;
	GncalFullDay *fullday;

	if (event->button != 3)
		return FALSE;

	child = data;
	fullday = GNCAL_FULL_DAY (widget->parent);

	gtk_signal_emit_stop_by_name (GTK_OBJECT (widget), "button_press_event");
	child_popup_menu (fullday, child, event->time);

	return TRUE;
}

static Child *
child_new (GncalFullDay *fullday, time_t start, time_t end, iCalObject *ico)
{
	Child *child;

	child = g_new (Child, 1);

	child->ico = ico;
	child->widget = gtk_text_new (NULL, NULL);
	child->window = NULL;
	child->x = 0;
	child->y = 0;
	child->width = 0;
	child->height = 0;
	child->start  = start;
	child->end    = end;
	
	child_range_changed (fullday, child);

	/* We set the i-beam cursor and the initial summary text upon realization */

	gtk_signal_connect (GTK_OBJECT (child->widget), "realize",
			    (GtkSignalFunc) child_realized_setup,
			    child);

	gtk_signal_connect_after (GTK_OBJECT (child->widget), "focus_in_event",
				  (GtkSignalFunc) child_focus_in,
				  child);

	gtk_signal_connect_after (GTK_OBJECT (child->widget), "focus_out_event",
				  (GtkSignalFunc) child_focus_out,
				  child);

	gtk_signal_connect (GTK_OBJECT (child->widget), "key_press_event",
			    (GtkSignalFunc) child_key_press,
			    child);

	gtk_signal_connect (GTK_OBJECT (child->widget), "button_press_event",
			    (GtkSignalFunc) child_button_press,
			    child);

	/* Finish setup */

	gtk_text_set_editable (GTK_TEXT (child->widget), TRUE);
	gtk_text_set_word_wrap (GTK_TEXT (child->widget), TRUE);

	gtk_widget_set_parent (child->widget, GTK_WIDGET (fullday));

	return child;
}

static void
child_destroy (GncalFullDay *fullday, Child *child)
{
	/* Unparent the child widget manually as we don't have a remove method */

	gtk_widget_ref (child->widget);

	gtk_widget_unparent (child->widget);

	if (GTK_WIDGET_MAPPED (fullday))
		child_unmap (fullday, child);

	if (GTK_WIDGET_REALIZED (fullday))
		child_unrealize (fullday, child);

	gtk_widget_unref (child->widget);

	g_free (child);
}

static struct layout_row *
layout_get_rows (GncalFullDay *fullday, int *rowcount)
{
	struct layout_row *rows;
	int max_i;
	int f_rows;
	GList *children;
	Child *child;
	int i, n;

	get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows);

	*rowcount = f_rows;
	rows = g_new0 (struct layout_row, f_rows);
	max_i = 0;

	for (children = fullday->children; children; children = children->next) {
		child = children->data;

		for (i = 0; i < child->rows_used; i++) {
			n = child->lower_row + i;

			rows[n].intersections++;
			
			if (rows[n].intersections > max_i)
				max_i = rows[n].intersections;
		}
	}

	for (i = 0; i < f_rows; i++)
		rows[i].slots = g_new0 (int, max_i);

	return rows;
}

static void
layout_free_rows (struct layout_row *rows, int f_rows)
{
	int i;

	for (i = 0; i < f_rows; i++)
		g_free (rows[i].slots);

	g_free (rows);
}

static void
layout_get_child_intersections (Child *child, struct layout_row *rows, int *min, int *max)
{
	int i, n;
	int imin, imax;

	imax = 0;

	for (i = 0; i < child->rows_used; i++) {
		n = child->lower_row + i;

		if (rows[n].intersections > imax)
			imax = rows[n].intersections;
	}

	imin = imax;

	for (i = 0; i < child->rows_used; i++) {
		n = child->lower_row + i;

		if (rows[n].intersections < imin)
			imin = rows[n].intersections;
	}

	if (min)
		*min = imin;

	if (max)
		*max = imax;
}

static int
calc_labels_width (GncalFullDay *fullday)
{
	struct tm cur, upper;
	time_t tim, time_upper;
	int width, max_w;
	char buf[40];

	get_tm_range (fullday, fullday->lower, fullday->upper, &cur, &upper, NULL, NULL);

	max_w = 0;

	tim = mktime (&cur);
	time_upper = mktime (&upper);

	while (tim < time_upper) {
		if (am_pm_flag)
			strftime (buf, sizeof (buf), "%I:%M%p", &cur);
		else
			strftime (buf, sizeof (buf), "%H:%M", &cur);
			

		width = gdk_string_width (GTK_WIDGET (fullday)->style->font, buf);

		if (width > max_w)
			max_w = width;

		cur.tm_min += fullday->interval;
		tim = mktime (&cur);
	}

	return max_w;
}

static void
layout_child (GncalFullDay *fullday, Child *child, struct layout_row *rows, int left_x)
{
	GtkWidget *widget;

	widget = GTK_WIDGET (fullday);

	child->x = left_x;

	/* FIXME: for now, the children overlap.  Make it layout them nicely. */

	child->width = widget->allocation.width - (widget->style->klass->xthickness + left_x);

	/* Position child */

	child_set_size (child);
}

static void
layout_children (GncalFullDay *fullday)
{
	struct layout_row *rows;
	GList *children;
	GtkWidget *widget;
	int left_x, rowcount;

	rows = layout_get_rows (fullday, &rowcount);

	widget = GTK_WIDGET (fullday);

	left_x = 2 * (widget->style->klass->xthickness + TEXT_BORDER) + calc_labels_width (fullday);

	for (children = fullday->children; children; children = children->next)
		layout_child (fullday, children->data, rows, left_x);

	layout_free_rows (rows, rowcount);
}

guint
gncal_full_day_get_type (void)
{
	static guint full_day_type = 0;

	if (!full_day_type) {
		GtkTypeInfo full_day_info = {
			"GncalFullDay",
			sizeof (GncalFullDay),
			sizeof (GncalFullDayClass),
			(GtkClassInitFunc) gncal_full_day_class_init,
			(GtkObjectInitFunc) gncal_full_day_init,
			(GtkArgSetFunc) NULL,
			(GtkArgGetFunc) NULL
		};

		full_day_type = gtk_type_unique (gtk_container_get_type (), &full_day_info);
	}

	return full_day_type;
}

static void
gncal_full_day_class_init (GncalFullDayClass *class)
{
	GtkObjectClass    *object_class;
	GtkWidgetClass    *widget_class;
	GtkContainerClass *container_class;

	object_class = (GtkObjectClass *) class;
	widget_class = (GtkWidgetClass *) class;
	container_class = (GtkContainerClass *) class;

	parent_class = gtk_type_class (gtk_container_get_type ());

	fullday_signals[RANGE_ACTIVATED] =
		gtk_signal_new ("range_activated",
				GTK_RUN_LAST,
				object_class->type,
				GTK_SIGNAL_OFFSET (GncalFullDayClass, range_activated),
				gtk_signal_default_marshaller,
				GTK_TYPE_NONE, 0);

	gtk_object_class_add_signals (object_class, fullday_signals, LAST_SIGNAL);

	object_class->destroy = gncal_full_day_destroy;

	widget_class->map = gncal_full_day_map;
	widget_class->unmap = gncal_full_day_unmap;
	widget_class->realize = gncal_full_day_realize;
	widget_class->unrealize = gncal_full_day_unrealize;
	widget_class->draw = gncal_full_day_draw;
	widget_class->draw_focus = gncal_full_day_draw_focus;
	widget_class->size_request = gncal_full_day_size_request;
	widget_class->size_allocate = gncal_full_day_size_allocate;
	widget_class->button_press_event = gncal_full_day_button_press;
	widget_class->button_release_event = gncal_full_day_button_release;
	widget_class->motion_notify_event = gncal_full_day_motion;
	widget_class->expose_event = gncal_full_day_expose;
	widget_class->key_press_event = gncal_full_day_key_press;
	widget_class->focus_in_event = gncal_full_day_focus_in;
	widget_class->focus_out_event = gncal_full_day_focus_out;

	container_class->foreach = gncal_full_day_foreach;

	class->range_activated = range_activated;
}

static void
gncal_full_day_init (GncalFullDay *fullday)
{
	GTK_WIDGET_UNSET_FLAGS (fullday, GTK_NO_WINDOW);
	GTK_WIDGET_SET_FLAGS (fullday, GTK_CAN_FOCUS);

	fullday->calendar = NULL;

	fullday->lower = 0;
	fullday->upper = 0;
	fullday->interval = 30; /* 30 minutes by default */

	fullday->children = NULL;
	fullday->drag_info = g_new0 (struct drag_info, 1);

	fullday->up_down_cursor = NULL;
	fullday->beam_cursor = NULL;
}

static void
gncal_full_day_destroy (GtkObject *object)
{
	GncalFullDay *fullday;
	GList *children;
	Child *child;

	g_return_if_fail (object != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (object));

	fullday = GNCAL_FULL_DAY (object);

	/* Unparent the children manually as we don't have a remove method */

	for (children = fullday->children; children; children = children->next) {
		child = children->data;

		gtk_widget_unparent (child->widget);
	}

	g_list_free (fullday->children);
	g_free (fullday->drag_info);

	if (GTK_OBJECT_CLASS (parent_class)->destroy)
		(* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
}

GtkWidget *
gncal_full_day_new (GnomeCalendar *calendar, time_t lower, time_t upper)
{
	GncalFullDay *fullday;

	g_return_val_if_fail (calendar != NULL, NULL);

	fullday = gtk_type_new (gncal_full_day_get_type ());

	fullday->calendar = calendar;

	gncal_full_day_set_bounds (fullday, lower, upper);

	return GTK_WIDGET (fullday);
}

static void
gncal_full_day_map (GtkWidget *widget)
{
	GncalFullDay *fullday;
	GList *children;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

	GTK_WIDGET_SET_FLAGS (widget, GTK_MAPPED);

	fullday = GNCAL_FULL_DAY (widget);

	gdk_window_show (widget->window);

	for (children = fullday->children; children; children = children->next)
		child_map (fullday, children->data);
}

static void
gncal_full_day_unmap (GtkWidget *widget)
{
	GncalFullDay *fullday;
	GList *children;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

	GTK_WIDGET_UNSET_FLAGS (widget, GTK_MAPPED);

	fullday = GNCAL_FULL_DAY (widget);

	gdk_window_hide (widget->window);

	for (children = fullday->children; children; children = children->next)
		child_unmap (fullday, children->data);
}

static void
gncal_full_day_realize (GtkWidget *widget)
{
	GncalFullDay *fullday;
	GdkWindowAttr attributes;
	gint attributes_mask;
	GList *children;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

	GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);

	fullday = GNCAL_FULL_DAY (widget);

	attributes.window_type = GDK_WINDOW_CHILD;
	attributes.x = widget->allocation.x;
	attributes.y = widget->allocation.y;
	attributes.width = widget->allocation.width;
	attributes.height = widget->allocation.height;
	attributes.wclass = GDK_INPUT_OUTPUT;
	attributes.visual = gtk_widget_get_visual (widget);
	attributes.colormap = gtk_widget_get_colormap (widget);
	attributes.event_mask = (gtk_widget_get_events (widget)
				 | GDK_EXPOSURE_MASK
				 | GDK_BUTTON_PRESS_MASK
				 | GDK_BUTTON_RELEASE_MASK
				 | GDK_BUTTON_MOTION_MASK
				 | GDK_POINTER_MOTION_HINT_MASK);

	attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;

	widget->window = gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, attributes_mask);
	gdk_window_set_user_data (widget->window, widget);

	widget->style = gtk_style_attach (widget->style, widget->window);
	gdk_window_set_background (widget->window, &widget->style->bg[GTK_STATE_PRELIGHT]);

	fullday->up_down_cursor = gdk_cursor_new (GDK_DOUBLE_ARROW);
	fullday->beam_cursor    = gdk_cursor_new (GDK_XTERM);

	for (children = fullday->children; children; children = children->next)
		child_realize (fullday, children->data);
}

static void
gncal_full_day_unrealize (GtkWidget *widget)
{
	GncalFullDay *fullday;
	GList *children;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

	fullday = GNCAL_FULL_DAY (widget);

	for (children = fullday->children; children; children = children->next)
		child_unrealize (fullday, children->data);

	gdk_cursor_destroy (fullday->up_down_cursor);
	fullday->up_down_cursor = NULL;

	gdk_cursor_destroy (fullday->beam_cursor);
	fullday->beam_cursor = NULL;

	if (GTK_WIDGET_CLASS (parent_class)->unrealize)
		(* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget);
}

static void
paint_back (GncalFullDay *fullday, GdkRectangle *area)
{
	GtkWidget *widget;
	GdkRectangle rect, dest, aarea;
	struct drag_info *di;
	int x1, y1, width, height;
	int labels_width;
	int f_rows, row_height;
	int i, y;
	GdkGC *gc;
	struct tm tm;
	char buf [40];

	widget = GTK_WIDGET (fullday);

	if (!area) {
		area = &aarea;

		area->x = 0;
		area->y = 0;
		area->width = widget->allocation.width;
		area->height = widget->allocation.height;
	}

	x1 = widget->style->klass->xthickness;
	y1 = widget->style->klass->ythickness;
	width = widget->allocation.width - 2 * x1;
	height = widget->allocation.height - 2 * y1;

	di = fullday->drag_info;
	labels_width = calc_labels_width (fullday);
	row_height = calc_row_height (fullday);
	get_tm_range (fullday, fullday->lower, fullday->upper, &tm, NULL, NULL, &f_rows);

	/* Frame shadow */

	gtk_widget_draw_focus (widget);

	/* Area for labels before selection */

	rect.x = x1;
	rect.y = y1;
	rect.width = 2 * TEXT_BORDER + labels_width;

	if (di->sel_rows_used == 0)
		rect.height = height;
	else
		rect.height = row_height * di->sel_start_row;

	if (gdk_rectangle_intersect (&rect, area, &dest))
		gdk_draw_rectangle (widget->window,
				    widget->style->bg_gc[GTK_STATE_NORMAL],
				    TRUE,
				    dest.x, dest.y,
				    dest.width, dest.height);

	/* Blank area before selection */

	rect.x += rect.width + widget->style->klass->xthickness;
	rect.width = width - (rect.x - x1);

	if (gdk_rectangle_intersect (&rect, area, &dest))
		gdk_draw_rectangle (widget->window,
				    widget->style->bg_gc[GTK_STATE_PRELIGHT],
				    TRUE,
				    dest.x, dest.y,
				    dest.width, dest.height);

	/* Selection area */

	if (di->sel_rows_used != 0) {
		rect.x = x1;
		rect.y = y1 + row_height * di->sel_start_row;
		rect.width = width;
		rect.height = row_height * di->sel_rows_used;

		if (gdk_rectangle_intersect (&rect, area, &dest))
			gdk_draw_rectangle (widget->window,
					    widget->style->bg_gc[GTK_STATE_SELECTED],
					    TRUE,
					    dest.x, dest.y,
					    dest.width, dest.height);
	}

	/* Areas under selection */

	if (di->sel_rows_used != 0) {
		/* Area for labels */

		rect.x = x1;
		rect.y = y1 + row_height * (di->sel_start_row + di->sel_rows_used);
		rect.width = 2 * TEXT_BORDER + labels_width;
		rect.height = height - rect.y;

		if (gdk_rectangle_intersect (&rect, area, &dest))
			gdk_draw_rectangle (widget->window,
					    widget->style->bg_gc[GTK_STATE_NORMAL],
					    TRUE,
					    dest.x, dest.y,
					    dest.width, dest.height);

		/* Blank area */

		rect.x += rect.width + widget->style->klass->xthickness;
		rect.width = width - (rect.x - x1);
		
		if (gdk_rectangle_intersect (&rect, area, &dest))
			gdk_draw_rectangle (widget->window,
					    widget->style->bg_gc[GTK_STATE_PRELIGHT],
					    TRUE,
					    dest.x, dest.y,
					    dest.width, dest.height);
	}

	/* Vertical division */

	gtk_draw_vline (widget->style, widget->window,
			GTK_STATE_NORMAL,
			y1,
			y1 + height - 1,
			x1 + 2 * TEXT_BORDER + labels_width);

	/* Horizontal divisions */

	y = y1 + row_height - 1;

	for (i = 1; i < f_rows; i++) {
		gdk_draw_line (widget->window,
			       widget->style->black_gc,
			       x1, y,
			       x1 + width - 1, y);

		y += row_height;
	}

	/* Labels */

	y = y1 + ((row_height - 1) - (widget->style->font->ascent + widget->style->font->descent)) / 2;

	rect.x = x1;
	rect.y = y1;
	rect.width = 2 * TEXT_BORDER + labels_width;
	rect.height = row_height - 1;

	for (i = 0; i < f_rows; i++) {
		mktime (&tm);

		if (gdk_rectangle_intersect (&rect, area, &dest)) {
			if (am_pm_flag)
				strftime (buf, sizeof (buf), "%I:%M%p", &tm);
			else
				strftime (buf, sizeof (buf), "%H:%M", &tm);

			if ((di->sel_rows_used != 0)
			    && (i >= di->sel_start_row)
			    && (i < (di->sel_start_row + di->sel_rows_used)))
				gc = widget->style->fg_gc[GTK_STATE_SELECTED];
			else
				gc = widget->style->fg_gc[GTK_STATE_NORMAL];

			gdk_draw_string (widget->window,
					 widget->style->font,
					 gc,
					 x1 + TEXT_BORDER,
					 y + widget->style->font->ascent,
					 buf);
		}

		rect.y += row_height;
		y += row_height;

		tm.tm_min += fullday->interval;
	}
}

static void
paint_back_rows (GncalFullDay *fullday, int start_row, int rows_used)
{
	int row_height;
	int xthickness, ythickness;
	GtkWidget *widget;
	GdkRectangle area;

	widget = GTK_WIDGET (fullday);

	row_height = calc_row_height (fullday);

	xthickness = widget->style->klass->xthickness;
	ythickness = widget->style->klass->ythickness;

	area.x = xthickness;
	area.y = ythickness + start_row * row_height;
	area.width = widget->allocation.width - 2 * xthickness;
	area.height = rows_used * row_height;

	paint_back (fullday, &area);
}

static void
gncal_full_day_draw (GtkWidget *widget, GdkRectangle *area)
{
	GncalFullDay *fullday;
	GList *children;
	Child *child;
	GdkRectangle rect, dest;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (widget));
	g_return_if_fail (area != NULL);

	if (!GTK_WIDGET_DRAWABLE (widget))
		return;

	fullday = GNCAL_FULL_DAY (widget);

	paint_back (fullday, area);

	for (children = fullday->children; children; children = children->next) {
		child = children->data;

		rect.x = child->x;
		rect.y = child->y;
		rect.width = child->width;
		rect.height = child->height;

		if (gdk_rectangle_intersect (&rect, area, &dest)) {
			dest.x -= child->x;
			dest.y -= child->y;

			child_draw (fullday, child, &dest, TRUE);
		}
	}
}

static void
gncal_full_day_draw_focus (GtkWidget *widget)
{
	g_return_if_fail (widget != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (widget));

	if (!GTK_WIDGET_DRAWABLE (widget))
		return;

	gtk_draw_shadow (widget->style, widget->window,
			 GTK_STATE_NORMAL, GTK_SHADOW_ETCHED_IN,
			 0, 0,
			 widget->allocation.width,
			 widget->allocation.height);

	if (GTK_WIDGET_HAS_FOCUS (widget))
		gdk_draw_rectangle (widget->window,
				    widget->style->black_gc,
				    FALSE,
				    0, 0,
				    widget->allocation.width - 1,
				    widget->allocation.height - 1);
}

static void
gncal_full_day_size_request (GtkWidget *widget, GtkRequisition *requisition)
{
	GncalFullDay *fullday;
	int labels_width;
	int rows;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (widget));
	g_return_if_fail (requisition != NULL);

	fullday = GNCAL_FULL_DAY (widget);

	/* Border and min width */

	labels_width = calc_labels_width (fullday);

	requisition->width = 2 * widget->style->klass->xthickness + 4 * TEXT_BORDER + labels_width + MIN_WIDTH;
	requisition->height = 2 * widget->style->klass->ythickness;

	/* Rows */

	get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &rows);

	requisition->height += (rows * (2 * TEXT_BORDER + widget->style->font->ascent + widget->style->font->descent)
				+ (rows - 1)); /* division lines */
}

static void
gncal_full_day_size_allocate (GtkWidget *widget, GtkAllocation *allocation)
{
	GncalFullDay *fullday;

	g_return_if_fail (widget != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (widget));
	g_return_if_fail (allocation != NULL);

	widget->allocation = *allocation;

	fullday = GNCAL_FULL_DAY (widget);

	if (GTK_WIDGET_REALIZED (widget))
		gdk_window_move_resize (widget->window,
					allocation->x, allocation->y,
					allocation->width, allocation->height);

	layout_children (fullday);
}

static Child *
find_child_by_window (GncalFullDay *fullday, GdkWindow *window, int *on_text)
{
	GList *children;
	Child *child;
	GtkWidget *owner;

	*on_text = FALSE;

	gdk_window_get_user_data (window, (gpointer *) &owner);

	for (children = fullday->children; children; children = children->next) {
		child = children->data;

		if (child->window == window)
			return child;

		if (child->widget == owner) {
			*on_text = TRUE;
			return child;
		}
	}

	return NULL;
}

static void
draw_xor_rect (GncalFullDay *fullday)
{
	GtkWidget *widget;
	struct drag_info *di;
	int i;
	int row_height;
	int ythickness;

	widget = GTK_WIDGET (fullday);

	gdk_gc_set_function (widget->style->white_gc, GDK_INVERT);
	gdk_gc_set_subwindow (widget->style->white_gc, GDK_INCLUDE_INFERIORS);

	ythickness = widget->style->klass->ythickness;

	di = fullday->drag_info;

	row_height = calc_row_height (fullday);

	for (i = 0; i < XOR_RECT_WIDTH; i++)
		gdk_draw_rectangle (widget->window,
				    widget->style->white_gc,
				    FALSE,
				    di->child->x + i,
				    di->child_start_row * row_height + ythickness + i,
				    di->child->width - 2 * i - 1,
				    di->child_rows_used * row_height - 2 - 2 * i);

	gdk_gc_set_function (widget->style->white_gc, GDK_COPY);
	gdk_gc_set_subwindow (widget->style->white_gc, GDK_CLIP_BY_CHILDREN);
}

static int
get_row_from_y (GncalFullDay *fullday, int y, int round)
{
	GtkWidget *widget;
	int row_height;
	int f_rows;
	int ythickness;

	get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows);

	row_height = calc_row_height (fullday);

	widget = GTK_WIDGET (fullday);

	ythickness = widget->style->klass->ythickness;

	y -= ythickness;

	if (y < 0)
		y = 0;
	else if (y >= (f_rows * row_height))
		y = f_rows * row_height - 1;

	if (round)
		y += row_height / 2;

	y /= row_height;

	if (y > f_rows)
		y = f_rows; /* note that this is 1 more than the last row's index */

	return y;
}

static int
button_1 (GncalFullDay *fullday, GdkEventButton *event)
{
	GtkWidget *widget;
	Child *child;
	int on_text;
	struct drag_info *di;
	gint y;
	int row_height;
	int has_focus;
	int old_start_row, old_rows_used;
	int old_max;
	int paint_start_row, paint_rows_used;

	widget = GTK_WIDGET (fullday);

	if (event->window == widget->window) {
		/* Clicked on main window */

		if (!GTK_WIDGET_HAS_FOCUS (widget))
			gtk_widget_grab_focus (widget);

		/* Prepare for drag */

		di = fullday->drag_info;

		di->drag_mode = DRAG_SELECT;

		old_start_row = di->sel_start_row;
		old_rows_used = di->sel_rows_used;

		di->sel_click_row = get_row_from_y (fullday, event->y, FALSE);
		di->sel_start_row = di->sel_click_row;
		di->sel_rows_used = 1;

		di->click_time = event->time;

		gdk_pointer_grab (widget->window, FALSE,
				  (GDK_BUTTON_MOTION_MASK
				   | GDK_POINTER_MOTION_HINT_MASK
				   | GDK_BUTTON_RELEASE_MASK),
				  NULL,
				  fullday->up_down_cursor,
				  event->time);

		if (old_rows_used == 0) {
			paint_start_row = di->sel_start_row;
			paint_rows_used = di->sel_rows_used;
		} else {
			paint_start_row = MIN (old_start_row, di->sel_start_row);
			old_max = old_start_row + old_rows_used - 1;
			paint_rows_used = MAX (old_max, di->sel_start_row) - paint_start_row + 1;
		}

		paint_back_rows (fullday, paint_start_row, paint_rows_used);
	} else {
		/* Clicked on a child? */

		child = find_child_by_window (fullday, event->window, &on_text);

		if (!child || on_text || child->ico->recur)
			return FALSE;

		/* Prepare for drag */

		di = fullday->drag_info;

		gtk_widget_get_pointer (widget, NULL, &y);

		has_focus = GTK_WIDGET_HAS_FOCUS (child->widget);

		if (has_focus) {
			if (event->y < HANDLE_SIZE)
				di->drag_mode = DRAG_SIZE_TOP;
			else if (event->y >= (child->height - HANDLE_SIZE))
				di->drag_mode = DRAG_SIZE_BOTTOM;
			else
				di->drag_mode = DRAG_MOVE;
		} else
			di->drag_mode = DRAG_MOVE;

		row_height = calc_row_height (fullday);

		di->child = child;

		di->child_click_y = event->y;
		di->child_start_row = child->lower_row;
		di->child_rows_used = child->rows_used;

		gdk_pointer_grab (child->window, FALSE,
				  (GDK_BUTTON_MOTION_MASK
				   | GDK_POINTER_MOTION_HINT_MASK
				   | GDK_BUTTON_RELEASE_MASK),
				  NULL,
				  fullday->up_down_cursor,
				  event->time);

		draw_xor_rect (fullday);
	}

	return FALSE;
}

static int
button_3 (GncalFullDay *fullday, GdkEventButton *event)
{
	static struct menu_item main_items[] = {
		{ N_("New appointment..."), (GtkSignalFunc) new_appointment, NULL, TRUE }
	};

	GtkWidget *widget;
	Child *child;
	int on_text;

	widget = GTK_WIDGET (fullday);

	if (event->window == widget->window) {
		/* Clicked on main window */

		if (!GTK_WIDGET_HAS_FOCUS (widget))
			gtk_widget_grab_focus (widget);

		main_items[0].data = fullday;

		popup_menu (main_items, sizeof (main_items) / sizeof (main_items[0]), event->time);
	} else {
		child = find_child_by_window (fullday, event->window, &on_text);

		if (!child || on_text)
			return FALSE;

		child_popup_menu (fullday, child, event->time);
		return TRUE;
	}

	return FALSE;
}

static gint
gncal_full_day_button_press (GtkWidget *widget, GdkEventButton *event)
{
	GncalFullDay *fullday;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	fullday = GNCAL_FULL_DAY (widget);

	switch (event->button) {
	case 1:
		return button_1 (fullday, event);

	case 3:
		return button_3 (fullday, event);

	default:
		break;
	}

	return FALSE;
}

static void
recompute_motion (GncalFullDay *fullday, int y)
{
	struct drag_info *di;
	int f_rows;
	int row;
	int has_focus;

	di = fullday->drag_info;

	get_tm_range (fullday, fullday->lower, fullday->upper, NULL, NULL, NULL, &f_rows);

	switch (di->drag_mode) {
	case DRAG_SELECT:
		row = get_row_from_y (fullday, y, FALSE);

		if (row >= f_rows)
			row = f_rows - 1;

		if (row < di->sel_click_row) {
			di->sel_start_row = row;
			di->sel_rows_used = di->sel_click_row - row + 1;
		} else {
			di->sel_start_row = di->sel_click_row;
			di->sel_rows_used = row - di->sel_start_row + 1;
		}

		break;

	case DRAG_MOVE:
		has_focus = GTK_WIDGET_HAS_FOCUS (di->child->widget);

		row = get_row_from_y (fullday, y - di->child_click_y + (has_focus ? HANDLE_SIZE : 0), TRUE);

		if (row > (f_rows - di->child_rows_used))
			row = f_rows - di->child_rows_used;

		di->child_start_row = row;

		break;

	case DRAG_SIZE_TOP:
		row = get_row_from_y (fullday, y + HANDLE_SIZE, TRUE);

		if (row > (di->child_start_row + di->child_rows_used - 1))
			row = di->child_start_row + di->child_rows_used - 1;

		di->child_rows_used = (di->child_start_row + di->child_rows_used) - row;
		di->child_start_row = row;

		break;

	case DRAG_SIZE_BOTTOM:
		row = get_row_from_y (fullday, y - HANDLE_SIZE, TRUE);

		if (row <= di->child_start_row)
			row = di->child_start_row + 1;
		else if (row > f_rows)
			row = f_rows;

		di->child_rows_used = row - di->child_start_row;

		break;

	default:
		g_assert_not_reached ();
	}
}

static void
get_time_from_rows (GncalFullDay *fullday, int start_row, int rows_used, time_t *t_lower, time_t *t_upper)
{
	struct tm tm;
	int row_height;

	get_tm_range (fullday, fullday->lower, fullday->upper, &tm, NULL, NULL, NULL);

	row_height = calc_row_height (fullday);

	tm.tm_min += fullday->interval * start_row;
	*t_lower = mktime (&tm);

	tm.tm_min += fullday->interval * rows_used;
	*t_upper = mktime (&tm);
}

static void
update_from_drag_info (GncalFullDay *fullday)
{
	struct drag_info *di;
	GtkWidget *widget;

	di = fullday->drag_info;

	widget = GTK_WIDGET (fullday);

	get_time_from_rows (fullday, di->child_start_row, di->child_rows_used,
			    &di->child->ico->dtstart,
			    &di->child->ico->dtend);

	child_range_changed (fullday, di->child);

	/* Notify calendar of change */

	gnome_calendar_object_changed (fullday->calendar, di->child->ico, CHANGE_DATES);
}

static gint
gncal_full_day_button_release (GtkWidget *widget, GdkEventButton *event)
{
	GncalFullDay *fullday;
	struct drag_info *di;
	gint y;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	fullday = GNCAL_FULL_DAY (widget);

	di = fullday->drag_info;

	gtk_widget_get_pointer (widget, NULL, &y);

	switch (di->drag_mode) {
	case DRAG_NONE:
		return FALSE;

	case DRAG_SELECT:
		if ((event->time - di->click_time) < UNSELECT_TIMEOUT)
			di->sel_rows_used = 0;
		else
			recompute_motion (fullday, y);

		gdk_pointer_ungrab (event->time);

		paint_back_rows (fullday, di->sel_start_row, MAX (di->sel_rows_used, 1));

		break;

	case DRAG_MOVE:
	case DRAG_SIZE_TOP:
	case DRAG_SIZE_BOTTOM:
		draw_xor_rect (fullday);
		recompute_motion (fullday, y);
		gdk_pointer_ungrab (event->time);

		update_from_drag_info (fullday);

		di->child_rows_used = 0;

		break;

	default:
		g_assert_not_reached ();
	}

	di->drag_mode = DRAG_NONE;
	di->child = NULL;

	return FALSE;
}

static gint
gncal_full_day_motion (GtkWidget *widget, GdkEventMotion *event)
{
	GncalFullDay *fullday;
	struct drag_info *di;
	gint y;
	int old_min, old_max;
	int new_min, new_max;
	int new_start_row, new_rows_used;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	fullday = GNCAL_FULL_DAY (widget);
	di = fullday->drag_info;

	gtk_widget_get_pointer (widget, NULL, &y);
	
	switch (di->drag_mode) {
	case DRAG_NONE:
		break;

	case DRAG_SELECT:
		old_min = di->sel_start_row;
		old_max = di->sel_start_row + di->sel_rows_used - 1;

		recompute_motion (fullday, y);

		new_min = di->sel_start_row;
		new_max = di->sel_start_row + di->sel_rows_used - 1;

		new_start_row = MIN (old_min, new_min);
		new_rows_used = MAX (old_max, new_max) - new_start_row + 1;

		paint_back_rows (fullday, new_start_row, new_rows_used);

		break;

	case DRAG_MOVE:
	case DRAG_SIZE_TOP:
	case DRAG_SIZE_BOTTOM:
		draw_xor_rect (fullday);
		recompute_motion (fullday, y);
		draw_xor_rect (fullday);

		break;

	default:
		g_assert_not_reached ();
	}

	return FALSE;
}

static gint
gncal_full_day_expose (GtkWidget *widget, GdkEventExpose *event)
{
	GncalFullDay *fullday;
	Child *child;
	int on_text;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	if (!GTK_WIDGET_DRAWABLE (widget))
		return FALSE;

	fullday = GNCAL_FULL_DAY (widget);

	if (event->window == widget->window)
		paint_back (fullday, &event->area);
	else {
		child = find_child_by_window (fullday, event->window, &on_text);

		if (child && !on_text)
			child_draw (fullday, child, &event->area, FALSE);
	}

	return FALSE;
}

static gint
gncal_full_day_key_press (GtkWidget *widget, GdkEventKey *event)
{
	GncalFullDay *fullday;
	struct drag_info *di;

	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	fullday = GNCAL_FULL_DAY (widget);

	di = fullday->drag_info;

	if (di->sel_rows_used == 0)
		return FALSE;

	if (event->keyval == GDK_Return) {
		gtk_signal_emit (GTK_OBJECT (fullday), fullday_signals [RANGE_ACTIVATED]);
		return TRUE;
	}

	return FALSE;
}

static gint
gncal_full_day_focus_in (GtkWidget *widget, GdkEventFocus *event)
{
	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	GTK_WIDGET_SET_FLAGS (widget, GTK_HAS_FOCUS);
	gtk_widget_draw_focus (widget);

	return FALSE;
}

static gint
gncal_full_day_focus_out (GtkWidget *widget, GdkEventFocus *event)
{
	g_return_val_if_fail (widget != NULL, FALSE);
	g_return_val_if_fail (GNCAL_IS_FULL_DAY (widget), FALSE);
	g_return_val_if_fail (event != NULL, FALSE);

	GTK_WIDGET_UNSET_FLAGS (widget, GTK_HAS_FOCUS);
	gtk_widget_draw_focus (widget);

	return FALSE;
}

static void
gncal_full_day_foreach (GtkContainer *container, GtkCallback callback, gpointer callback_data)
{
	GncalFullDay *fullday;
	GList *children;
	Child *child;

	g_return_if_fail (container != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (container));
	g_return_if_fail (callback != NULL);

	fullday = GNCAL_FULL_DAY (container);

	for (children = fullday->children; children; children = children->next) {
		child = children->data;

		(*callback) (child->widget, callback_data);
	}
}

static gint
child_compare_by_start (gpointer a, gpointer b)
{
	Child *ca = a;
	Child *cb = b;
	time_t diff;
	
	diff = ca->start - cb->start;
	return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}

static int
fullday_add_children (iCalObject *obj, time_t start, time_t end, void *c)
{
	GncalFullDay *fullday = c;
	Child *child;
	
	child = child_new (fullday, start, end, obj);
	fullday->children = g_list_insert_sorted (fullday->children, child, child_compare_by_start);

	return 1;
}

void
gncal_full_day_update (GncalFullDay *fullday, iCalObject *ico, int flags)
{
	GList *children;
	Child *child;
	
	g_return_if_fail (fullday != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (fullday));

	if (!fullday->calendar->cal)
		return;

	/* Try to find child that changed */

	for (children = fullday->children; children; children = children->next) {
		child = children->data;

		if (child->ico == ico)
			break;
	}

	/* If child was found and nothing but the summary changed, we can just paint the child and return */

	if (children && !(flags & ~CHANGE_SUMMARY)) {
		child_draw (fullday, child, NULL, TRUE);
		return;
	}

	/* We have to regenerate and layout our list of children */

	for (children = fullday->children; children; children = children->next)
		child_destroy (fullday, children->data);

	g_list_free (fullday->children);

	fullday->children = NULL;
	
	calendar_iterate (fullday->calendar->cal,
			  fullday->lower,
			  fullday->upper,
			  fullday_add_children,
			  fullday);
	
	layout_children (fullday);

	/* Realize and map children */

	for (children = fullday->children; children; children = children->next) {
		if (GTK_WIDGET_REALIZED (fullday))
			child_realize (fullday, children->data);

		if (GTK_WIDGET_MAPPED (fullday))
			child_map (fullday, children->data);
	}

	gtk_widget_draw (GTK_WIDGET (fullday), NULL);
}

void
gncal_full_day_set_bounds (GncalFullDay *fullday, time_t lower, time_t upper)
{
	struct drag_info *di;

	g_return_if_fail (fullday != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (fullday));

	if ((lower != fullday->lower) || (upper != fullday->upper)) {
		fullday->lower = lower;
		fullday->upper = upper;

		di = fullday->drag_info;

		di->sel_rows_used = 0; /* clear selection */

		gncal_full_day_update (fullday, NULL, 0);
	}
}

int
gncal_full_day_selection_range (GncalFullDay *fullday, time_t *lower, time_t *upper)
{
	struct drag_info *di;
	time_t alower, aupper;

	g_return_val_if_fail (fullday != NULL, FALSE);
	g_return_val_if_fail (GNCAL_IS_FULL_DAY (fullday), FALSE);

	di = fullday->drag_info;

	if (di->sel_rows_used == 0){
		time_t now = time (NULL);
		struct tm tm    = *localtime (&now);
		struct tm thisd = *localtime (&fullday->lower);
		
		thisd.tm_hour = tm.tm_hour;
		thisd.tm_min  = tm.tm_min;
		thisd.tm_sec  = 0;
		*lower = mktime (&thisd);
		thisd.tm_hour++;
		*upper = mktime (&thisd);
		return FALSE;
	}

	get_time_from_rows (fullday, di->sel_start_row, di->sel_rows_used, &alower, &aupper);

	if (lower)
		*lower = alower;

	if (upper)
		*upper= aupper;

	return TRUE;
}

void
gncal_full_day_focus_child (GncalFullDay *fullday, iCalObject *ico)
{
	GList *children;
	Child *child;
	GdkEvent event;

	g_return_if_fail (fullday != NULL);
	g_return_if_fail (ico != NULL);

	for (children = fullday->children; children; children = children->next) {
		child = children->data;

		if (child->ico == ico) {
			gtk_widget_grab_focus (child->widget);

			/* We synthesize a click because GtkText will not set the cursor and
			 * the user will not be able to type-- this has to be fixed in
			 * GtkText.  */

			memset (&event, 0, sizeof (event));

			event.type = GDK_BUTTON_PRESS;
			event.button.window = child->widget->window;
			event.button.time = GDK_CURRENT_TIME;
			event.button.x = 0;
			event.button.y = 0;
			event.button.button = 1;

			gtk_widget_event (child->widget, &event);

			break;
		}
	}
}

static void
range_activated (GncalFullDay *fullday)
{
	struct drag_info *di;

	g_return_if_fail (fullday != NULL);
	g_return_if_fail (GNCAL_IS_FULL_DAY (fullday));

	di = fullday->drag_info;

	/* Remove selection; at this point someone should already have added an appointment */

	di->sel_rows_used = 0;

	paint_back (fullday, NULL);
}