/* -*- mode:C; c-file-style:"linux"; tab-width:8; -*- */
/* 
 *  DatesView - An electronic calendar widget optimised for embedded devices.
 *
 *  Principal author	: Chris Lord <chris@o-hand.com>
 *  Maemo port		: Tomas Frydrych <tf@o-hand.com>
 *
 *  Copyright (c) 2005 - 2006 OpenedHand Ltd - http://o-hand.com
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, 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.
 *
 */

#include "config.h"
#include <time.h>
#include <sys/time.h>
#include <nl_types.h>
#include <langinfo.h>
#include <math.h>
#include <string.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <pango/pango.h>
#include <gtk/gtk.h>
#include <gdk/gdkx.h>
#include <gdk/gdkkeysyms.h>
#include <libecal/e-cal.h>
#include <libecal/e-cal-util.h>
#include <libecal/e-cal-time-util.h>
#include <libical/icaltime.h>
#include <libical/icalperiod.h>
#include <libical/icalcomponent.h>
#include <gconf/gconf-client.h>
#include "dates_view.h"

#define CALENDAR_GCONF_PREFIX   "/apps/evolution/calendar"
#define CALENDAR_GCONF_TIMEZONE CALENDAR_GCONF_PREFIX "/display/timezone"
#define CALENDAR_GCONF_24H CALENDAR_GCONF_PREFIX "/display/use_24hour_format"
#define CALENDAR_GCONF_SOURCES CALENDAR_GCONF_PREFIX "/sources"

/* Macros to give the day of the week, taking into account a week start */
#define DOWSTART(d,m,y,o) ((7 + time_day_of_week ((d),(m)-1,(y)) - (o)) % 7)
#define IDOWSTART(d,o) (((7 + (icaltime_day_of_week (d)-1) - (o)) % 7)+1)

#define _PANGO_FONT_STRING(family, pts) \
  #family ", " #pts

#define _PANGO_FONT_STRING_INT(family, pts) \
  #family ", " #pts ".0"

#if defined(WITH_HILDON) || defined(WITH_OMOKO)
#	define DEFAULT_FONT_STRING _PANGO_FONT_STRING(sans, 11.5)
#endif

#define BORDER 4
#define TEETH 6
#define GRIPS 5
#ifndef FRAMES
#	define FRAMES 10
#endif
#define DATES_VIEW_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), \
				     DATES_TYPE_VIEW, DatesViewPrivate))

#ifdef DEBUG
typedef enum {
	DATES_VIEW_DEBUG_DRAW	= 1 << 0,
	DATES_VIEW_DEBUG_EDIT	= 1 << 1,
	DATES_VIEW_DEBUG_FIT	= 1 << 2,
	DATES_VIEW_DEBUG_QUERY	= 1 << 3,
	DATES_VIEW_DEBUG_DND	= 1 << 4,
} DatesViewDebugFlag;

static const GDebugKey dates_view_debug_keys[] = {
	{ "draw", DATES_VIEW_DEBUG_DRAW },
	{ "edit", DATES_VIEW_DEBUG_EDIT },
	{ "fit", DATES_VIEW_DEBUG_FIT },
	{ "query", DATES_VIEW_DEBUG_QUERY },
	{ "dnd", DATES_VIEW_DEBUG_DND },
};
#endif

typedef struct
{
	ECal *ecal;
	ECalView *calview;
	GdkGC *gc;
	GdkGC *dark_gc;
	GdkGC *text_gc;
} DatesViewCalendar;

typedef struct {
	DatesView *parent;
	DatesViewCalendar *cal;
	ECalComponent *comp;
	gchar *uri_uid;
	GList *draw_data;
	PangoLayout *summary;
	PangoLayout *details;
	GList *list_refs;	/* List of lists the event is in */
} DatesViewEvent;

/* Data describing particular instances of an event across days/recurrences */
typedef struct {
	DatesViewEvent *parent;
	gint position;		/* Position of event in table */
	gint width;		/* Row width */
	gint span;		/* Cel-span */
	PangoLayout *layout;	/* Layout representing time for list-view */
	struct icaltimetype date;
	time_t abs_start;	/* Start of instance of event */
	time_t abs_end;		/* End of instance of event */
	time_t start;		/* Start of displayed part of event */
	time_t end;		/* End of displayed part of event */
	gboolean first;		/* If it's the first day */
	gboolean last;		/* If it's the last day */
	GdkRectangle *rect;
} DatesViewEventData;

typedef struct
{
	gint day;
	GdkRectangle rect;
} DatesViewDayRegion;

typedef struct
{
	GdkRectangle *mrect;
	GList *drects;
} DatesViewRegion;

typedef enum {
	DATES_VIEW_NONE 	= 0,
	DATES_VIEW_MOVE 	= 1 << 0,
	DATES_VIEW_SIZE 	= 1 << 1,
	DATES_VIEW_PICK 	= 1 << 2,
	DATES_VIEW_SEL 		= 1 << 3,
} DatesViewOperation;

typedef struct
{
	/* Variables for holding date information */
	guint hours;
	guint days;
	guint months;
	guint months_in_row;
	icaltimetype *date;
	icaltimezone *zone;
	gchar *zone_s;
	icaltimezone *utc;
	icaltimetype now;
	icaltimetype start;	/* Start of visible event span */
	icaltimetype end;	/* End of visible event span */
	icaltimetype cstart;	/* Start of visible calendar span */
	icaltimetype cend;	/* End of visible calendar span */
	guint week_start;
	
	/* Variables for user interaction */
	DatesViewRegion regions[12];
	gboolean read_only;
	DatesViewEventData *selected_event;
	gboolean unselected;
	DatesViewOperation operation;
	/* Variables for moving/sizing events */
	gint initial_x;		/* Where the mouse was when it started a drag */
	gint initial_y;
	gint current_x;		/* Where it is */
	gint current_y;
	gint prev_x;		/* Where it was last frame */
	gint prev_y;
	gdouble offset_x;	/* Where it was initially in respect to the */
	gdouble offset_y;	/* event rectangle, as a percentage of the */
				/* event rectangle width */
	gboolean first_move;
#ifdef WITH_DND
	gboolean drag_source;	/* If the drag originated from this app */
	guint motion_idle;	/* Don't lag when using DND for motion events */
#endif
	/**/
	gdouble start_select;
	gdouble end_select;
	guint select_day;
	GdkCursor *lr_cursor;
	GdkCursor *move_cursor;
	gboolean dragbox;
	gboolean single_click;
	gboolean double_click;
	guint snap;
	gboolean use_list;
	gboolean use_24h;
	
	/* Variables for rendering */
	PangoLayout *date_layouts[31];
	PangoLayout *bdate_layouts[31];
	PangoLayout *time_layouts[23];
	PangoLayout *month_layouts[12];
#ifndef DATES_ABREV_DAY_LABELS
	PangoLayout *day_layouts[7];
#endif
	PangoLayout *abday_layouts[7];
	gint time_layouts_height;
	PangoFontDescription *font;
  
	gboolean draw_selected;
	gboolean done;
	GdkPixbuf *recur_pixbuf;
	GdkPixbuf *alarm_pixbuf;

	/* Widgets */
	GtkWidget *main;
	GtkWidget *side;
	GtkWidget *top;
	GtkAdjustment *adjust;

	/* Variables for holding calendar and event information */
	GList *events;
	GConfClient *gconf_client;
	GList *calendars;
	GList *event_days[12][31];
	
	/* GC/Colours */
	GdkGC *gc;
	GdkColor grey[3];
	GdkColor red[3];
	GdkColor green[3];
	GdkColor blue[3];
	
	/* For debugging */
	guint debug;

	/* Scrollable: Whether the pane should be scrollable. */
	gboolean scrollable;

	/*
	 * Max rectangle for a day number. Used for calculating the variables
         * below.
	 */
	PangoRectangle max_rect;

	/* The width and height for the rectangle in the corner */
	int corner_width, corner_height; 

	/* Pixmap for the stippled effect */
	GdkPixmap *stipple_pixmap;

	/* Whether to show the Marcu Bains line or not */
	gboolean show_marcus_bains;

	/* Pixmaps containing pre-drawn corner boxes */
	GdkPixmap *date_pixmaps[31];
}
DatesViewPrivate;

#ifdef WITH_DND
static const GtkTargetEntry dates_view_drag_targets[] = {
	{ "application/x-e-calendar-event", 0, 0 },
	{ "text/x-calendar", 0, 0 },
	{ "text/calendar", 0, 0 }
};
#endif

enum {
	DATE_CHANGED,
	EVENT_SELECTED,
	EVENT_MOVED,
	EVENT_SIZED,
	COMMIT_EVENT,
	EVENT_ACTIVATED,
	ICAL_DROP,
	LAST_SIGNAL
};

enum {
	PROP_HOURS = 1,
	PROP_DAYS,
	PROP_MONTHS,
	PROP_MONTHS_ROW,
	PROP_DATE,
	PROP_WEEK_START,
	PROP_READONLY,
	PROP_USE_DRAGBOX,
	PROP_SINGLE_CLICK,
	PROP_SNAP,
	PROP_USE_LIST,
	PROP_USE_24H
};

static guint signals[LAST_SIGNAL] = { 0 };

const static gchar stipple_bits[] = {
	0x11, 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22 };

static void	dates_view_class_init	(DatesViewClass	*klass);
static void	dates_view_init		(DatesView	*view);
static void	dates_view_set_property	(GObject 	*object,
					 guint		prop_id,
					 const GValue	*value,
					 GParamSpec 	*pspec);
static void	dates_view_get_property	(GObject	*object,
					 guint		prop_id,
					 GValue		*value,
					 GParamSpec	*pspec);
static void	dates_view_finalize	(GObject	*object);
static void	dates_view_realize	(GtkWidget	*widget);

static gboolean	dates_view_main_expose	(GtkWidget	*widget,
						 GdkEventExpose	*event,
						 DatesView	*view);
static gboolean	dates_view_main_button_press (GtkWidget	*widget,
						 GdkEventButton	*event,
						 DatesView	*view);
static gboolean	dates_view_main_button_release (GtkWidget *widget,
						 GdkEventButton	*event,
						 DatesView	*view);
static gboolean dates_view_main_motion_notify	(GtkWidget	*widget,
						 GdkEventMotion	*event,
						 DatesView	*view);
static gboolean	dates_view_main_scroll_event (GtkWidget *widget,
						 GdkEventScroll	*event,
						 DatesView	*view);
static gboolean	dates_view_top_expose	(GtkWidget	*widget,
						 GdkEventExpose	*event,
						 DatesView	*view);
static gboolean	dates_view_side_expose	(GtkWidget	*widget,
						 GdkEventExpose	*event,
						 DatesView	*view);
static gboolean	dates_view_key_press	(GtkWidget	*widget,
						 GdkEventKey	*event,
						 DatesView	*view);

static void	dates_view_scroll_value_changed	(GtkAdjustment	*adjust,
						 DatesView	*view);
#ifdef WITH_DND
static gboolean	dates_view_drag_drop 	(GtkWidget 	*widget,
						 GdkDragContext *dc,
						 gint 		x,
						 gint 		y,
						 guint 		t,
						 DatesView 	*view);
static void		dates_view_drag_motion 	(GtkWidget 	*widget,
						 GdkDragContext *dc,
						 gint 		x,
						 gint 		y,
						 guint 		t,
						 DatesView 	*view);
static void		dates_view_drag_data_received	(GtkWidget 	*widget,
							 GdkDragContext	*dc,
							 gint 		x,
							 gint 		y,
							 GtkSelectionData 
							    *selection_data,
							 guint 		info,
							 guint 		t,
							 DatesView 	*view);
static void		dates_view_drag_data_get	(GtkWidget *widget,
							 GdkDragContext *dc,
							 GtkSelectionData *
							 	data,
							 guint info,
							 guint time,
							 DatesView *view);
static void		dates_view_drag_end	(GtkWidget 	*widget,
						 GdkDragContext	*dc,
						 DatesView 	*view);
#endif

static void	dates_view_set_scroll_adjustments	(DatesView *view, 
							 GtkAdjustment *hadjustment,
							 GtkAdjustment *vadjustment);

static void	dates_view_update_scrollability		(DatesView *view);

static GtkWidgetClass *parent_class = NULL;

G_DEFINE_TYPE(DatesView, dates_view, GTK_TYPE_TABLE);

/* 
 * Copy and pasted from GTK+: gtk/gtkmarshalers.c
 * Needed for setting up the signal used for the controlling the scrolling
 * functionality
 */
 
#define g_marshal_value_peek_object(v)   g_value_get_object (v)

void
_gtk_marshal_VOID__OBJECT_OBJECT (GClosure     *closure,
                                  GValue       *return_value,
                                  guint         n_param_values,
                                  const GValue *param_values,
                                  gpointer      invocation_hint,
                                  gpointer      marshal_data)
{
  typedef void (*GMarshalFunc_VOID__OBJECT_OBJECT) (gpointer     data1,
                                                    gpointer     arg_1,
                                                    gpointer     arg_2,
                                                    gpointer     data2);
  register GMarshalFunc_VOID__OBJECT_OBJECT callback;
  register GCClosure *cc = (GCClosure*) closure;
  register gpointer data1, data2;

  g_return_if_fail (n_param_values == 3);

  if (G_CCLOSURE_SWAP_DATA (closure))
    {
      data1 = closure->data;
      data2 = g_value_peek_pointer (param_values + 0);
    }
  else
    {
      data1 = g_value_peek_pointer (param_values + 0);
      data2 = closure->data;
    }
  callback = (GMarshalFunc_VOID__OBJECT_OBJECT) (marshal_data ? marshal_data : cc->callback);

  callback (data1,
            g_marshal_value_peek_object (param_values + 1),
            g_marshal_value_peek_object (param_values + 2),
            data2);
}

/* End copy and paste code */

static void
dates_view_class_init (DatesViewClass *class)
{
	GObjectClass *gobject_class = G_OBJECT_CLASS (class);
/*	GtkObjectClass *object_class = GTK_OBJECT_CLASS (class);*/
	GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class);

	parent_class = g_type_class_peek_parent (class);

	gobject_class->set_property = dates_view_set_property;
	gobject_class->get_property = dates_view_get_property;
	gobject_class->finalize = dates_view_finalize;
	widget_class->realize = dates_view_realize;

	g_object_class_install_property (
		gobject_class,
		PROP_HOURS,
		g_param_spec_uint (
			"hours",
			("Hours"),
			("The number of hours to display"),
			1, 24, 24,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_DAYS,
		g_param_spec_uint (
			"days",
			("Days"),
			("The number of days to display"),
			1, 7, 1,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_MONTHS,
		g_param_spec_uint (
			"months",
			("Months"),
			("The number of months to display"),
			0, 12, 0,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_MONTHS_ROW,
		g_param_spec_uint (
			"months_in_row",
			("Months in a row"),
			("The number of months to display in a row before "
			 "starting a new row"),
			1, 12, 4,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_DATE,
		g_param_spec_pointer (
			"date",
			("Selected date"),
			("The currently active date"),
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_WEEK_START,
		g_param_spec_uint (
			"week_start",
			("Week start"),
			("Day the week should begin on"),
			0, 6, 1,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_READONLY,
		g_param_spec_boolean (
			"readonly",
			("Read-only"),
			("Whether the view should disallow event editing"),
			FALSE,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_USE_DRAGBOX,
		g_param_spec_boolean (
			"use_dragbox",
			("Use drag-box"),
			("Whether the creating new events should be possible "
			 "using a 'drag-box'."),
			TRUE,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_SINGLE_CLICK,
		g_param_spec_boolean (
			"single_click",
			("Single-click"),
			("Whether to enable single-click event activation."),
			TRUE,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_SNAP,
		g_param_spec_uint (
			"snap",
			("Snap"),
			("The minutes to snap to when adjusting events"),
			0, 60, 30,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_USE_LIST,
		g_param_spec_boolean (
			"use_list",
			("Use list-view"),
			("Whether to use the list-view."),
			FALSE,
			G_PARAM_READWRITE));
	g_object_class_install_property (
		gobject_class,
		PROP_USE_24H,
		g_param_spec_boolean (
			"use_24h",
			("Use 24-hour time"),
			("Whether to use 24-hour time."),
			TRUE,
			G_PARAM_READWRITE));

	signals[DATE_CHANGED] =
		g_signal_new ("date_changed",
			G_OBJECT_CLASS_TYPE (gobject_class),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (DatesViewClass, date_changed),
			NULL, NULL,
			g_cclosure_marshal_VOID__VOID,
			G_TYPE_NONE, 0);
	signals[EVENT_SELECTED] =
		g_signal_new ("event_selected",
			G_OBJECT_CLASS_TYPE (gobject_class),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (DatesViewClass, event_selected),
			NULL, NULL,
			g_cclosure_marshal_VOID__VOID,
			G_TYPE_NONE, 0);
	signals[EVENT_MOVED] =
		g_signal_new ("event_moved",
			G_OBJECT_CLASS_TYPE (gobject_class),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (DatesViewClass, event_moved),
			NULL, NULL,
			g_cclosure_marshal_VOID__OBJECT,
			G_TYPE_NONE, 1, G_TYPE_OBJECT);
	signals[EVENT_SIZED] =
		g_signal_new ("event_sized",
			G_OBJECT_CLASS_TYPE (gobject_class),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (DatesViewClass, event_sized),
			NULL, NULL,
			g_cclosure_marshal_VOID__OBJECT,
			G_TYPE_NONE, 1, G_TYPE_OBJECT);
	signals[COMMIT_EVENT] =
		g_signal_new ("commit_event",
			G_OBJECT_CLASS_TYPE (gobject_class),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (DatesViewClass, commit_event),
			NULL, NULL,
			g_cclosure_marshal_VOID__VOID,
			G_TYPE_NONE, 0);
	signals[EVENT_ACTIVATED] =
		g_signal_new ("event_activated",
			G_OBJECT_CLASS_TYPE (gobject_class),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (DatesViewClass, event_activated),
			NULL, NULL,
			g_cclosure_marshal_VOID__VOID,
			G_TYPE_NONE, 0);
	signals[ICAL_DROP] =
		g_signal_new ("ical_drop",
			G_OBJECT_CLASS_TYPE (gobject_class),
			G_SIGNAL_RUN_LAST,
			G_STRUCT_OFFSET (DatesViewClass, ical_drop),
			NULL, NULL,
			g_cclosure_marshal_VOID__STRING,
			G_TYPE_NONE, 1, G_TYPE_STRING);
	
	/* 
	 * Set the set_scroll_adjustments signal of GtkWidget class to put to
	 * our set_scroll_adjustments implementation. This will allow us to
	 * make the widget scrollable (so that it can be embedded inside a
	 * GtkScrolledWindow
	 */
	widget_class->set_scroll_adjustments_signal = 
          g_signal_new (("set_scroll_adjustments"),
		  G_OBJECT_CLASS_TYPE (gobject_class),
		  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
		  G_STRUCT_OFFSET (DatesViewClass, set_scroll_adjustments),
		  NULL, NULL,
		  _gtk_marshal_VOID__OBJECT_OBJECT,
		  G_TYPE_NONE, 2,
		  GTK_TYPE_ADJUSTMENT,
		  GTK_TYPE_ADJUSTMENT);

        class->set_scroll_adjustments = dates_view_set_scroll_adjustments;

	g_type_class_add_private (class, sizeof (DatesViewPrivate));
}

static void 
dates_view_set_property (GObject *object, guint prop_id,
			 const GValue *value, GParamSpec *pspec)
{
	DatesView *view;

	view = DATES_VIEW (object);

	switch (prop_id)
	{
		case PROP_HOURS:
			dates_view_set_visible_hours (
				view, g_value_get_uint (value));
			break;
		case PROP_DAYS:
			dates_view_set_visible_days (
				view, g_value_get_uint (value));
			break;
		case PROP_MONTHS:
			dates_view_set_visible_months (
				view, g_value_get_uint (value));
			break;
		case PROP_MONTHS_ROW:
			dates_view_set_months_in_row (
				view, g_value_get_uint (value));
			break;
		case PROP_DATE:
			dates_view_set_date (
				view, g_value_get_pointer (value));
			break;
		case PROP_WEEK_START:
			dates_view_set_week_start (
				view, g_value_get_uint (value));
			break;
		case PROP_READONLY:
			dates_view_set_read_only (
				view, g_value_get_boolean (value));
			break;
		case PROP_USE_DRAGBOX:
			dates_view_set_use_dragbox (
				view, g_value_get_boolean (value));
			break;
		case PROP_SINGLE_CLICK:
			dates_view_set_single_click (
				view, g_value_get_boolean (value));
			break;
		case PROP_SNAP:
			dates_view_set_snap (
				view, g_value_get_uint (value));
			break;
		case PROP_USE_LIST:
			dates_view_set_use_list (
				view, g_value_get_boolean (value));
			break;
		case PROP_USE_24H:
			dates_view_set_use_24h (
				view, g_value_get_boolean (value));
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (
				object, prop_id, pspec);
			break;
	}
}

static void
dates_view_get_property (GObject *object, guint prop_id,
			 GValue *value, GParamSpec *pspec)
{
	DatesView *view;
	DatesViewPrivate *priv;

	view = DATES_VIEW (object);
	priv = DATES_VIEW_GET_PRIVATE (view);

	switch (prop_id)
	{
		case PROP_HOURS:
			g_value_set_uint (value, priv->hours);
			break;
		case PROP_DAYS:
			g_value_set_uint (value, priv->days);
			break;
		case PROP_MONTHS:
			g_value_set_uint (value, priv->months);
			break;
		case PROP_MONTHS_ROW:
			g_value_set_uint (value, priv->months_in_row);
			break;
		case PROP_DATE:
			g_value_set_pointer (value, priv->date);
			break;
		case PROP_WEEK_START:
			g_value_set_uint (value, priv->week_start);
			break;
		case PROP_READONLY:
			g_value_set_boolean (value, priv->read_only);
			break;
		case PROP_USE_DRAGBOX:
			g_value_set_boolean (value, priv->dragbox);
			break;
		case PROP_SINGLE_CLICK:
			g_value_set_boolean (value, priv->single_click);
			break;
		case PROP_SNAP:
			g_value_set_uint (value, priv->snap);
			break;
		case PROP_USE_LIST:
			g_value_set_boolean (value, priv->use_list);
			break;
		case PROP_USE_24H:
			g_value_set_boolean (value, priv->use_24h);
			break;
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (
				object, prop_id, pspec);
			break;
	}
}

static gchar *
dates_view_config_get_tzid (GConfClient *gconf_client)
{
	gchar *location;
	GError *error = NULL;
	
	location = gconf_client_get_string (gconf_client,
		CALENDAR_GCONF_TIMEZONE, &error);
	if (!location) {
		g_warning ("Error retrieving local time zone");
		if (error) {
			g_warning ("Error message: %s",
				error->message);
			g_error_free (error);
		}
		return NULL;
	}
	
	return location;
}
#if 0
static gboolean
dates_view_config_get_24h (GConfClient *gconf_client)
{
	GError *error = NULL;
	/* NOTE: Seems on hildon this doesn't set the error when the key
	 * doesn't exist, so until we can configure this, always return true
	 */
	if (!gconf_client_get_bool (gconf_client, CALENDAR_GCONF_24H, &error)) {
		if (error) {
			g_warning ("Error retrieving gconf key '%s':\n\t\"%s\"",
				CALENDAR_GCONF_24H, error->message);
			g_error_free (error);
			return TRUE;
		} else
			return FALSE;
	} else
		return TRUE;
}
#endif
static void dates_view_event_free (DatesViewEvent *event);

static void
dates_view_finalize (GObject *object)
{
	DatesView *view;
	DatesViewPrivate *priv;
	guint i;

	view = DATES_VIEW (object);
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	/* NOTE: This should free all calendars and events */
	dates_view_remove_all_calendars (view);

	g_free (priv->date);
	for (i = 0; i < 31; i++) {
		g_object_unref (priv->date_layouts[i]);
		g_object_unref (priv->bdate_layouts[i]);
		g_object_unref (priv->date_pixmaps[i]);
	}
	for (i = 0; i < 23; i++)
		g_object_unref (priv->time_layouts[i]);
	for (i = 0; i < 7; i++) {
		g_object_unref (priv->abday_layouts[i]);
#ifndef DATES_ABREV_DAY_LABELS
		g_object_unref (priv->day_layouts[i]);
#endif
	}
    
	for (i = 0; i < 12; i++) {
		g_object_unref (priv->month_layouts[i]);
		if (priv->regions[i].mrect) {
			g_free (priv->regions[i].mrect);
			g_list_foreach (
				priv->regions[i].drects, (GFunc)g_free, NULL);
			g_list_free (priv->regions[i].drects);
		}
	}
	
	if (priv->recur_pixbuf)
		g_object_unref (priv->recur_pixbuf);
	if (priv->alarm_pixbuf)
		g_object_unref (priv->alarm_pixbuf);

	if (priv->font)
		pango_font_description_free (priv->font);	

	if (priv->lr_cursor)
		gdk_cursor_unref (priv->lr_cursor);

	if (priv->move_cursor)
		gdk_cursor_unref (priv->move_cursor);
	
	if (priv->gc)
		g_object_unref (priv->gc);
	
	G_OBJECT_CLASS (parent_class)->finalize (object);
}

static void
dates_view_redraw (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);

#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_DRAW)
		g_debug ("Redraw");
#endif

	if (GTK_WIDGET_REALIZED (view)) {
		gdk_window_invalidate_rect (GTK_WIDGET (view)->window,
			&GTK_WIDGET (view)->allocation, TRUE);
	}
}

static gboolean
dates_view_refresh (gpointer data)
{
	DatesView *view = DATES_VIEW (data);
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	
	/* Refresh time and redraw */
	priv->now = icaltime_current_time_with_zone (NULL);
	dates_view_redraw (view);
	
	return TRUE;
}

static void
dates_view_realize (GtkWidget *widget)
{
	DatesView *view;
	DatesViewPrivate *priv;

	view = DATES_VIEW (widget);
	priv = DATES_VIEW_GET_PRIVATE (view);

	GTK_WIDGET_CLASS (parent_class)->realize (widget);
	
	gtk_widget_realize (priv->main);
	
	/* Refresh the time and redraw every minute, to update the time-line */
	g_timeout_add (60 * 1000, dates_view_refresh, view);
}

static void
dates_view_event_data_free (DatesViewEventData *data)
{
	if (data->layout) g_object_unref (data->layout);
	g_free (data->rect);
	g_free (data);
}

static void
dates_view_remove_event (DatesView *view, DatesViewEvent *event)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);

	while (event->list_refs) {
		GList *d;
		for (d = event->draw_data; d; d = d->next) {
			DatesViewEventData *data =
				(DatesViewEventData *)d->data;
			*(GList **)event->list_refs->data = g_list_remove (
				*(GList **)event->list_refs->data, data);
		}
		event->list_refs = g_list_remove (
			event->list_refs, event->list_refs->data);
	}

	while (event->draw_data) {
		DatesViewEventData *data =
			(DatesViewEventData *)event->draw_data->data;
		event->draw_data = g_list_remove (event->draw_data, data);
		
		/* Don't free the event data if it's selected */
		if (priv->selected_event != data)
			dates_view_event_data_free (data);
	}
}

static void
dates_view_event_free (DatesViewEvent *event)
{
	dates_view_remove_event (event->parent, event);
	if (event->summary)
		g_object_unref (event->summary);
	if (event->details)
		g_object_unref (event->details);
	g_object_unref (event->comp);
	g_free (event->uri_uid);
	g_free (event);
}

static void
dates_view_get_extra_visible (DatesView *view, icaltimetype *start,
			      icaltimetype *end, gint *soffset, gint *eoffset)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);

	/* Work out how many days are visible before/after this month
	 * when in week/month view.
	 */
	*soffset = 0;
	*eoffset = 0;
	if (priv->months <= 1) {
		if (start->day == 1) {
			*soffset = DOWSTART (1, start->month,
				start->year, priv->week_start);
				
		}
		if ((end->day == 1) &&
		    (end->month > start->month)) {
			gint dim = time_days_in_month (
			    start->year, start->month - 1);
			*eoffset = 6 - DOWSTART (dim, start->month,
				start->year, priv->week_start);
		}
	}
}

static void
dates_view_get_visible_cspan (DatesView *view, struct icaltimetype *start,
			     struct icaltimetype *end)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	
	*start = *priv->date;
	*end = *priv->date;
	
	if (priv->months == 0) {
		if (priv->days != 1) {
			/* Less simple case */
			guint day, wstart, wend;
			
			day = IDOWSTART (*priv->date, priv->week_start);
			wstart = priv->days < day ? priv->days - 1 :
				day - 1;
			wend = priv->days - wstart;
			
			icaltime_adjust (start, -wstart, 0, 0, 0);
			icaltime_adjust (end, wend, 0, 0, 0);
		} else {
			/* Simple case */
			icaltime_adjust (end, 1, 0, 0, 0);
		}
	} else {
		gint diff = (priv->date->month + priv->months) - 13;
		start->day = 1;
		end->day = 1;
		if (diff > 0) {
			start->month -= diff;
			end->month += (priv->months - diff);
		} else {
			end->month += priv->months;
		}
		
		diff = (start->month - 1) % priv->months;
		if (diff != 0) {
			diff = MIN (diff, priv->months);
			start->month -= diff;
			end->month -= diff;
		}
		
		*start = icaltime_normalize (*start);
		*end = icaltime_normalize (*end);
	}
}

void
dates_view_get_visible_span (DatesView *view, struct icaltimetype *start,
			     struct icaltimetype *end)
{
	gint soffset, eoffset;

	dates_view_get_visible_cspan (view, start, end);
	dates_view_get_extra_visible (view, start, end, &soffset, &eoffset);
	icaltime_adjust (start, -soffset, 0, 0, 0);
	icaltime_adjust (end, eoffset, 0, 0, 0);
}

static void
dates_view_refresh_date_layouts (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	PangoRectangle rect;
	gint i;

	for (i = 0; i < 31; i++) {
		gchar *text = g_strdup_printf (
			"<small>%d</small>", i+1);
		if (priv->date_layouts[i])
			g_object_unref (priv->date_layouts[i]);
		priv->date_layouts[i] = gtk_widget_create_pango_layout (
			GTK_WIDGET (view), NULL);
		if (priv->font)
			pango_layout_set_font_description (
				priv->date_layouts[i], priv->font);
		pango_layout_set_markup (priv->date_layouts[i], text, -1);
		g_free (text);
		text = g_strdup_printf (
			"<small><b><i>%d</i></b></small>", i+1);
		priv->bdate_layouts[i] = gtk_widget_create_pango_layout (
			GTK_WIDGET (view), NULL);
		if (priv->font)
			pango_layout_set_font_description (
				priv->bdate_layouts[i], priv->font);
		pango_layout_set_markup (priv->bdate_layouts[i], text, -1);
		g_free (text);

		
		/* NOTE: there seems to be a bug in pango that causes the font
		* to be incorrectly sized unless the get_extents function is
		* called on the layout.
		*/
		pango_layout_get_extents (priv->date_layouts[i], NULL, &rect);
		pango_layout_get_extents (priv->bdate_layouts[i], NULL, &rect);

		if (rect.width > priv->max_rect.width)
			priv->max_rect.width = rect.width;

		if (rect.height > priv->max_rect.height)
			priv->max_rect.height = rect.height;
	}

	/*
	 * The width and height of the corner box are 1.5 x the width and
	 * height of the maximum size that the day number ever occupies
	 */
	priv->corner_width = (PANGO_PIXELS (priv->max_rect.width) * 3) >> 1;
	priv->corner_height = (PANGO_PIXELS (priv->max_rect.height) * 3) >> 1;
}

static void
dates_view_refresh_date_pixmaps (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	guint i;

	for (i=0; i < 31; i++)
	{
		gint x, y;
		PangoRectangle layout_rect;

		if (priv->date_pixmaps[i] != NULL)
			g_object_unref (priv->date_pixmaps[i]);
		priv->date_pixmaps[i] = gdk_pixmap_new (priv->main->window, 
				priv->corner_width + 1 , priv->corner_height + 1,
				-1);

		/* Draw the box in the corner to put
		 * the day number in
		 */

		/* Draw filled rectange */
		gdk_gc_set_foreground (priv->gc,
			&priv->main->style->white);
		gdk_draw_rectangle (priv->date_pixmaps[i],
			priv->gc, TRUE,
			0, 0,
			priv->corner_width,
			priv->corner_height);

		gdk_gc_set_foreground (priv->gc,
			&priv->main->style->black);

		gdk_draw_rectangle (priv->date_pixmaps[i],
			priv->gc, FALSE,
			0, 0,
			priv->corner_width,
			priv->corner_height);

		/* Get the bounding box for this day */
		pango_layout_get_extents (
			priv->date_layouts[i], NULL,
			&layout_rect);

		/* Calculate the origin for the text
		 * such that it will be centered in the
		 * corner box.
		 */
		x = (priv->corner_width - PANGO_PIXELS (
			layout_rect.width)) >> 1;
		y = (priv->corner_height - PANGO_PIXELS(
			layout_rect.height)) >> 1;

		/* Set the wrap width for the content */
		pango_layout_set_width (
			priv->date_layouts[i],
			(priv->corner_width - (BORDER * 2)) *
			PANGO_SCALE);

		/* Draw the day number */
		gdk_draw_layout (priv->date_pixmaps[i],
			priv->gc,
			x,
			y,
			priv->date_layouts[i]);
	}
}

static void
dates_view_refresh_time_layouts (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	PangoRectangle rect;
	gint i, width;

	width = 0;
	priv->time_layouts_height = 0;
	for (i = 0; i < 23; i++) {
		gchar *text;
		if (priv->use_24h) {
			text = g_strdup_printf (
				"<small>%02d:00</small>", i + 1);
		} else {
			text = g_strdup_printf (
				"<small>%02d:00 %s</small>", i % 12 + 1,
				i < 11 ? _("AM") : _("PM"));
		}
		if (priv->time_layouts[i])
			g_object_unref (priv->time_layouts[i]);
		priv->time_layouts[i] = gtk_widget_create_pango_layout (
			GTK_WIDGET (view), NULL);
		if (priv->font)
			pango_layout_set_font_description (
				priv->time_layouts[i], priv->font);
		pango_layout_set_markup (priv->time_layouts[i], text, -1);
		g_free (text);

		/* See above note about pango bug */
		pango_layout_get_extents (priv->time_layouts[i], NULL, &rect);
		if (rect.height > priv->time_layouts_height)
			priv->time_layouts_height = rect.height;
		if (rect.width > width) width = rect.width;
	}
	gtk_widget_set_size_request (priv->side,
		PANGO_PIXELS (width) + (BORDER * 2), -1);
}

static void
dates_view_refresh_day_layouts (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	PangoRectangle rect;
	gint i, height;

	height = 0;
	for (i = 0; i < 7; i++) {
		int day_no = (priv->week_start + i) % 7;
		gchar *day_markup;
#ifndef DATES_ABREV_DAY_LABELS
		day_markup = g_strdup_printf ("<small>%s</small>",
			nl_langinfo (DAY_1 + day_no));
		if (priv->day_layouts[i])
			g_object_unref (priv->day_layouts[i]);
		priv->day_layouts[i] = gtk_widget_create_pango_layout (
			GTK_WIDGET(view), NULL);
		if (priv->font)
			pango_layout_set_font_description (
				priv->day_layouts[i], priv->font);
		pango_layout_set_markup (priv->day_layouts[i], day_markup, -1);
		pango_layout_set_wrap (priv->day_layouts[i], PANGO_WRAP_CHAR);
		g_free (day_markup);
#endif
		day_markup = g_strdup_printf ("<small>%s</small>",
			nl_langinfo (ABDAY_1 + day_no));
		if (priv->abday_layouts[i])
			g_object_unref (priv->abday_layouts[i]);
		priv->abday_layouts[i] = gtk_widget_create_pango_layout (
			GTK_WIDGET(view), NULL);
		if (priv->font)
			pango_layout_set_font_description (
				priv->abday_layouts[i], priv->font);
		pango_layout_set_markup (
			priv->abday_layouts[i], day_markup, -1);
		pango_layout_set_wrap (priv->abday_layouts[i], PANGO_WRAP_CHAR);
		g_free (day_markup);
        
		/* See above note about pango bug */
#ifndef DATES_ABREV_DAY_LABELS
		pango_layout_get_extents (priv->day_layouts[i], NULL, &rect);
#endif
		pango_layout_get_extents (priv->abday_layouts[i], NULL, &rect);
		
		if (rect.height > height) height = rect.height;
	}
	gtk_widget_set_size_request (priv->top, -1, PANGO_PIXELS (height)
		+ (BORDER * 2));
}

static void
dates_view_refresh_month_layouts (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	PangoRectangle rect;
	gint i;

	for (i = 0; i < 12; i++) {
		gint j;
		gchar *month_markup = g_strdup_printf (
			"<small>%s</small>",
			nl_langinfo (MON_1 + i));
		if (priv->month_layouts[i])
			g_object_unref (priv->month_layouts[i]);
		priv->month_layouts[i] = gtk_widget_create_pango_layout (
			GTK_WIDGET(view), NULL);
		if (priv->font)
			pango_layout_set_font_description (
				priv->month_layouts[i], priv->font);
		pango_layout_set_markup (priv->month_layouts[i],
			month_markup, -1);
		pango_layout_set_ellipsize (priv->month_layouts[i],
			PANGO_ELLIPSIZE_END);	
		g_free (month_markup);
		
		for (j = 0; j < 31; j++) {
			priv->event_days[i][j] = NULL;
		}

		/* See above note about pango bug */
		pango_layout_get_extents (priv->month_layouts[i], NULL, &rect);
	}
}

static void
dates_view_init (DatesView *view)
{
	DatesViewPrivate *priv;
	GdkColor colour;
	GtkWidget *widget;
	gint i;
#ifdef DEBUG
	const gchar *debug;
#endif
	widget = GTK_WIDGET (view);
	priv = DATES_VIEW_GET_PRIVATE (view);
	
#ifdef DEBUG
	debug = g_getenv ("DATES_VIEW_DEBUG");
	if (debug)
		priv->debug = g_parse_debug_string (debug,
			dates_view_debug_keys,
			G_N_ELEMENTS (dates_view_debug_keys));
#endif
	
	priv->calendars = NULL;
	priv->events = NULL;
	priv->gconf_client = gconf_client_get_default ();
	if (priv->gconf_client) {
		priv->zone_s = dates_view_config_get_tzid (priv->gconf_client);
		/* Don't read in this setting here, it should be controlled
		 * by the app.
		 */
/*		priv->use_24h = dates_view_config_get_24h (
			priv->gconf_client);*/
	} else {
		g_warning ("Error retrieving default gconf client");
		priv->zone_s = NULL;
	}

	/* 
	 * Test if we have proper idea of the timezone. The Evolution schema
	 * defaults to UTC which is not really a time zone location. We only
	 * want to show the Marcus Bains line when we know that it is correct
	 */
	if (priv->zone_s == NULL || strncmp(priv->zone_s, "UTC", 3) == 0)
		priv->show_marcus_bains = FALSE;
	else
		priv->show_marcus_bains = TRUE;

	priv->zone = icaltimezone_get_builtin_timezone (priv->zone_s);
	priv->use_24h = TRUE;
	priv->utc = icaltimezone_get_utc_timezone ();
	priv->now = icaltime_current_time_with_zone (priv->zone);
	priv->read_only = FALSE;
	priv->selected_event = NULL;
	priv->unselected = TRUE;
	priv->operation = DATES_VIEW_NONE;
	priv->lr_cursor = gdk_cursor_new (GDK_BOTTOM_RIGHT_CORNER);
	priv->move_cursor = gdk_cursor_new (GDK_FLEUR);
	priv->hours = 24;
	priv->days = 1;
	priv->months = 0;
	priv->months_in_row = 4;
	priv->week_start = 1;
	priv->dragbox = TRUE;
	priv->single_click = TRUE;
	priv->double_click = FALSE;
	priv->snap = 30;
	priv->use_list = FALSE;
#ifdef WITH_DND
	priv->motion_idle = 0;
#endif

	/* Initialise colours */
	priv->gc = NULL;
	gdk_color_parse ("red", &priv->red[0]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->red[0]);
	gdk_color_parse ("light red", &priv->red[1]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->red[1]);
	gdk_color_parse ("dark red", &priv->red[2]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->red[2]);
	gdk_color_parse ("green", &priv->green[0]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->green[0]);
	gdk_color_parse ("light green", &priv->green[1]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->green[1]);
	gdk_color_parse ("dark green", &priv->green[2]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->green[2]);
	gdk_color_parse ("cyan", &priv->blue[0]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->blue[0]);
	gdk_color_parse ("light cyan", &priv->blue[1]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->blue[1]);
	gdk_color_parse ("dark cyan", &priv->blue[2]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->blue[2]);
	gdk_color_parse ("#7e7e7e", &priv->grey[0]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->grey[0]);
	gdk_color_parse ("#ededed", &priv->grey[1]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->grey[1]);
	gdk_color_parse ("dark grey", &priv->grey[2]);
	gdk_color_alloc (gtk_widget_get_colormap (widget), &priv->grey[2]);

	priv->start_select = 0;
	priv->end_select = 0;
	priv->recur_pixbuf = gtk_widget_render_icon (GTK_WIDGET (view),
		GTK_STOCK_REFRESH, GTK_ICON_SIZE_MENU, NULL);
	if (!priv->recur_pixbuf)
		g_warning ("Error loading icon '%s'", GTK_STOCK_REFRESH);
	priv->alarm_pixbuf = gtk_widget_render_icon (GTK_WIDGET (view),
		GTK_STOCK_DIALOG_WARNING, GTK_ICON_SIZE_MENU, NULL);
	if (!priv->alarm_pixbuf)
		g_warning ("Error loading icon '%s'", GTK_STOCK_DIALOG_WARNING);

#if defined(WITH_HILDON) || defined(WITH_OMOKO)
	priv->font = pango_font_description_from_string(DEFAULT_FONT_STRING);
#else
	priv->font = NULL;
#endif
	
	for (i = 0; i < 31; i++) {
		priv->date_layouts[i] = NULL;
		if (i < 23) {
			priv->time_layouts[i] = NULL;
			if (i < 12) {
				priv->month_layouts[i] = NULL;
				if (i < 7) {
#ifndef DATES_ABREV_DAY_LABELS
					priv->day_layouts[i] = NULL;
#endif
					priv->abday_layouts[i] = NULL;
				}
			}
		}
	}
	priv->main = gtk_drawing_area_new ();
	priv->side = gtk_drawing_area_new ();
	priv->top = gtk_drawing_area_new ();
	gdk_color_parse ("white", &colour);
	gtk_widget_modify_bg (priv->main, GTK_STATE_NORMAL, &colour);
	gtk_widget_modify_bg (priv->side, GTK_STATE_NORMAL, &colour);
	gtk_widget_modify_bg (priv->top, GTK_STATE_NORMAL, &colour);
	//priv->adjust = GTK_ADJUSTMENT (gtk_adjustment_new (8, 0, 13, 1, 1, 1));
	priv->date = g_new (icaltimetype, 1);
	*priv->date = icaltime_today ();
	dates_view_get_visible_span (view, &priv->start, &priv->end);
	dates_view_get_visible_cspan (view, &priv->cstart, &priv->cend);
	
	gtk_table_attach (GTK_TABLE (view), priv->top, 0, 3, 0, 1,
			  GTK_FILL, GTK_FILL, 0, 0);
	gtk_table_attach (GTK_TABLE (view), priv->side, 0, 1, 1, 2,
			  GTK_FILL, GTK_FILL, 0, 0);
	gtk_table_attach_defaults (GTK_TABLE (view), priv->main, 1, 2, 1, 2);

	dates_view_refresh_date_layouts (view);
	dates_view_refresh_time_layouts (view);
	dates_view_refresh_day_layouts (view);
	dates_view_refresh_month_layouts (view);

	gtk_widget_add_events (priv->main,
		GDK_POINTER_MOTION_MASK |
		GDK_POINTER_MOTION_HINT_MASK |
		GDK_BUTTON_PRESS_MASK |
		GDK_BUTTON_RELEASE_MASK |
		GDK_SCROLL_MASK);
	g_object_set (G_OBJECT (view), "can-focus", TRUE, NULL);
	gtk_widget_add_events (GTK_WIDGET (view), GDK_KEY_PRESS_MASK);
	
	g_signal_connect (G_OBJECT (priv->main), "expose_event",
			  G_CALLBACK (dates_view_main_expose), view);
	g_signal_connect (G_OBJECT (priv->main), "button-press-event",
			  G_CALLBACK (dates_view_main_button_press), view);
	g_signal_connect (G_OBJECT (priv->main), "button-release-event",
			  G_CALLBACK (dates_view_main_button_release), view);
	g_signal_connect (G_OBJECT (priv->main), "motion-notify-event",
			  G_CALLBACK (dates_view_main_motion_notify), view);
	g_signal_connect (G_OBJECT (view), "key_press_event",
			  G_CALLBACK (dates_view_key_press), view);

#ifdef WITH_DND
	gtk_drag_dest_set (priv->main, GTK_DEST_DEFAULT_MOTION |
		GTK_DEST_DEFAULT_DROP | GTK_DEST_DEFAULT_HIGHLIGHT,
		dates_view_drag_targets, G_N_ELEMENTS (dates_view_drag_targets),
		GDK_ACTION_COPY | GDK_ACTION_MOVE);

	g_signal_connect (G_OBJECT (priv->main), "drag-drop",
			  G_CALLBACK (dates_view_drag_drop), view);
	g_signal_connect (G_OBJECT (priv->main), "drag-motion",
			  G_CALLBACK (dates_view_drag_motion), view);
	g_signal_connect (G_OBJECT (priv->main), "drag-data-received",
			  G_CALLBACK (dates_view_drag_data_received), view);
	g_signal_connect (G_OBJECT (priv->main), "drag-data-get",
			  G_CALLBACK (dates_view_drag_data_get), view);
	g_signal_connect (G_OBJECT (priv->main), "drag-end",
			  G_CALLBACK (dates_view_drag_end), view);
#endif
	
	g_signal_connect (G_OBJECT (priv->main), "scroll-event",
			  G_CALLBACK (dates_view_main_scroll_event), view);
	g_signal_connect (G_OBJECT (priv->side), "expose_event",
			  G_CALLBACK (dates_view_side_expose), view);
	g_signal_connect (G_OBJECT (priv->top), "expose_event",
			  G_CALLBACK (dates_view_top_expose), view);

	/* the default view contains only one day -- do not show top */
/*	gtk_widget_hide (priv->top);*/
	gtk_widget_show (priv->main);
	gtk_widget_show (priv->side);

	/* Create the pixmap for the stippled effect */
	priv->stipple_pixmap = gdk_bitmap_create_from_data (priv->main->window,
			stipple_bits,
			8,
			8);
}

#ifdef WITH_DND
static void
dates_view_drag_data_received (GtkWidget *widget, GdkDragContext *dc,
			       gint x, gint y,
			       GtkSelectionData *selection_data, guint info,
			       guint t, DatesView *view)
{
	gchar *string;
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);	

	if (priv->drag_source) {
#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_DND)
			g_debug ("drag data received from this source");
		return;
	} else {
		if (priv->debug & DATES_VIEW_DEBUG_DND)
			g_debug ("drag data received");
#else
		return;
#endif
	}

	if ((selection_data->length <= 0) || (selection_data->format != 8)) {
		g_warning ("Zero-length or invalid format drag data received");
		gtk_drag_finish (dc, FALSE, FALSE, t);
		return;
	}
	
	string = g_strndup ((gchar *)selection_data->data,
		selection_data->length);
	if (string) {
		gtk_drag_finish (dc, TRUE, FALSE, t);
#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_DND)
			g_debug ("Emitting signal ical_drop");
#endif
		g_signal_emit (view, signals[ICAL_DROP], 0, string);
		g_free (string);
	} else {
		g_warning ("Error duplicating string");
		gtk_drag_finish (dc, FALSE, FALSE, t);
	}
}

static void
dates_view_drag_data_get (GtkWidget *widget, GdkDragContext *dc,
			  GtkSelectionData *data, guint info, guint time,
			  DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	gchar *ical;
	
#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_DND)
		g_debug ("drag data get");
#endif
	ical = icalcomponent_as_ical_string (
		e_cal_component_get_icalcomponent (
			priv->selected_event->parent->comp));
	gtk_selection_data_set (data, GDK_SELECTION_TYPE_STRING, 8,
		(const guchar *)ical, strlen (ical));
}
#endif

static gint
dates_view_event_data_sort (DatesViewEventData *d1, DatesViewEventData *d2)
{
	gint returnval;
	
	returnval = d1->start - d2->start;
	if (returnval == 0)
		returnval = d2->end - d1->end;
	
	return returnval;
}

static void
dates_view_make_event_layouts (DatesView *view, DatesViewEvent *event)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);

	/* Free and create event label Pango layout */	
	if (event->summary) {
		g_object_unref (event->summary);
		event->summary = NULL;
	}
	if (event->details) {
		g_object_unref (event->details);
		event->details = NULL;
	}

	if (priv->months <= 1) {
		PangoRectangle rect;
		const gchar *summary;
		event->summary = gtk_widget_create_pango_layout (
			GTK_WIDGET (view), NULL);

		if (priv->font) {
			pango_layout_set_font_description (
				event->summary, priv->font);
		}
		summary = icalcomponent_get_summary (
			e_cal_component_get_icalcomponent (event->comp));
		if (summary) {
			gchar *string = g_markup_printf_escaped (
				"<small>%s</small>", summary);
			pango_layout_set_markup (event->summary, string, -1);
			g_free (string);
		}
		
		/* Ellipses looks nice, but rarely shows enough data */
		pango_layout_set_wrap (event->summary, PANGO_WRAP_WORD);
/*		pango_layout_set_ellipsize (
			event->summary, PANGO_ELLIPSIZE_END);*/
			
		/* NOTE: For pango bug on hildon, see dates_view_init */
		pango_layout_get_extents (event->summary, NULL, &rect);
		
		/* Create details layout */
		if (priv->months == 0) {
			static const gchar *head = "<span size=\"x-small\">";
			static const gchar *tail = "</span>";
			GSList *text_list;
			const gchar *location = NULL;
			gchar *string = NULL;

			event->details = gtk_widget_create_pango_layout (
				GTK_WIDGET (view), NULL);
			e_cal_component_get_description_list (
				event->comp, &text_list);
			e_cal_component_get_location (event->comp, &location);

			if (priv->font) {
				pango_layout_set_font_description (
					event->details, priv->font);
			}
			
			if (text_list) {
				ECalComponentText *desc = text_list->data;
				
				if (desc->value) {
					if (location)
						string = g_strconcat (
							head, location, "\n",
							desc->value,
							tail, NULL);
					else
						string = g_strconcat (
							head, desc->value,
							tail, NULL);
				}
				
				e_cal_component_free_text_list (text_list);
			} else if (location) {
				string = g_strconcat (head, location, tail,
					NULL);
					
			}
			if (string) {
				pango_layout_set_markup (
					event->details, string, -1);
				g_free (string);
			}
			
			pango_layout_set_wrap (event->details, PANGO_WRAP_WORD);
			/* NOTE: Again, pango bug on hildon */
			pango_layout_get_extents (event->details, NULL, &rect);
		}
	}
}

static gboolean
dates_view_event_file_cb (ECalComponent *comp, time_t start,
			  time_t end, DatesViewEvent *event)
{
	DatesView *view;
	DatesViewPrivate *priv;
	struct icaltimetype istart, itt;
	time_t rend, end2, abs_start;
	gboolean first = TRUE;
	icalcomponent *icalcomp;

	view = event->parent;
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	/* Start and end times need to be converted into local time if they
	 * aren't floating times.
	 */
	icalcomp = e_cal_component_get_icalcomponent (comp);
	itt = icalcomponent_get_dtstart (icalcomp);
	if (itt.zone) {
		gint daylight, offset;
		/* TODO: Check if assuming the end time is in the same zone as
		 * the start time is ok.
		 */
		offset = icaltimezone_get_utc_offset (
			priv->zone, &itt, &daylight);
		start += offset;
		end += offset;
#ifdef DEBUG		
		if (priv->debug & DATES_VIEW_DEBUG_FIT)
			g_debug ("Event with %d second offset found", offset);
#endif
	}
	
	istart = icaltime_from_timet (start, TRUE);
	end2 = icaltime_as_timet (istart);
	abs_start = start;
	
	/* Make sure not to create data outside of the visible range */
	rend = MIN (end, icaltime_as_timet (priv->end));
	
	/* Split event into separate days */
	while (start < rend) {
		DatesViewEventData *data = g_new0 (DatesViewEventData, 1);

#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_FIT)
			g_debug ("Filing event \"%s\", %d/%d/%d",
				icalcomponent_get_summary (
					e_cal_component_get_icalcomponent (
						event->comp)),
				istart.day, istart.month, istart.year);
#endif
	
		data->parent = event;
		data->date = istart;
		data->start = start;

		end2 = time_add_day (end2, 1);
		data->end = MIN (end2, end);
		data->abs_start = abs_start;
		data->abs_end = end;

		priv->event_days[istart.month - 1][istart.day - 1] =
			g_list_insert_sorted (priv->event_days[
				istart.month - 1][istart.day - 1], data,
				(GCompareFunc)dates_view_event_data_sort);
		event->list_refs = g_list_prepend (event->list_refs,
			&priv->event_days[istart.month - 1][istart.day - 1]);

		icaltime_adjust (&istart, 1, 0, 0, 0);
		start = icaltime_as_timet (istart);
		
		data->first = first;
		if (end2 >= end) data->last = TRUE;
		else data->last = FALSE;
		first = FALSE;
		
		if ((priv->months == 1) && (priv->use_list)) {
			/* Create time-layout for list view */
			gchar *string;
			struct icaltimetype istart2, iend;
			
			istart2 = icaltime_from_timet (data->start, FALSE);
			iend = icaltime_from_timet (data->end, FALSE);
			data->layout = gtk_widget_create_pango_layout (
				GTK_WIDGET (view), NULL);
			string = g_strdup_printf (
				"<small>(%02d:%02d)-(%02d:%02d) </small>",
				istart2.hour, istart2.minute,
				iend.hour, iend.minute);
			pango_layout_set_markup (data->layout, string, -1);
			g_free (string);
		}
		
		event->draw_data = g_list_prepend (event->draw_data, data);
	}
	
	return TRUE;
}

static void
dates_view_event_file (DatesView *view, DatesViewEvent *event)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);

	dates_view_make_event_layouts (view, event);
	
#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_FIT)
		g_debug ("Testing event \"%s\" for filing"
			" between %d/%d/%d and %d/%d/%d",
			icalcomponent_get_summary (
				e_cal_component_get_icalcomponent (
					event->comp)),
			priv->start.day, priv->start.month, priv->start.year,
			priv->end.day, priv->end.month, priv->end.year);
#endif
	e_cal_recur_generate_instances (event->comp,
		icaltime_as_timet_with_zone (priv->start, priv->zone),
		icaltime_as_timet_with_zone (priv->end, priv->zone),
		(ECalRecurInstanceFn)dates_view_event_file_cb,
		event, e_cal_resolve_tzid_cb, event->cal->ecal,
		NULL);
/*		icalcomponent_get_timezone (e_cal_component_get_icalcomponent (
			event->comp), priv->zone_s));*/
}

static gboolean
dates_view_fit_events (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);

	if (((!priv->use_list) && (priv->months <= 1)) || (priv->months == 0)) {
		/* Generate data necessary for detailed view */
		gint m, d;
		
		for (m = 0; m < 12; m++) {
			for (d = 0; d < 31; d++) {
				GList *e, *c, *l;
				
				l = NULL;
				c = priv->event_days[m][d];
				/* Clear event data */
				for (e = c; e; e = e->next) {
					DatesViewEventData *d;
					d = (DatesViewEventData *)e->data;
					d->position = 0;
					d->width = 0;
					d->span = 0;
				}
				/* Calculate position */
				for (e = c; e; e = e->next) {
					GList *e2;
					DatesViewEventData *d;
					gboolean move_left = TRUE;
					
					d = (DatesViewEventData *)e->data;

					for (e2 = c; e2; e2 = e2->next) {
						DatesViewEventData *d2 =
							(DatesViewEventData *)
								e2->data;

						if (e == e2) {
							move_left = FALSE;
							continue;
						}
						if (!((d2->start < d->end) &&
						    (d2->end > d->start))) {
							continue;
						}
						
						if (d2->position ==
						    d->position) {
							if (move_left)
								d->position ++;
							else
								d2->position ++;
						}
					}
					l = e;
				}

				/* Calculate width */
				for (e = l; e; e = e->prev) {
					GList *e2;
					DatesViewEventData *d;
					time_t start, end;
					
					d = (DatesViewEventData *)e->data;
					d->width = d->position;

					start = d->start;
					end = d->end;
					for (e2 = c; e2; e2 = e2->next) {
						DatesViewEventData *d2 =
							(DatesViewEventData *)
								e2->data;

						if (e == e2) continue;
						if (!((d2->start < end) &&
						    (d2->end > start))) {
							continue;
						}
						
						if (d2->start < start)
							start = d2->start;
						if (d2->end > end)
							end = d2->end;
						
						if (d2->position > d->width)
							d->width = d2->position;
						if (d2->width > d->width)
							d->width = d2->width;
						if (d->width > d2->width)
							d2->width = d->width;
					}
				}

				/* Calculate span */
				for (e = c; e; e = e->next) {
					GList *e2;
					DatesViewEventData *d;
					
					d = (DatesViewEventData *)e->data;

					d->span = d->width - d->position;
					for (e2 = c; e2; e2 = e2->next) {
						gint span;
						DatesViewEventData *d2 =
							(DatesViewEventData *)
								e2->data;

						if (e == e2) continue;
						if (!((d2->start < d->end) &&
						    (d2->end > d->start))) {
							continue;
						}
						
						span = (d2->position -
							d->position) - 1;
						if ((span >= 0) &&
						    (span < d->span))
							d->span = span;
					}
				}
			}
		}
	} else if (priv->months == 1) {
		/* TODO: Create a "(start) - (finish)" pango layout for list
		 * view.
		 */
	}

	return FALSE;
}

static gint
dates_view_event_find_cb (gconstpointer a, gconstpointer b)
{
	const DatesViewEvent *event = a;
	const gchar *uri_uid = b;
	
	return strcmp (event->uri_uid, uri_uid);
}

static DatesViewEvent *
dates_view_find_event (DatesView *view, const gchar *uri_uid)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	DatesViewEvent *event = NULL;
	GList *e;
	
	e = g_list_find_custom (priv->events, uri_uid,
		dates_view_event_find_cb);
	if (e)
		event = (DatesViewEvent *)e->data;
	
	return event;
}

static gint
dates_view_find_calendar_cb (gconstpointer a, gconstpointer b)
{
	const DatesViewCalendar *cal = a;
	const ECal *ecal = b;
	
	if (cal->ecal == ecal)
		return 0;
	else
		return -1;
}

static void
dates_view_objects_changed (ECalView *ecalview, GList *objects, DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	ECal *ecal = e_cal_view_get_client (ecalview);

	for (; objects; objects = objects->next) {
		const char *uid = icalcomponent_get_uid (objects->data);
		gchar *uri_uid;
		DatesViewEvent *event, *selected_event;
		gboolean new_event = TRUE;
		GList *c;

		if (!uid) continue;
			
		uri_uid = g_strconcat (e_cal_get_uri (ecal), uid, NULL);
		event = dates_view_find_event (view, uri_uid);
		ECalComponent *comp = e_cal_component_new ();
		if (!event) {
			event = g_new0 (DatesViewEvent, 1);
		} else {
			new_event = FALSE;
		}
		event->parent = view;
		c = g_list_find_custom (priv->calendars,
			ecal, dates_view_find_calendar_cb);
		event->cal = c ? (DatesViewCalendar *)c->data : NULL;;
		e_cal_component_set_icalcomponent (comp,
			icalcomponent_new_clone (objects->data));
		if (event->comp)
			g_object_unref (event->comp);
		event->comp = comp;
		event->uri_uid = uri_uid;
		
		selected_event = priv->selected_event ?
			priv->selected_event->parent : NULL;
		
		if (!new_event) {
#if DEBUG
			if (priv->debug & DATES_VIEW_DEBUG_QUERY)
				g_debug ("Object changed: %s", uri_uid);
#endif
			dates_view_remove_event (view, event);
			priv->events = g_list_remove (
				priv->events, event);
		} else {
#if DEBUG
			if (priv->debug & DATES_VIEW_DEBUG_QUERY)
				g_debug ("Object added: %s", uri_uid);
#endif
		}
		priv->events = g_list_prepend (priv->events, event);
		/* File the event into the correct days in priv->event_days */
		dates_view_event_file (view, event);
		
		if (selected_event == event) {
			GList *d;
			/* Select the right part of the event */
			for (d = event->draw_data; d; d = d->next) {
				DatesViewEventData *data =
					(DatesViewEventData *)d->data;
				
				if (icaltime_compare_date_only (data->date,
				    priv->selected_event->date) == 0) {
					dates_view_event_data_free (
						priv->selected_event);
					priv->selected_event = data;
					break;
				}
			}

#if DEBUG
			if (priv->debug & DATES_VIEW_DEBUG_QUERY)
				g_debug ("Event has %d visible instances",
					g_list_length (event->draw_data));
#endif
			
			/* Cancel sizing events */
			if (priv->operation & DATES_VIEW_SIZE)
				priv->operation = DATES_VIEW_NONE;
		}
	}

	dates_view_fit_events (view);
	dates_view_redraw (view);
}

static void
dates_view_objects_removed (ECalView *ecalview, GList *uids, DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	ECal *ecal = e_cal_view_get_client (ecalview);
	
	for (; uids; uids = uids->next) {
		gchar *uri_uid;
		DatesViewEvent *event;
		GList *e;
#ifdef HAVE_CID_TYPE
		ECalComponentId *id = uids->data;
#	ifdef DEBUG
		/* FIXME: What happens with uid/rid here? */
		if (priv->debug & DATES_VIEW_DEBUG_QUERY)
			g_debug ("%s (%s)", id->uid, id->rid);
#	endif
		uri_uid = g_strconcat (e_cal_get_uri (ecal), id->uid, NULL);
#else
		uri_uid = g_strconcat (e_cal_get_uri (ecal), uids->data, NULL);
#endif

#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_QUERY)
			g_debug ("Object removed: %s", uri_uid);
#endif
		e = g_list_find_custom (priv->events, uri_uid,
			dates_view_event_find_cb);
		if (e) {
			event = e->data;
			if ((priv->selected_event) &&
			    (event == priv->selected_event->parent)) {
				/* Cancel moving/sizing events */
				priv->operation = DATES_VIEW_NONE;
				priv->selected_event = NULL;
				priv->unselected = TRUE;
				g_signal_emit (view,
					signals[EVENT_SELECTED], 0);
			}

			priv->events = g_list_remove (priv->events, event);
			dates_view_event_free (event);
		} else
			g_warning ("Event doesn't exist?");

		g_free (uri_uid);

	}

	dates_view_fit_events (view);
	dates_view_redraw (view);
}

static inline gboolean
dates_view_point_in (GdkRectangle *rect, gint x, gint y)
{
	if ((x >= rect->x) && (x <= rect->x + rect->width) &&
	    (y >= rect->y) && (y <= rect->y + rect->height))
		return TRUE;
	else
		return FALSE;
}

static gint
dates_view_find_day_region_cb (gconstpointer a, gconstpointer b)
{
	DatesViewDayRegion *region = (DatesViewDayRegion *)a;
	gint day = *(gint *)b;
	
	return region->day - day;
}

static void
dates_view_add_region (DatesView 	*view,
		       gint 		month,
		       gint		day,
		       GdkRectangle 	*rect)
{
	DatesViewPrivate *priv;
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
/*	while (day < 1) {
		month --;
		day += time_days_in_month (priv->date->year, month - 1);
		offset ++;
	}
	if (month < 1) return;*/
	
	if (!priv->regions[month-1].mrect) {
		priv->regions[month-1].mrect = g_new0 (GdkRectangle, 1);
		priv->regions[month-1].drects = NULL;
	}
	if (day == G_MAXINT) {
		g_memmove (priv->regions[month-1].mrect,
			   rect, sizeof (GdkRectangle));
	} else {
		DatesViewDayRegion *region;
		GList *r = g_list_find_custom (priv->regions[month-1].drects,
			&day, dates_view_find_day_region_cb);
		if (r) {
			region = r->data;
		} else {
			region = g_new0 (DatesViewDayRegion, 1);
			region->day = day;
			priv->regions[month-1].drects = g_list_prepend (
				priv->regions[month-1].drects, region);
		}
		g_memmove (&region->rect, rect, sizeof (GdkRectangle));
	}
}

static GdkRectangle *
dates_view_get_region (DatesView *view, guint month, gint day)
{
	DatesViewPrivate *priv;
	gint yearadd;
	
	priv = DATES_VIEW_GET_PRIVATE (view);

	/* Don't allow getting of invisible regions */
	yearadd = (priv->cstart.year == priv->cend.year) ? 0 : 12;
	if ((month < priv->cstart.month) ||
	    (month > priv->cend.month + yearadd))
		return NULL;
	if (day != G_MAXINT) {
		if (((month == priv->start.month) &&
		     (day < priv->start.day)) ||
		    ((month == priv->end.month+yearadd) &&
		     (day >= priv->end.day))) {
			return NULL;
		}
		/* Alter month/day to fall in current month, when viewing
		 * a month/week that has a previous month visible.
		 */
/*
#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_DRAW)
			g_debug (G_STRLOC ": Before %d/%d", day, month);
#endif
		if (priv->months <= 1) {
			gint dim = time_days_in_month (month-1);
			if (month > priv->date->month) {
				day += time_days_in_month (priv->date->year,
					month-1);
				month --;
			} else if (month < priv->date->month) {
				day = (time_days_in_month (priv->date->year,
					month-1) - day) + 1;
			}
		}
#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_DRAW)
			g_debug (G_STRLOC ": After %d/%d", day, month);
#endif
*/
	}
	
	if (!priv->regions[month-1].mrect)
		return NULL;
	
	if (day == G_MAXINT) {
		return priv->regions[month-1].mrect;
	} else {
		GList *r = g_list_find_custom (priv->regions[month-1].drects,
			&day, dates_view_find_day_region_cb);
		if (r)
			return &((DatesViewDayRegion *)r->data)->rect;
		else
			return NULL;
	}
}

static GdkRectangle *
dates_view_in_region (DatesView *view, gint x, gint y, gint *month, gint *day)
{
	DatesViewPrivate *priv;
	guint i;
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	for (i = 1; i <= 12; i++) {
		GdkRectangle *mrect;
		if (!(mrect = dates_view_get_region (view, i, G_MAXINT)))
			continue;
		
		if (dates_view_point_in (mrect, x, y)) {
			*month = i;
			*day = G_MAXINT;
			gint j;
			
			/* Try to look a week before and after, for month/week
			 * view with regions from the previous/next month
			 * visible.
			 */
			for (j = -6; j <= 38; j++) {
				GdkRectangle *drect;
				if (!(drect = dates_view_get_region (
				    view, i, j)))
					continue;
				
				if (dates_view_point_in (drect, x, y)) {
					*day = j;
					return drect;
				}
			}
			
			return mrect;
		}
	}
	
	return NULL;
}

static void
dates_view_scroll_value_changed	(GtkAdjustment *adjust, DatesView *view)
{
	DatesViewPrivate *priv;
	
	priv = DATES_VIEW_GET_PRIVATE (view);

	/* 
	 * In the year & month cases we need to handle changing the date based
	 * on scrolling ourselves.
	 */
	if (priv->months > 0)
	{
		struct icaltimetype now = priv->now;
		struct icaltimetype *date = &now;
	
		if (priv->months == 12)
		{
			/* 
			 * If in year case just use the value from the
			 * adjuster as an offset.
			 */
			date->year += (int)adjust->value;
		} else {
			if ((int)adjust->value < 0)
			{
				if (abs((int)adjust->value) < date->month)
				{
					date->month -= abs((int)adjust->value);
				} else {
					/* Roll back into the previous year */
					date->month = date->month - abs((int)adjust->value) + 12;
					date->year -= 1;
				}
			} else {
				if ((int)adjust->value > (12-date->month))
				{
					/* Roll over into the next year */
					date->month = date->month + (int)adjust->value - 12;
					date->year += 1;
				} else {
					date->month += (int)adjust->value;
				}
			}
		}
		dates_view_set_date (view, date);
	}
	dates_view_redraw (view);
}

static void
dates_view_calc_pan (DatesView *view, gdouble *panx, gdouble *pany,
		     gdouble zoomx, gdouble zoomy, gint iwidth, gint iheight)
{
	guint month, day, hour;
	gdouble height, width;
	DatesViewPrivate *priv;
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	height = iheight;
	width = iwidth;
	month = priv->date->month;
	day = priv->date->day;
	hour = priv->date->hour;
	
	if (priv->months >= month) {
		*panx = 0;
		*pany = 0;
	} else {
		gint vrows, row, prow;
		gint vcols, col, pcol;
		
		/* Work out what row the month is on and the amount of
		 * visible rows.
		 */
		row = ceil (month / (gdouble)priv->months_in_row);
		vrows = ceil (priv->months / (gdouble)priv->months_in_row);
		
		/* Pan to center the month on the y-axis */
		prow = (row - (vrows/2));
		/* Don't let empty rows become visible */
		if ((prow + (vrows-1)) > 12 / priv->months_in_row)
			prow = (12 / priv->months_in_row) - vrows;
		*pany = (prow-1) * height;
		
		/* Work out the amount of visible months */
		vcols = MIN (priv->months, priv->months_in_row);
		/* Work out what column the current month is */
		col = month % priv->months_in_row;
		if (col == 0)
			col = priv->months_in_row;
		/* Center on that column */
		pcol = (col - (vcols/2));
		if (pcol + (vcols - 1) > priv->months_in_row)
			pcol = priv->months_in_row - (vcols - 1);
		if (pcol < 1)
			pcol = 1;
		*panx = (pcol - 1) * width;

		/* Pan to specific day when viewing less than a whole month */
		if (priv->months == 0) {
			guint day, days, sdom, week, weeks;
			
			/* Take border into account */
			height -= (BORDER * 2) / zoomy;
			if (priv->days == 1) {
				width -= (BORDER * 2) / zoomx;
				*panx += (BORDER / zoomx);
			}
			*pany += (BORDER / zoomy);
			
			days = icaltime_days_in_month (
				priv->date->month, priv->date->year);
			day = IDOWSTART(*priv->date, priv->week_start);
			sdom = DOWSTART (1, priv->date->month, priv->date->year,
				priv->week_start);
			week = (priv->date->day + (sdom - 1)) / 7;
			weeks = ceil ((days + sdom) / (gdouble)7);
			
			if (day > priv->days)
				*panx += (day - (priv->days)) *
					(width / 7.0);

			/* Pan to correct week */
			*pany += (week * (height / (gdouble)weeks));
			
			/* Pan to hours specified by scrollbar */
			if (priv->scrollable)
				*pany += gtk_adjustment_get_value (priv->adjust)
					 * ((height / (gdouble)weeks) / 24.0);
		}
	}
}

static void
dates_view_calc_zoom (DatesView *view, gdouble *zoomx, gdouble *zoomy,
		      gint width, gint height)
{
	DatesViewPrivate *priv;

	priv = DATES_VIEW_GET_PRIVATE (view);
	
	if (priv->months >= 1) {
		*zoomx = 1/(gdouble)MIN (priv->months, priv->months_in_row);
		*zoomy = 1/(gdouble)
			ceil ((priv->months / (gdouble)priv->months_in_row));
	} else {
		gint dim, dim_ceil, rows;
		
		*zoomx = 7/(gdouble)priv->days;
		
		/* Work out how many rows of weeks in current month */
		dim = time_days_in_month (priv->date->year,
					  priv->date->month - 1);
		dim_ceil = dim + DOWSTART(1,priv->date->month,priv->date->year,
			priv->week_start);
		rows = ceil (dim_ceil / (gdouble)7.0);
		/* Zoom to only show 1 row */
		if (height > 0)
			*zoomy = height/(height/(rows /
				(gdouble)(priv->hours / (gdouble)24)));
	}
}

static void
dates_view_draw_event_emblems (DatesView *view, ECalComponent *comp,
			       GdkGC *gc, gint x, gint y, gint *width)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	gint emblem_offset = 0;

	/* Draw emblems (recurrence, alarm, etc.) */
	if (e_cal_component_has_alarms (comp)) {
		if (priv->alarm_pixbuf) {
			emblem_offset += gdk_pixbuf_get_width (
				priv->alarm_pixbuf);
			gdk_draw_pixbuf (
				priv->main->window, gc, priv->alarm_pixbuf,
				0, 0, x - emblem_offset,
				y - gdk_pixbuf_get_height (priv->alarm_pixbuf),
				-1, -1, GDK_RGB_DITHER_NORMAL,
				0, 0);
		}
	}
	if (e_cal_component_has_recurrences (comp)) {
		if (priv->recur_pixbuf) {
			emblem_offset += gdk_pixbuf_get_width (
				priv->recur_pixbuf);
			gdk_draw_pixbuf (
				priv->main->window, gc, priv->recur_pixbuf,
				0, 0, x - emblem_offset,
				y - gdk_pixbuf_get_height (priv->recur_pixbuf),
				-1, -1, GDK_RGB_DITHER_NORMAL,
				0, 0);
		}
	}
	if (width) *width = emblem_offset;
}

static void
dates_view_draw_event_list (DatesView *view, gint month)
{
	DatesViewPrivate *priv;
	GdkRectangle *mrect;
	gint day, offset = BORDER;

	/* Get the target rectangle */
	if (!(mrect = dates_view_get_region (view, month, G_MAXINT)))
		return;

	priv = DATES_VIEW_GET_PRIVATE (view);
	for (day = 0; day < 31; day++ ) {
		GList *e;
		gboolean first = TRUE;
		for (e = priv->event_days[month-1][day]; e; e = e->next) {
			/* Draw event */
			GdkGC *gc, *dark_gc, * text_gc;
			DatesViewEventData *data;
			DatesViewEvent *event;
			gint width, height, ewidth;
			gboolean read_only = TRUE;
			
			data = (DatesViewEventData *)e->data;
			event = data->parent;
			gc = event->cal->gc;
			dark_gc = event->cal->dark_gc;
			text_gc = event->cal->text_gc;
			
			/* Draw fancy date label */
			if (first) {
				PangoLayout *layout;
				struct tm timem;
				gchar buffer[256];
				
				timem = icaltimetype_to_tm (&data->date);
				strftime (buffer, 255,
					"<small><b>%A %d %B</b></small>",
					&timem);
				layout = gtk_widget_create_pango_layout (
					GTK_WIDGET (view), NULL);
				pango_layout_set_markup (layout, buffer, -1);
				
				gdk_draw_layout (priv->main->window,
					priv->main->style->fg_gc[
						GTK_STATE_NORMAL],
					mrect->x + (2 * BORDER),
					mrect->y + offset + (1.5 * BORDER),
					layout);
				
				pango_layout_get_pixel_size (layout, NULL,
					&height);
				offset += height + (2 * BORDER);
				g_object_unref (layout);
				first = FALSE;
			}
			
			if (!data->rect)
				data->rect = g_new (GdkRectangle, 1);

			/* Work out text height */
			pango_layout_get_pixel_size (data->layout,
				&width, &height);

			data->rect->x = mrect->x + (2 * BORDER);
			data->rect->y = mrect->y + BORDER + offset;
			data->rect->width = mrect->width - (4 * BORDER);
			data->rect->height = height + (2 * BORDER);
			
			/* Draw event rectangle fill */
			gdk_draw_rectangle (priv->main->window, gc, TRUE,
				data->rect->x, data->rect->y,
				data->rect->width, data->rect->height);

			/* Draw bold outline for selected events */
			if ((priv->selected_event) &&
			    (priv->selected_event->parent == event)) {
				gdk_gc_set_line_attributes (dark_gc, 2,
					GDK_LINE_SOLID, GDK_CAP_BUTT,
					GDK_JOIN_MITER);
			}
			/* Draw a dotted outline for read-only events */
			if (e_cal_is_read_only (
				event->cal->ecal, &read_only, NULL)
			    && (read_only)) {
				gdk_gc_set_line_attributes (dark_gc,
					((priv->selected_event) &&
					 (priv->selected_event->parent ==
					  event))
						? 2 : 1,
					GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT,
					GDK_JOIN_MITER);
			}
			gdk_draw_rectangle (priv->main->window, dark_gc, FALSE,
				data->rect->x, data->rect->y,
				data->rect->width, data->rect->height);
			
			/* Draw emblems */
			dates_view_draw_event_emblems (view, event->comp,
				dark_gc,
				(data->rect->x + data->rect->width),
				(data->rect->y + data->rect->height), &ewidth);
			
			/* Draw layouts */
			gdk_draw_layout (priv->main->window,
				priv->main->style->fg_gc[GTK_STATE_NORMAL],
				data->rect->x + BORDER,
				data->rect->y + BORDER,
				data->layout);
			pango_layout_set_width (event->summary,
				(mrect->width - (8 * BORDER) - width - ewidth) *
					PANGO_SCALE);
			gdk_draw_layout (priv->main->window,
				text_gc,
				data->rect->x + width + BORDER,
				data->rect->y + BORDER,
				event->summary);

			/* Reset gc */
			gdk_gc_set_line_attributes (dark_gc, 1,
				GDK_LINE_SOLID, GDK_CAP_BUTT,
				GDK_JOIN_MITER);
			
			offset += height + (3 * BORDER);
		}
	}
}

static void
dates_view_add_hpoints (GList **points, gint x, gint y,
			gint length, gboolean teeth)
{
	GdkPoint *point_start, *point_end;
	
	point_start = g_new (GdkPoint, 1);
	point_start->x = x;
	point_start->y = y;
	*points = g_list_append (*points, point_start);
	
	if (teeth) {
		gint i;
		gdouble twidth;
		
		twidth = length / (gdouble)(TEETH);
		for (i = 0; i < TEETH; i++) {
			GdkPoint *point1, *point2, *point3;
			
			point1 = g_new (GdkPoint, 1);
			point1->x = x + (twidth * ((gdouble)i + 0.25));
			point1->y = y + (BORDER/2);
			
			point2 = g_new (GdkPoint, 1);
			point2->x = x + (twidth * ((gdouble)i + 0.75));
			point2->y = y - (BORDER/2);

			point3 = g_new (GdkPoint, 1);
			point3->x = x + (twidth * ((gdouble)i + 1));
			point3->y = y;

			*points = g_list_append (*points, point1);
			*points = g_list_append (*points, point2);
			*points = g_list_append (*points, point3);
		}
	}

	point_end = g_new (GdkPoint, 1);
	point_end->x = x+length;
	point_end->y = y;
	*points = g_list_append (*points, point_end);
}

static void
dates_view_add_vpoints (GList **points, gint x, gint y,
			gint length, gboolean teeth)
{
	GdkPoint *point = g_new (GdkPoint, 1);
	point->x = x;
	point->y = y;
	*points = g_list_append (*points, point);
	
	if (teeth) {
		gint i;
		gdouble twidth;
		
		twidth = length / (gdouble)TEETH;
		for (i = 1; i <= TEETH; i++) {
			GdkPoint *point1, *point2;
			
			point1 = g_new (GdkPoint, 1);
			point1->x = x + (BORDER);
			point1->y = y + (twidth * ((gdouble)i - 0.5));
			
			point2 = g_new (GdkPoint, 1);
			point2->x = x;
			point2->y = y + (twidth * i);

			*points = g_list_append (*points, point1);
			*points = g_list_append (*points, point2);
		}
	} else {
		GdkPoint *point2 = g_new (GdkPoint, 1);
		point2->x = x;
		point2->y = y + length;
		*points = g_list_append (*points, point2);
	}
}

static void
dates_view_draw_event (DatesView *view, DatesViewEventData *data,
		       gboolean sub_date, guint month,
		       gint day)
{
	DatesViewPrivate *priv;
	GList *p, *points = NULL;
	DatesViewEvent *event;
	struct icaltimetype start, end;
	gint x, x2, y, y2, i;
	gboolean tu, td;
	gboolean read_only = TRUE;
	GdkRectangle *rect, *drectp;
	GdkPoint *points_arr;
	GdkGC *layout_gc, *gc, *dark_gc;
	gboolean draw_detail = TRUE, has_grip = FALSE;
	guint dim;

	priv = DATES_VIEW_GET_PRIVATE (view);


	dim = time_days_in_month (priv->date->year, priv->date->month - 1);


	event = data->parent;
	
	if (!data->rect)
		data->rect = g_new (GdkRectangle, 1);
	
	rect = data->rect;
	start = icaltime_from_timet (data->start, FALSE);
	end = icaltime_from_timet (data->end, FALSE);
#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_DRAW)
		g_debug ("Drawing event on (%d/%d) \"%s\" (%d/%d/%d-%d:%d)"
			"-(%d/%d/%d-%d:%d)",
			day, month,
			icalcomponent_get_summary (
				e_cal_component_get_icalcomponent (
					event->comp)),
			start.day, start.month,
			start.year, start.hour, start.minute,
			end.day, end.month,
			end.year, end.hour, end.minute);
#endif

	if (!sub_date) {
		month = start.month;
		day = start.day;
	}

	/* Get the target rectangle */
	if (!(drectp = dates_view_get_region (view,
	      month, day))) {
		if (priv->selected_event == data) {
			/* TODO: Could probably always use the current date,
			 * for drag-moving events - could then probably cache
			 * this look-up and save some time...
			 */
			if (!(drectp = dates_view_get_region (
			    view, priv->date->month, priv->date->day))) {
				g_warning ("Selected event "
					"data has NULL rectangle!");
				g_free (data->rect);
				data->rect = NULL;
				return;
			}
		} else {
			g_free (data->rect);
			data->rect = NULL;
			return;
		}
	}
	g_memmove (rect, drectp, sizeof (GdkRectangle));
	rect->height -= BORDER;
	rect->y += BORDER / 2;
	rect->width -= BORDER;
	rect->x += BORDER / 2;

	/* Top edge*/
	y = (rect->height/(gdouble)24) * start.hour;
	y += (rect->height/(gdouble)1440) * start.minute;
	tu = !data->first;

	/* Bottom edge */
	if (end.day != start.day) {
		y2 = rect->height;
	} else {
		y2 = (rect->height/(gdouble)24) * end.hour;
		y2 += (rect->height/(gdouble)1440) * end.minute;
	}
	td = !data->last;

	/* Calculate position */
	rect->x += BORDER;
	rect->y += y;
	rect->width -= 2 * BORDER;
	rect->height = y2 - y;
	
	/* Adjust for intersecting events */
	rect->width /= data->width + 1;
	rect->x += rect->width * data->position;
	if (data->width != data->position) {
		rect->width *= data->span + 1;
		if (data->position + data->span != data->width)
			rect->width -= BORDER;
	}

	/* Now that we've created the rect it's ok to return if we don't want
	 * to draw.
	 */	
	if ((priv->selected_event) &&
	    (priv->selected_event->parent == event) &&
	    ((priv->operation & DATES_VIEW_MOVE) ||
	     (priv->operation & DATES_VIEW_SIZE))) {
		if (priv->first_move)
			return;
		draw_detail = FALSE;
	}
	
	/* We've finished creating the event rectangle, so stop modifying it */
	x = rect->x;
	y = rect->y;
	x2 = rect->width;
	y2 = rect->height;
	
	/* Alter rectangle for dragging/moving */
	/* TODO: Implement dragging particular recurrences */
	if ((priv->selected_event) &&
	    (priv->selected_event->parent == event)) {
		if (priv->operation & DATES_VIEW_MOVE) {
			/* NOTE: For this to work, the selected event rectangle
			 * must exist - so if it doesn't, create it
			 */
			if (!priv->selected_event->rect) {
				gboolean first = priv->first_move;
				priv->first_move = TRUE;
				dates_view_draw_event (view,
					priv->selected_event, sub_date,
					month, day);
				priv->first_move = first;
			}
		
			x = (priv->current_x -
				(priv->offset_x *
				 priv->selected_event->rect->width)) -
				(priv->selected_event->rect->x - rect->x);
			y = (priv->current_y -
				(priv->offset_y *
				 priv->selected_event->rect->height)) -
				(priv->selected_event->rect->y - rect->y);
		} else if ((priv->operation & DATES_VIEW_SIZE) &&
			   (priv->selected_event == data)) {
			y2 +=
				priv->current_y -
				priv->initial_y;
			/* Don't show sizing events backwards */
			if (y2 < 1) y2 = 1;
		}
	}

	/* Create polygon */
	dates_view_add_hpoints (&points, x, y, x2, tu);
	dates_view_add_vpoints (&points, x + x2, y, y2, FALSE);
	dates_view_add_hpoints (&points, x + x2, y + y2, -x2, td);
	dates_view_add_vpoints (&points, x, y + y2, -y2, FALSE);
	
	points_arr = g_new (GdkPoint, g_list_length (points));
	for (p = points, i = 0; p; p = p->next, i++)
		g_memmove (points_arr+i, p->data,
			sizeof (GdkPoint));

	/* Set drawing properties */
	gc = event->cal->gc;
	if (day <= 0 || day > dim)
	{
		gdk_gc_set_stipple (gc, priv->stipple_pixmap);
		gdk_gc_set_fill (gc, GDK_STIPPLED);
	} else {
		gdk_gc_set_fill (gc, GDK_SOLID);
	}

	dark_gc = event->cal->dark_gc;
	if ((priv->selected_event) &&
	    (priv->selected_event->parent == event)) {
		gdk_gc_set_line_attributes (dark_gc, 2,
			GDK_LINE_SOLID, GDK_CAP_BUTT,
			GDK_JOIN_MITER);
	}
	/* Draw event polygon */
	if (draw_detail)
		gdk_draw_polygon (priv->main->window,
			gc,
			TRUE, points_arr, g_list_length (points));
	/* Draw a dotted outline for read-only events */
	if (e_cal_is_read_only (event->cal->ecal, &read_only, NULL)
	    && (read_only)) {
		gdk_gc_set_line_attributes (dark_gc,
			((priv->selected_event) &&
			 (priv->selected_event->parent == event)) ? 2 : 1,
			GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT,
			GDK_JOIN_MITER);
	}
	gdk_draw_polygon (priv->main->window, dark_gc,
		FALSE, points_arr, g_list_length (points));
			
	if (draw_detail)
		gdk_gc_set_clip_rectangle (dark_gc, rect);
	/* Draw drag grip */
	if ((priv->months == 0) && (!read_only) && (draw_detail) &&
	    (data->last)) {
		for (i = 0; i < GRIPS * 4; i+= 4) {
			gdk_draw_line (
				priv->main->window,
				dark_gc,
				(x + x2) - i, y + y2,
				x + x2, (y + y2) - i);
		}
		has_grip = TRUE;
	}
			
	/* Draw emblems */
	if (draw_detail) {
		dates_view_draw_event_emblems (view, event->comp, dark_gc,
			(x + x2) - (has_grip ? (4 * GRIPS) : 0),
				(y + y2), NULL);
		gdk_gc_set_clip_rectangle (dark_gc, NULL);
	}

	/* Reset gc */
	gdk_gc_set_line_attributes (dark_gc, 1,
		GDK_LINE_SOLID, GDK_CAP_BUTT,
		GDK_JOIN_MITER);
	
	/* Draw event summary */
	if (draw_detail) {
		PangoRectangle text_rect;
		guint line_count;
		guint clip_height = 0;
		GdkRectangle clip_rect;
		
		clip_rect.x = rect->x;
		clip_rect.y = rect->y;

		pango_layout_set_width (event->summary,
			(x2 - (BORDER * 2)) * PANGO_SCALE);

		/* 
		 * Test to see if the text would overlap with the box in the
		 * corner of the day. If so indent the text so that it does
		 * not happen.
		 */
		if (y < (drectp->y + priv->corner_height) && x < (drectp->x + priv->corner_width))
			pango_layout_set_indent (event->summary, (priv->corner_width - BORDER) * PANGO_SCALE);
		else
			pango_layout_set_indent (event->summary, 0);

		/* Get the rectangle needed for all the text */
		pango_layout_get_extents (event->summary, NULL, &text_rect);

		layout_gc = event->cal->text_gc;

		/* Adjust the clipping rectangle to include as much of the
		 * text as can be completely drawn
		 */
		if (!(PANGO_PIXELS (text_rect.height) < (y2 - BORDER)))
		{
			line_count = pango_layout_get_line_count(event->summary);

			for (i = 0; i < line_count; i++)
			{
				PangoLayoutLine *line;
#if defined PANGO_VERSION_CHECK
#if PANGO_VERSION_CHECK(1,16,0)
				line = pango_layout_get_line_readonly (event->summary, i);
#else
				line = pango_layout_get_line (event->summary, i);
#endif
#else
				line = pango_layout_get_line (event->summary, i);
#endif
				pango_layout_line_get_extents (line, NULL, &text_rect);

				if (PANGO_PIXELS (clip_height + text_rect.height) + BORDER * 2 < rect->height)
				{	
					clip_height += text_rect.height;
				}
			}

			clip_rect.width = rect->width;
			clip_rect.height = PANGO_PIXELS (clip_height) + BORDER;
		} else {
			clip_rect.width = rect->width;
			clip_rect.height = rect->height;
		}

		/* Clip to event rectangle */
		gdk_gc_set_clip_rectangle (layout_gc, &clip_rect);

		gdk_draw_layout (priv->main->window,
			layout_gc, x + BORDER, y + BORDER,
			event->summary);

		if (priv->months == 0) {
			gint height;
			pango_layout_get_pixel_size (event->summary,
				NULL, &height);
			pango_layout_set_width (event->details,
				(x2 - (BORDER * 2)) * PANGO_SCALE);
			gdk_draw_layout (priv->main->window, layout_gc,
				x + BORDER,
				y + BORDER + height,
				event->details);
		}
		gdk_gc_set_clip_rectangle (layout_gc, NULL);
	
	}

	g_free (points_arr);
	g_list_foreach (points, (GFunc)g_free, NULL);
	g_list_free (points);
	points = NULL;
}

static gboolean
dates_view_main_expose (GtkWidget	*widget,
			GdkEventExpose	*event,
			DatesView	*view)
{
	DatesViewPrivate *priv;
	guint i, i_start, i_end, j;
	PangoRectangle layout_rect;
	/* At zoom level 1.0, one day will be displayed - This can be scaled,
	 * depending on how many days you want to view (so 1.0/31 would scale
	 * the view to show 31 days, etc.)
	 */
	gdouble zoomx = 0, zoomy = 0;
	gdouble dpanx, dpany;
	gint panx, pany;
	guint year;
	gint mw, mh;	/* Width/height of month area */
		
	priv = DATES_VIEW_GET_PRIVATE (view);

#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_DRAW)
		g_debug ("Entered %s:%s", G_STRLOC, G_STRFUNC);
#endif

	if (!priv->gc)
		priv->gc = gdk_gc_new (widget->window);

	if (!priv->date_pixmaps[0])
		dates_view_refresh_date_pixmaps (view);

	/* Work out zoom and panning details */
	mw = widget->allocation.width;
	mh = widget->allocation.height;
	dates_view_calc_zoom (view, &zoomx, &zoomy, mw, mh);
	dates_view_calc_pan (view, &dpanx, &dpany, zoomx, zoomy, mw, mh);
	panx = dpanx * zoomx;
	pany = dpany * zoomy;
	mw *= zoomx;
	mh *= zoomy;
	
	year = priv->date->year;
	i_start = (priv->cstart.year != year) ? 1 : priv->cstart.month;
	i_end = (priv->cend.year != year) ? 12 : priv->cend.month;
	
	/* Modify i_start/i_end for special cases (needed so events drawn
	 * from the previous/next month are only drawn once).
	 */
	if ((i_start != i_end) && (priv->months <= 1)) {
		if (priv->months == 1) i_end = i_start;
		else if (priv->days > 1) {
			if (priv->date->day <= 7) i_start = i_end;
			else i_end = i_start;
		}
	}
#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_DRAW)
		g_debug ("Start month: %d, End month: %d", i_start, i_end);
#endif
	for (i = i_start, j = (i-1) / priv->months_in_row; i <= i_end; i++) {
		gint k, l, dim, k_start, k_end, sdow;
		GdkRectangle rect;	/* Dimensions of month area */

		/* Calculate drawing area */
		rect.width = mw;
		rect.height = mh;
		rect.x = (((i-1) % priv->months_in_row) * rect.width) - panx;
		rect.y = (j * rect.height) - pany;
		
		dates_view_add_region (view, i, G_MAXINT, &rect);
		
		/* Don't draw these things when they aren't visible */
		if (priv->months != 0) {
			/* Decide on line-width/colour */
			if (priv->date->month == i) {
				gdk_gc_set_foreground (priv->gc,
					&widget->style->black);
				gdk_gc_set_line_attributes (priv->gc, 2,
					GDK_LINE_SOLID, GDK_CAP_BUTT,
					GDK_JOIN_MITER);
			} else {
				gdk_gc_set_foreground (priv->gc,
					&priv->grey[0]);
			}
			
			/* Draw month label */
			pango_layout_set_width (priv->month_layouts[i-1],
				(rect.width - (BORDER * 2)) * PANGO_SCALE);
			pango_layout_get_extents (
				priv->month_layouts[i-1], NULL, &layout_rect);
			if (priv->months > 1) {
				gdk_draw_layout (widget->window,
					 priv->gc,
					 rect.x + BORDER,
					 rect.y + BORDER,
					 priv->month_layouts[i-1]);
				rect.y += PANGO_PIXELS (layout_rect.height) +
					BORDER;
				rect.height -= PANGO_PIXELS (
					layout_rect.height) + BORDER;
			}
			
			/* Draw month outline */
			gdk_draw_rectangle (
				widget->window, priv->gc, FALSE,
				rect.x + BORDER, rect.y + BORDER,
				rect.width - (2 * BORDER), rect.height -
					(2 * BORDER));
			
			gdk_gc_set_line_attributes (priv->gc, 1, GDK_LINE_SOLID,
				GDK_CAP_BUTT, GDK_JOIN_MITER);
		}
		
		dim = time_days_in_month (year, i-1);
		
		if ((priv->months == 1) && (priv->use_list))
			dates_view_draw_event_list (view, i);

		/* Draw days */
		k_start = (i == priv->cstart.month) ? priv->cstart.day : 1;
		k_end = (i == i_end) ?
			((i == priv->cend.month) ?
				priv->cend.day : dim + 1) : dim + 1;
		sdow = DOWSTART(1,i,year,priv->week_start);

		/* Set k to draw previous/next month events */
		/* FIXME: This next/previous month event logic needs to be
		 * altered to handle the 12 > month > 1 cases
		 */
		if ((priv->days > 1) && (priv->months <= 1)) {
			if ((k_start == 1) && (i != 1)) {
				k_start -= sdow;
			}
			if ((k_end == dim + 1) && (i != 12)) {
				k_end += 7 - DOWSTART(k_end,i,year,
					priv->week_start);
			}
		}
		
#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_DRAW)
			g_debug ("Start day: %d, End day: %d, Month: %d",
				k_start, k_end, i);
#endif

		for (k = k_start, l = ((k_start + sdow - 1) / 7);
		     k < k_end; k++) {
			GdkRectangle drect;
			gint dow = DOWSTART(k,i,year,priv->week_start);
			gint dim_ceil = dim + sdow;
			gboolean selected_day = FALSE;
			gboolean today = FALSE;
			
			if ((year == priv->now.year) &&
			    (i == priv->now.month) &&
			    (k == priv->now.day)) today = TRUE;

			if ((year == priv->date->year) &&
			    (i == priv->date->month) &&
			    (k == priv->date->day)) selected_day = TRUE;

			/* Calculate drawing area */
			drect.x = (rect.x + BORDER) + floor (((rect.width - 
			       (2 * BORDER))/(gdouble)7.0) * (gdouble)dow);
			drect.y = (rect.y + BORDER) + (l * ((rect.height - 
			       (2 * BORDER)) / ceil (dim_ceil / (gdouble)7.0)));
			drect.width = floor ((rect.width - (2 * BORDER)) / 
				     (gdouble)7.0);
			drect.height = (rect.height - (2 * BORDER)) /
				     ceil (dim_ceil /
				     	(gdouble)7.0);

			dates_view_add_region (view, i, k, &drect);

			/* Adjust for border */
			drect.x += BORDER/2;
			drect.y += BORDER/2;
			drect.width -= BORDER;
			drect.height -= BORDER;

			if ((priv->months == 0) && (priv->select_day == k)) {
				/* Draw selected time */
				if (priv->start_select != priv->end_select) {
					/* Finished selection */
					gint y, height;
					
					y = (drect.height * priv->start_select);
					height = (drect.height *
						priv->end_select) - y;
					y += drect.y;
					gdk_gc_set_foreground (priv->gc,
						&priv->blue[2]);
					gdk_draw_rectangle (widget->window,
						priv->gc, FALSE,
						drect.x + BORDER - 1,
						y - 1,
						drect.width - (BORDER * 2) + 1,
						height + 1);
					gdk_gc_set_foreground (priv->gc,
						&priv->blue[0]);
					gdk_draw_rectangle (widget->window,
						priv->gc, TRUE,
						drect.x + BORDER,
						y,
						drect.width - (BORDER * 2),
						height);
				} else if ((priv->operation & DATES_VIEW_SEL) &&
					   (priv->initial_y !=
					    priv->current_y)) {
				    	/* During selection */
					gint lower = MAX (MIN (priv->initial_y,
						priv->current_y), drect.y);
					gint height = MIN (MAX (priv->initial_y,
						priv->current_y) - lower,
							drect.height -
							(lower - drect.y));
					gdk_gc_set_foreground (priv->gc,
						&priv->blue[2]);
					gdk_draw_rectangle (widget->window,
						priv->gc, FALSE,
						drect.x + BORDER - 1,
						lower - 1,
						drect.width - (BORDER * 2) + 1,
						height + 1);
					gdk_gc_set_foreground (priv->gc,
						&priv->blue[0]);
					gdk_draw_rectangle (widget->window,
						priv->gc, TRUE,
						drect.x + BORDER,
						lower,
						drect.width - (BORDER * 2),
						height);
				}
			}
			
			/* Set line-width/colour */
			if (selected_day) {				
				gdk_gc_set_foreground (priv->gc,
					&widget->style->black);
				gdk_gc_set_line_attributes (priv->gc, 2,
					GDK_LINE_SOLID, GDK_CAP_BUTT,
					GDK_JOIN_MITER);
			} else {
				if (today)
					gdk_gc_set_foreground (priv->gc,
						&priv->blue[2]);
				else
					gdk_gc_set_foreground (priv->gc,
						&priv->grey[0]);
			}
			
			if (((!priv->use_list) && (priv->months <= 1)) ||
			    (priv->months == 0)) {
				GList *e;
				gint io, ko;
				gint x, y;

				if (k <= 0 || k >dim)
				{

					gdk_gc_set_foreground (priv->gc,
						&priv->grey[1]);
					/* 
					 * Draw the background of the day as
					 * grey if it is outside the
					 * month/week.
					 */
					gdk_draw_rectangle (
						widget->window, priv->gc, TRUE,
						drect.x, drect.y,
						drect.width, drect.height);

					gdk_gc_set_foreground (priv->gc,
						&priv->grey[0]);
				}


				/* Draw border */
				gdk_draw_rectangle (
					widget->window, priv->gc, FALSE,
					drect.x, drect.y,
					drect.width, drect.height);
				
				gdk_gc_set_line_attributes (priv->gc, 1,
					GDK_LINE_SOLID, GDK_CAP_BUTT,
					GDK_JOIN_MITER);

				/* Draw hours-lines */
				if ((priv->months == 0) && (priv->days <= 7)) {
					gdouble mh4;
					gint m, skip;
					
					mh4 = drect.height / (gdouble)24;
					skip = ceil ((PANGO_PIXELS (
						priv->time_layouts_height) +
						BORDER/2) / mh4);
					if (skip < 1) skip = 1;
					if (skip >= 4) continue;

					gdk_gc_set_foreground (priv->gc,
						&priv->grey[0]);
					for (m = skip; m <= 23; m += skip) {
						gdk_draw_line (
							widget->window,
							priv->gc,
							drect.x,
							drect.y + floor (
							  mh4 * m),
							drect.x + drect.width,
							drect.y + floor (
							  mh4 * m));
					}
				}

				/* Draw events */
				/* Logic for drawing events visible from the
				 * previous/next month in day/week/month view
				 */
				if (k < 1) {
					io = i-2;
					ko = (k + time_days_in_month (
						year, i - 2)) - 1;
				} else if (k > dim) {
					io = i;
					ko = (k - dim) - 1;
				} else {
					io = i-1;
					ko = k-1;
				}

				for (e = priv->event_days[io][ko];
				     e; e = e->next) {
					dates_view_draw_event (view,
						(DatesViewEventData *)
							e->data, TRUE, i, k);
				}

				/* 
				 * For the date in the corner; in the case of
				 * the currently selected day or that the date
				 * is today explictly draw. In other
				 * circumstances copy the pre-drawn pixmap
				 */

				if (today || selected_day)
				{
					/* Draw filled rectange */
					gdk_gc_set_foreground (priv->gc,
						&widget->style->white);
					gdk_draw_rectangle (widget->window,
						priv->gc, TRUE,
						drect.x, drect.y,
						priv->corner_width,
						priv->corner_height);

					/* Draw outline */
					if (today && !selected_day)
						gdk_gc_set_foreground (priv->gc,
							&priv->blue[2]);
					else
						gdk_gc_set_foreground (priv->gc,
							&widget->style->black);

					if (selected_day)
						gdk_gc_set_line_attributes (priv->gc, 2,
							GDK_LINE_SOLID, GDK_CAP_BUTT,
							GDK_JOIN_MITER);

					gdk_draw_rectangle (widget->window,
						priv->gc, FALSE,
						drect.x, drect.y,
						priv->corner_width,
						priv->corner_height);

					/* Get the bounding box for this day */
					pango_layout_get_extents (
						priv->date_layouts[ko], NULL,
						&layout_rect);

					/* Calculate the origin for the text
					 * such that it will be centered in the
					 * corner box.
					 */
					x = (priv->corner_width - PANGO_PIXELS (
						layout_rect.width)) >> 1;
					y = (priv->corner_height - PANGO_PIXELS(
						layout_rect.height)) >> 1;

					/* Set the wrap width for the content */
					pango_layout_set_width (
						priv->date_layouts[ko],
						(drect.width - (BORDER * 2)) *
						PANGO_SCALE);

					/* Draw the day number */
					gdk_draw_layout (widget->window,
						priv->gc,
						drect.x + x,
						drect.y + y,
						priv->date_layouts[ko]);
				} else {
				
					gdk_draw_drawable (widget->window,
							priv->gc,
							priv->date_pixmaps[ko],
							0, 0,
							drect.x,
							drect.y,
							-1, -1);

				}
				/* Only draw Marcus Bains line when we know
				 * its okay */
				if ((today) && (priv->months == 0) &&
						priv->show_marcus_bains) {
					/* Draw line at current time */
					gdouble day_progress;
					gdouble line;

					gdk_gc_set_line_attributes (priv->gc, 1,
						GDK_LINE_ON_OFF_DASH, GDK_CAP_BUTT,
						GDK_JOIN_MITER);

					day_progress = ((priv->now.hour * 60) +
						priv->now.minute)/
							(gdouble)(60 * 24);

					line = drect.y + (
						day_progress * drect.height);
					gdk_gc_set_foreground (priv->gc,
						&priv->red[2]);
					gdk_draw_line (widget->window,
						priv->gc,
						drect.x, line,
						drect.x + drect.width, line);
				}

				/* Reset the gc */
				gdk_gc_set_line_attributes (priv->gc, 1,
					GDK_LINE_SOLID, GDK_CAP_BUTT,
					GDK_JOIN_MITER);

			} else if (priv->months > 1){
				/* Draw a GtkCalendar-style month summary if 
				 * we're viewing multiple months.
				 */
				PangoLayout *layout;
				if (g_list_length (
				    priv->event_days[i-1][k-1]) > 0) {
					layout = priv->bdate_layouts[k-1];
				} else {
					layout = priv->date_layouts[k-1];
				}

				pango_layout_get_extents (
					layout, NULL, &layout_rect);
				gdk_draw_layout (widget->window, priv->gc,
					(drect.x + (drect.width/2)) -
					 PANGO_PIXELS (layout_rect.width/2),
					(drect.y + (drect.height/2)) -
					 PANGO_PIXELS (layout_rect.height/2),
					layout);

				gdk_gc_set_line_attributes (priv->gc, 1,
					GDK_LINE_SOLID, GDK_CAP_BUTT,
					GDK_JOIN_MITER);
			}
			
			if (dow == 6) l++;
		}
		
		if (i % priv->months_in_row == 0) j++;
	}
	
	/* Don't mess up the XOR/INVERT when moving/sizing */
	priv->first_move = TRUE;
	
	return TRUE;
}

static gboolean
dates_view_main_scroll_event (GtkWidget		*widget,
			      GdkEventScroll	*event,
			      DatesView		*view)
{
	DatesViewPrivate *priv;
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	if (priv->scrollable) {
	}
	
	return FALSE;
}

static DatesViewEventData *
dates_view_point_in_event (DatesView *view, gint x, gint y)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	GList *e;
	
	for (e = priv->events; e; e = e->next) {
		DatesViewEvent *event = e->data;
		GList *d;
		for (d = event->draw_data; d; d = d->next) {
			DatesViewEventData *data =
				(DatesViewEventData *)d->data;
			
#ifdef DEBUG
			if (priv->debug & DATES_VIEW_DEBUG_DRAW) {
				if (data->rect)
					g_debug ("Event has rect (%d,%d,%d,%d),"
						" point pos %d,%d",
						data->rect->x, data->rect->y,
						data->rect->width,
						data->rect->height, x, y);
			}
#endif


			if (data->rect &&
			    dates_view_point_in (data->rect, x, y)) {
				/* If it's the selected event, we need to check
				 * that it's really in the visible range, as
				 * you can select an event that's off-screen
				 */
				if ((priv->selected_event == data) &&
				    ((icaltime_compare_date_only (data->date,
				      priv->start) < 0) || 
				     (icaltime_compare_date_only (data->date,
				      priv->end) >= 0)))
					continue;
				return data;
			}
		}
	}
	
	return NULL;
}

static DatesViewOperation
dates_view_handle_mouse_pos (DatesView *view, gint x, gint y,
			     DatesViewEventData *data)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	GdkRectangle *rect;

	if (!data) {
		gdk_window_set_cursor (priv->main->window, NULL);
		return DATES_VIEW_NONE;
	}

	rect = data->rect;
	if (dates_view_point_in (rect, x, y)) {
		gboolean read_only = TRUE;
		GError *error = NULL;
		GdkRectangle grip_rect;
		
		/* Don't allow moving/sizing of read-only/recurring events */
		if (!e_cal_is_read_only (
		    data->parent->cal->ecal, &read_only, NULL) || read_only ||
		    e_cal_component_has_recurrences (data->parent->comp)) {
			gdk_window_set_cursor (priv->main->window, NULL);
			if (error) {
				g_warning ("Error reading read-only "
					"state of calendar: %s",
					error->message);
				g_error_free (error);
				return DATES_VIEW_NONE;
			}
			return DATES_VIEW_PICK;
		}
		
		if (data->last) {
			grip_rect.x = (rect->x + rect->width) -
				(GRIPS * 4);
			grip_rect.y = (rect->y + rect->height) -
				(GRIPS * 4);
			grip_rect.width = (GRIPS * 4);
			grip_rect.height = (GRIPS * 4);
			if ((priv->months == 0) &&
			    (dates_view_point_in (&grip_rect, x, y))) {
				gdk_window_set_cursor (
					priv->main->window,
					priv->lr_cursor);
					
				return DATES_VIEW_SIZE;
			}
		}

		gdk_window_set_cursor (
			priv->main->window,
			priv->move_cursor);
		
		return DATES_VIEW_MOVE;
	}

	gdk_window_set_cursor (priv->main->window, NULL);
	
	return DATES_VIEW_NONE;
}

static void
dates_view_refresh_event (DatesView *view, DatesViewEvent *event)
{
	GList *objects;
	
	/* NOTE: Fire a synthetic change event, used mostly for refreshing
	 * event data of selected events that aren't visible or are only
	 * partially visible.
	 */
#if DEBUG
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	if ((priv->debug & DATES_VIEW_DEBUG_EDIT) ||
	    (priv->debug & DATES_VIEW_DEBUG_QUERY))
		g_debug ("Manually refreshing event");
#endif
	objects = g_list_prepend (NULL,
		e_cal_component_get_icalcomponent (event->comp));
	dates_view_objects_changed (event->cal->calview, objects, view);
	g_list_free (objects);
}

static gboolean
dates_view_main_button_press (GtkWidget		*widget,
			      GdkEventButton	*event,
			      DatesView		*view)
{
	DatesViewPrivate *priv;
	
	priv = DATES_VIEW_GET_PRIVATE (view);
		
	gtk_widget_grab_focus (GTK_WIDGET (view));

	/* Handle left mouse-button */
	if (event->button == 1) {
		gint month, day;
		gboolean in_region;
		struct icaltimetype new_date = *priv->date;

		if (event->type == GDK_2BUTTON_PRESS)
			priv->double_click = TRUE;

		in_region = dates_view_in_region (view, event->x, event->y,
			&month, &day) ? TRUE : FALSE;

		if ((priv->months <= 1) && (!priv->read_only)) {
			gboolean unselected = FALSE;
			DatesViewEventData *data =
				dates_view_point_in_event (view,
					event->x, event->y);
			
			if (priv->start_select != priv->end_select) {
				priv->start_select = priv->end_select = 0;
				dates_view_redraw (view);
			}

			if (data) {
				if (data != priv->selected_event) {
					if (priv->selected_event)
						g_signal_emit (view,
							signals[COMMIT_EVENT],
							0);
					unselected = TRUE;
					priv->unselected = TRUE;
					priv->selected_event = data;
					dates_view_redraw (view);
#ifdef DEBUG
					if (priv->debug & DATES_VIEW_DEBUG_EDIT)
						g_debug ("Selecting new event");
#endif
				}
				priv->offset_x = (event->x - data->rect->x) /
					data->rect->width;
				priv->offset_y = (event->y - data->rect->y) /
					data->rect->height;
			} else if (priv->selected_event) {
				DatesViewEventData *data = priv->selected_event;

				unselected = TRUE;
				g_signal_emit (view, signals[COMMIT_EVENT], 0);
				priv->selected_event = NULL;
				priv->unselected = TRUE;
				/* Synthetic change event to get rid of any
				 * data that we hung onto because it was
				 * selected.
				 */
				dates_view_refresh_event (view, data->parent);
				
				g_signal_emit (
					view, signals[EVENT_SELECTED], 0);
				dates_view_redraw (view);
			}
			
			priv->operation = dates_view_handle_mouse_pos (
					view, event->x, event->y, data);
		
			priv->current_x = priv->initial_x = event->x;
			priv->current_y = priv->initial_y = event->y;
			priv->first_move = TRUE;
			
#ifdef WITH_DND
			if (priv->operation & DATES_VIEW_MOVE) {
				/* Begin DND drag */
#ifdef DEBUG
				if (priv->debug & DATES_VIEW_DEBUG_DND)
					g_debug ("Beginning drag");
#endif
				priv->drag_source = TRUE;
				gtk_drag_begin (
				    priv->main, 
				    gtk_target_list_new (
					dates_view_drag_targets,
					G_N_ELEMENTS (dates_view_drag_targets)),
				    GDK_ACTION_COPY, 1, (GdkEvent *) event);
			}
#endif
		}
		
		if (in_region && (priv->operation == DATES_VIEW_NONE)) {
			new_date.month = month;
			if (day != G_MAXINT) {
				if ((priv->dragbox) && (priv->months == 0)) {
					priv->select_day = day;
					priv->operation = DATES_VIEW_SEL;
				} else
					new_date.day = day;
			}
			dates_view_set_date (view, &new_date);
		}
	}
	return FALSE;
}

static gint
dates_view_snap_time (gint minute, guint snap)
{
	gint i, last, adjust;
	gboolean neg = FALSE;
	
	if (snap <= 1) return 0;
	
	if (minute < 0) {
		neg = TRUE;
		minute = -minute;
	}
	
	for (i = 0, last = 0; i < minute; i += snap) {
		if ((last < minute) && (i > minute))
			break;
		last = i;
	}
	adjust = ((minute - last) < (i - minute)) ?
		last - minute : i - minute;
		
	return neg ? -adjust : adjust;
}

static void dates_view_update_all_queries (DatesView *view);

static void
dates_view_main_button_release_cb (DatesView *view, gint x, gint y)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	struct icaltimetype new_date = *priv->date;
	
	if ((priv->operation & DATES_VIEW_MOVE) ||
	    (priv->operation & DATES_VIEW_SIZE) ||
	    (priv->operation & DATES_VIEW_PICK)) {
		/* Find when mouse cursor is */
		icaltimetype mouse_date = *priv->date;
		ECalComponent *comp =
			dates_view_get_selected_event (view);
		
		if (dates_view_in_region (view, x, y,
		    &mouse_date.month, &mouse_date.day)) {
			gint hoffset = 0, mnoffset = 0;
			gboolean moved = FALSE, sized = FALSE;
			
			if (priv->months == 0) {
				gint initial_y;

				/* NOTE: Work out initial_y here as it
				 * might not be the actual initial_y if
				 * the zoom has changed during the
				 * move.
				 */
				initial_y =
					priv->selected_event->rect->y +
					(priv->offset_y * priv->
					 selected_event->rect->height);
				mnoffset = (priv->current_y -
					initial_y) /
					(priv->main->
						allocation.height /
					(priv->hours * 60.0));
				hoffset = mnoffset / 60;
				mnoffset = mnoffset % 60;
				mnoffset += dates_view_snap_time (
					mnoffset, priv->snap);
			}
			
			if (priv->unselected) {
				priv->unselected = FALSE;
				g_signal_emit (view,
					signals[EVENT_SELECTED], 0);
			}
			if (priv->operation & DATES_VIEW_MOVE) {
				gint yoffset, moffset, doffset;
				
				/* Do this relative to the selected
				 * date, as the selected date will be
				 * where the initial mouse click was
				 */
				yoffset = mouse_date.year -
					priv->selected_event->
						date.year;
				moffset = mouse_date.month ? 
					mouse_date.month -
					priv->selected_event->
						date.month : 0;
				doffset = (mouse_date.day != G_MAXINT) ?
					mouse_date.day -
					priv->selected_event->
						date.day : 0;

				/* Normalise day offset if it isn't zero -
				 * Done so we can activate days that are
				 * possibly visible but not in the current
				 * month.
				 */
				if (doffset != 0) {
					while (doffset >= time_days_in_month (
					       priv->selected_event->date.year,
					       priv->selected_event->date.month
					       + moffset - 1)) {
						doffset -= time_days_in_month (
							priv->selected_event->
								date.year,
							priv->selected_event->
								date.month
								+ moffset - 1);
						moffset ++;
					}
					while (doffset < 0) {
						doffset += time_days_in_month (
							priv->selected_event->
								date.year,
							priv->selected_event->
								date.month
								+ moffset - 2);
						moffset --;
					}
				}

				if ((yoffset != 0) || (moffset != 0) ||
				    (doffset != 0) || (hoffset != 0) ||
				    (mnoffset != 0)) {
					ECalComponentDateTime start, end;
					e_cal_component_get_dtstart (
						comp, &start);
					e_cal_component_get_dtend (
						comp, &end);
					
					start.value->year += yoffset;
					start.value->month += moffset;
					icaltime_adjust (start.value,
						doffset, hoffset,
						mnoffset, 0);
					end.value->year += yoffset;
					end.value->month += moffset;
					icaltime_adjust (end.value, 
						doffset, hoffset,
						mnoffset, 0);
					
					e_cal_component_set_dtstart (
					    comp, &start);
					e_cal_component_set_dtend (
					    comp, &end);
					
					e_cal_component_free_datetime (
						&start);
					e_cal_component_free_datetime (
						&end);

#ifdef DEBUG
					if (priv->debug &
					    DATES_VIEW_DEBUG_EDIT)
					g_debug ("Moving event(%p-%p): "
						"%d/%d/%d %d:%d",
						priv->selected_event->
							parent->comp,
						comp,
						doffset, moffset,
						yoffset, hoffset,
						mnoffset);
#endif
					g_signal_emit (view,
					    signals[EVENT_MOVED], 0,
					    comp);
					moved = TRUE;
				}
			} else if ((priv->operation & DATES_VIEW_SIZE)
				   && ((hoffset != 0) ||
				       (mnoffset != 0))) {
				ECalComponentDateTime /*start,*/end;
				struct icaldurationtype duration;
				
/*					e_cal_component_get_dtstart (
					comp, &start);*/
				e_cal_component_get_dtend (
					comp, &end);
					
#ifdef DEBUG
				if (priv->debug & DATES_VIEW_DEBUG_EDIT)
					g_debug ("Sizing event(%p-%p):"
						" %d:%d",
						priv->selected_event->
							parent->comp,
						comp, hoffset,
						mnoffset);
#endif
				
				/* Calculate duration on selected span
				 * or this will break on events
				 * spanning > 1 day.
				 */
				duration = icaltime_subtract (
					icaltime_from_timet (
					    priv->selected_event->start,
					    FALSE),
					icaltime_from_timet (
					    priv->selected_event->end,
					    FALSE));
				
#ifdef DEBUG
				if (priv->debug & DATES_VIEW_DEBUG_EDIT)
					g_debug ("Duration: %d:%d",
						duration.hours,
						duration.minutes);
#endif
				/* Make sure you can't size events
				 * backwards.
				 */
				if (hoffset <= -(gint)duration.hours) {
					hoffset = -(gint)duration.hours;
					if (mnoffset <
					    -(gint)duration.minutes)
					    mnoffset =
					    -(gint)duration.minutes +
					    	priv->snap;
				}

				if ((hoffset > -(gint)duration.hours) ||
				    ((hoffset ==
				     	-(gint)duration.hours) &&
				     (mnoffset >
				     	-(gint)duration.minutes))) {
					icaltime_adjust (end.value, 0,
						hoffset, mnoffset, 0);

					e_cal_component_set_dtend (
						comp, &end);
				}/* else {
					*end.value = *start.value;
					icaltime_adjust (start.value, 0,
						hoffset +
						   duration.hours,
						mnoffset +
						   duration.minutes, 0);
					
					e_cal_component_set_dtstart (
						comp, &start);
					e_cal_component_set_dtend (
						comp, &end);
					
				}*/
				
/*					e_cal_component_free_datetime (
					&start);*/
				e_cal_component_free_datetime (
					&end);
				sized = TRUE;
				g_signal_emit (view,
					signals[EVENT_SIZED], 0, comp);
			}
			if ((priv->single_click || priv->double_click)&&
			    !(moved || sized)) {
#ifdef DEBUG
				if (priv->debug & DATES_VIEW_DEBUG_EDIT)
					g_debug ("Activating event");
#endif
				g_signal_emit (view,
					signals[EVENT_ACTIVATED], 0);
				priv->double_click = FALSE;
			}
			
			/* Refresh the event before any changes are
			 * committed, to avoid jumpiness
			 */
			if (moved || sized)
				dates_view_refresh_event (view,
					priv->selected_event->parent);
			
			/* Select the date the event is on, if it
			 * wasn't sized.
			 */
			if (/*(!moved) &&*/ (!sized)) {
				new_date.month = mouse_date.month;
				if (mouse_date.day != G_MAXINT)
					new_date.day = mouse_date.day;
				dates_view_set_date (view, &new_date);
			}
			dates_view_redraw (view);
		}
		
		g_object_unref (comp);
		gdk_window_set_cursor (priv->main->window, NULL);
	} else if (priv->operation & DATES_VIEW_SEL) {
		/* Store the selection as a ratio of the month rect */
		GdkRectangle *rect = dates_view_get_region (view,
			priv->date->month, priv->date->day);
		priv->start_select = (
			MIN (priv->initial_y, priv->current_y) -
				rect->y) / (gdouble)rect->height;
		priv->end_select = (
			MAX (priv->current_y, priv->initial_y) -
				rect->y) / (gdouble)rect->height;
		if (priv->start_select < 0) priv->start_select = 0;
		if (priv->end_select > 1) priv->end_select = 1;
		
		/* Select the day the selection was made on */
		new_date.day = priv->select_day;
		dates_view_set_date (view, &new_date);
		if (priv->start_select != priv->end_select) 
			dates_view_redraw (view);
	}
	priv->operation = DATES_VIEW_NONE;
}

static gboolean
dates_view_main_button_release (GtkWidget	*widget,
				GdkEventButton	*event,
				DatesView	*view)
{
	DatesViewPrivate *priv;
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	if (event->button == 1) {
#ifdef WITH_DND
		if (!(priv->operation & DATES_VIEW_MOVE))
#endif
			dates_view_main_button_release_cb (
				view, priv->current_x, priv->current_y);
	}
	
	return FALSE;
}
#ifdef WITH_DND
static gboolean
dates_view_drag_drop (GtkWidget 	*widget,
		      GdkDragContext 	*dc,
		      gint 		x,
		      gint 		y,
		      guint 		t,
		      DatesView 	*view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	if (priv->operation & DATES_VIEW_MOVE) {
		/* The drop came from this app, ignore data and move event */
#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_DND)
			g_debug ("drag-drop from this source");
#endif
		gtk_drag_finish (dc, TRUE, FALSE, t);
		dates_view_main_button_release_cb (view, x, y);
		return FALSE;
	} else {
#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_DND)
			g_debug ("drag-drop from another source");
#endif
		priv->drag_source = FALSE;
		return TRUE;
	}
}

static void
dates_view_drag_end (GtkWidget *widget, GdkDragContext *dc, DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	
#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_DND)
		g_debug ("drag end");
#endif
	if (priv->operation & DATES_VIEW_MOVE) {
		/* If the user taps the event quickly and lets go, sometimes
		 * this starts the drag without triggering a drag-drop or any
		 * motion events - in this case, we want to act as if they
		 * tapped the event - otherwise we want to just cancel.
		 */
		if ((priv->current_x == priv->initial_x) &&
		    (priv->current_y == priv->initial_y))
			dates_view_main_button_release_cb (
				view, priv->initial_x, priv->initial_y);
	}

	priv->operation = DATES_VIEW_NONE;
	dates_view_redraw (view);
}
#endif
static gboolean
dates_view_main_motion_notify_cb (gpointer data)
{
	DatesView *view = DATES_VIEW (data);
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	GList *d;

	if ((!(priv->operation & DATES_VIEW_MOVE)) &&
	    (!(priv->operation & DATES_VIEW_SIZE))) {
		dates_view_redraw (view);
		return FALSE;
	}

	gdk_x11_display_grab (gdk_drawable_get_display (priv->main->window));
	if (priv->first_move) {
		dates_view_redraw (view);
		/* We need this redraw to happen straight away */
		gdk_window_process_all_updates ();
		priv->first_move = FALSE;

		gdk_gc_set_function (priv->selected_event->parent->cal->dark_gc,
			GDK_INVERT);
		gdk_gc_set_function (priv->selected_event->parent->
			cal->gc, GDK_INVERT);
	} else {
		gint old_x, old_y;
		
		gdk_gc_set_function (priv->selected_event->parent->cal->dark_gc,
			GDK_INVERT);
		gdk_gc_set_function (priv->selected_event->parent->
			cal->gc, GDK_INVERT);

		old_x = priv->current_x;
		old_y = priv->current_y;
		priv->current_x = priv->prev_x;
		priv->current_y = priv->prev_y;
		if (priv->selected_event->parent->draw_data) {
			for (d = priv->selected_event->parent->draw_data; d;
			     d = d->next) {
				dates_view_draw_event (view,
					(DatesViewEventData *)d->data, FALSE,
					0, 0);
			}
		} else
			dates_view_draw_event (view,
				priv->selected_event, FALSE, 0, 0);
		priv->current_x = old_x;
		priv->current_y = old_y;
	}
	
	/* Only redraw selected event using inverted */
	if (priv->selected_event->parent->draw_data) {
		for (d = priv->selected_event->parent->draw_data; d;
		     d = d->next) {
			dates_view_draw_event (view,
				(DatesViewEventData *)d->data, FALSE, 0, 0);
		}
	} else
		dates_view_draw_event (view, priv->selected_event, FALSE, 0, 0);
	gdk_x11_display_ungrab (gdk_drawable_get_display (priv->main->window));

	gdk_gc_set_function (priv->selected_event->parent->cal->gc, GDK_COPY);
	gdk_gc_set_function (priv->selected_event->parent->cal->dark_gc,
		GDK_COPY);
	
	priv->prev_x = priv->current_x;
	priv->prev_y = priv->current_y;

#ifdef WITH_DND	
	priv->motion_idle = 0;
#endif

	return FALSE;
}

static gboolean
dates_view_main_motion_notify (GtkWidget	*widget,
			       GdkEventMotion	*event,
			       DatesView	*view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	gint x, y;
	GdkModifierType mask;

	gdk_window_get_pointer (priv->main->window, &x, &y, &mask);
	
	if (priv->operation) {
		if ((mask & GDK_BUTTON1_MASK)) {
			priv->current_x = x;
			priv->current_y = y;
			
			dates_view_main_motion_notify_cb (view);
		}
		
	} else if ((priv->months <= 1) && (!priv->read_only)) {
		DatesViewEventData *data =
			dates_view_point_in_event (view,
				x, y);
		dates_view_handle_mouse_pos (view, x, y, data);
	}
	
	return FALSE;
}

#ifdef WITH_DND
static void
dates_view_drag_motion (GtkWidget 	*widget,
			GdkDragContext 	*dc,
			gint x,
			gint y,
			guint t,
			DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_DND)
		g_debug ("drag motion");
#endif
	if (priv->operation) {
		/* If we don't reach the threshold abort the drag. See #127. */
		if (!gtk_drag_check_threshold (GTK_WIDGET (view), 
		      priv->initial_x, priv->initial_y, x, y))
		{
			return;
		}
		priv->current_x = x;
		priv->current_y = y;
		if (priv->motion_idle == 0)
			priv->motion_idle = g_idle_add ((GSourceFunc)
				dates_view_main_motion_notify_cb, view);
	}
}
#endif

static gboolean
dates_view_top_expose (GtkWidget      *widget,
		       GdkEventExpose *event,
		       DatesView      *view)
{
	DatesViewPrivate *priv;
	gint i, j, visible_months, offset;
	gdouble width, moffset;
	GdkGC *gc;

	priv = DATES_VIEW_GET_PRIVATE (view);
	gc = priv->gc;

	moffset = (priv->main->allocation.width - (2 * BORDER));
	width = moffset / (gdouble)priv->days;
	visible_months = MIN (priv->months, priv->months_in_row);
	if (visible_months == 0) visible_months = 1;
	width /= visible_months;
	moffset /= visible_months;
	
	offset = IDOWSTART(*priv->date, priv->week_start) - priv->days;
	if (offset < 0) offset = 0;
			
	for (j = 0; j < visible_months; j++) {
		for (i = 0; i < priv->days; i++) {
			PangoLayout *playout;
			GdkRectangle clip_rect;
#ifndef DATES_ABREV_DAY_LABELS
			if(visible_months <= 1)
				playout = priv->day_layouts[i+offset];
			else
#endif
				playout = priv->abday_layouts[i+offset];

			/*
			 * Use black writing for the names if we are in single
			 * month mode.
			 */
			if (visible_months > 1)
				gdk_gc_set_foreground (gc,
						&priv->grey[0]);
			else
				gdk_gc_set_foreground (gc,
						&widget->style->black);
			
			pango_layout_set_width (playout,
				(width - BORDER) * PANGO_SCALE);

			clip_rect.x = 0;
			clip_rect.y = 0;
			clip_rect.width = priv->top->allocation.width;
			clip_rect.height = priv->top->allocation.height -
				(BORDER * 2);
				
			gdk_gc_set_clip_rectangle (
				gc,
				&clip_rect);
			gdk_draw_layout (
				widget->window,
				gc,
				(BORDER * 1.5) + (width * i) +
					(GTK_WIDGET_VISIBLE (priv->side) ?
					 priv->side->allocation.width : 0) +
					(moffset*j),
				BORDER/2,
				playout);
		}
	}
	gdk_gc_set_clip_rectangle (
		gc,
		NULL);
	
	return FALSE;
}

static gboolean
dates_view_side_expose (GtkWidget      *widget,
			GdkEventExpose *event,
			DatesView      *view)
{
	DatesViewPrivate *priv;
	gdouble height, pany = 0;
	gint i, skip;
    
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	if (priv->scrollable) {
		height = (priv->main->allocation.height - BORDER) /
			(gdouble)priv->hours;

		pany +=  gtk_adjustment_get_value (priv->adjust) * height;
	} else {
		height = ((gdouble)priv->main->allocation.height -
			(1.5 * BORDER)) / (gdouble)priv->hours;
	}

	skip = ceil ((PANGO_PIXELS (priv->time_layouts_height) +
		(BORDER/2))/(gdouble)height);
    
	if (skip < 1) skip = 1;
	
	pany += (PANGO_PIXELS (priv->time_layouts_height)/2) - (BORDER/2) -
		(priv->main->allocation.y - widget->allocation.y);
	
	for (i = skip; i < 24; i += skip) {
		gdk_draw_layout (
			widget->window,
			widget->style->black_gc,
			BORDER,
			(i * height) - pany,
			priv->time_layouts[i-1]);
	}

	return FALSE;
}

static gboolean
dates_view_key_press (GtkWidget *widget, GdkEventKey *event, DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);

	gint direction = 0;
#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_EDIT)
		g_debug ("Key pressed: %d", event->keyval);
#endif
	switch (event->keyval) {
		case GDK_Up :
			direction = -1;
		case GDK_Down :
			if (direction == 0) direction = 1;
			
			/* TODO: Scroll as well as move selection */
/*			if (GTK_WIDGET_VISIBLE (priv->vscroll)) {
				gtk_widget_event (priv->vscroll,
					(GdkEvent *)event);
				return FALSE;
			}*/
			
			if (priv->months > 1) {
				/* Scroll months */
				struct icaltimetype new_date =
					*(dates_view_get_date (view));
				new_date.month += direction *
					priv->months_in_row;
				dates_view_set_date (view, &new_date);
			} else {
				/* Scroll days */
				struct icaltimetype new_date =
					*(dates_view_get_date (view));
				icaltime_adjust (&new_date, direction * 7,
					0, 0, 0);
				dates_view_set_date (view, &new_date);
			}
			return TRUE;
		case GDK_Left :
			direction = -1;
		case GDK_Right :
			if (direction == 0) direction = 1;

			if (priv->months > 1) {
				/* Scroll months */
				struct icaltimetype new_date =
					*(dates_view_get_date (view));
				new_date.month += direction;
				dates_view_set_date (view, &new_date);
			} else {
				/* Scroll days */
				struct icaltimetype new_date =
					*(dates_view_get_date (view));
				icaltime_adjust (&new_date, direction, 0, 0, 0);
				dates_view_set_date (view, &new_date);
			}
			return TRUE;
		case GDK_3270_Enter :
		case GDK_ISO_Enter :
		case GDK_KP_Enter :
		case GDK_Return :
			if (priv->selected_event) {
				g_signal_emit (view,
					signals[EVENT_ACTIVATED], 0);
				return TRUE;
			} else
				return FALSE;
		case GDK_KP_Tab :
		case GDK_ISO_Left_Tab :
		case GDK_Tab : {
			gint m, m_start, m_end, neg = 1;
			DatesViewEventData *new_sel = NULL;
			gboolean next = priv->selected_event ? FALSE : TRUE;

			if (priv->months > 1)
				return FALSE;
			
			/* Go backwards on shift+tab */
			if (event->state & GDK_SHIFT_MASK) neg = -1;

			/* Find next event */
			m_start = priv->start.month;
			m_end = (priv->start.year == priv->end.year) ?
				priv->end.month : 12;
			if (neg == -1) {
				gint old_start = m_start;
				m_start = -m_end;
				m_end = -old_start;
			}
			for (m = m_start; m <= m_end; m++) {
				gint d, d_start, d_end, month;
				month = m * neg;
				d_start = (month == priv->start.month) ?
					priv->start.day : 1;
				d_end = (month == priv->end.month) ?
					((priv->start.year == priv->end.year) ?
						priv->end.day : 31) : 31;
				if (neg == -1) {
					gint old_start = d_start;
					d_start = -d_end;
					d_end = -old_start;
				}
				for (d = d_start; d <= d_end; d++) {
					gint day;
					GList *e;
					day = d * neg;
					e = priv->event_days[month-1][day-1];
					if (neg == -1) e = g_list_last (e);
					for (; e; e = ((neg == 1) ?
					     e->next : e->prev)) {
						DatesViewEventData *data =
							(DatesViewEventData *)
								e->data;
						if (next) {
							new_sel = data;
							break;
						}
						if (data == priv->
						    selected_event)
							next = TRUE;
					}
					if (new_sel) break;
				}
				if (new_sel) break;
			}
			
			/* Set next event selected (if it exists) */
			if (new_sel) {
				/* Unselect current event */
				if (priv->selected_event) {
					g_signal_emit (view,
						signals[COMMIT_EVENT], 0);
					priv->selected_event = NULL;
					priv->unselected = TRUE;
					g_signal_emit (view,
						signals[EVENT_SELECTED], 0);
				}
				priv->selected_event = new_sel;
				priv->unselected = FALSE;
				g_signal_emit (view,
					signals[EVENT_SELECTED], 0);
				
				dates_view_redraw (view);
				return TRUE;
			}

			return FALSE;
		}
	}
	
	return FALSE;
}

/** dates_view_new:
 * 
 * This creates a new #DatesView widget.
 * 
 * Return value: The new #DatesView widget
 **/
GtkWidget 
*dates_view_new ()
{
	DatesView *view;
	
	view = g_object_new (DATES_TYPE_VIEW, NULL);
	
	return (GtkWidget *)view;
}

static void
dates_view_remove_calendar_events (DatesView *view, DatesViewCalendar *cal,
				   gboolean remove_selected)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	GList *e;

	if (priv->selected_event &&
	    (priv->selected_event->parent->cal == cal)) {
		if (remove_selected) {
			/* Cancel moving/sizing events */
			priv->operation = DATES_VIEW_NONE;
			g_signal_emit (view, signals[COMMIT_EVENT], 0);
			priv->selected_event = NULL;
			priv->unselected = TRUE;
			g_signal_emit (view, signals[EVENT_SELECTED], 0);
		} else {
			/* Cancel sizing events, it doesn't make any sense
			 * when changing dates
			 */
			if (priv->operation & DATES_VIEW_SIZE)
				priv->operation = DATES_VIEW_NONE;
				
			/* NOTE: As the selected event might not be re-filed,
			 * re-create layouts here in case zoom level changes.
			 */
			dates_view_make_event_layouts (
				view, priv->selected_event->parent);
		}
	}

	e = priv->events;
	while (e) {
		DatesViewEvent *event = (DatesViewEvent *)e->data;
		e = e->next;
		if (event->cal == cal) {
			if ((priv->selected_event) &&
			    (!remove_selected) &&
			    (priv->selected_event->parent == event))
				continue;
			priv->events = g_list_remove (
				priv->events, event);
			dates_view_event_free (event);
		}
	}
}

static void
dates_view_update_query (DatesView *view, DatesViewCalendar *cal)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	gchar *query, *month_begin, *month_end;
/*	gint soffset, eoffset;*/
	icaltimetype start, end;

	/* Adjust time for when the previous/next month is slightly visible */
	start = priv->start;
	end = priv->end;
/*	dates_view_get_extra_visible (view, &soffset, &eoffset);
	icaltime_adjust (&start, -soffset, 0, 0, 0);
	icaltime_adjust (&end, eoffset, 0, 0, 0);*/
	
	month_begin = isodate_from_time_t (
		icaltime_as_timet_with_zone (start, priv->zone));
	month_end = isodate_from_time_t (
		icaltime_as_timet_with_zone (end, priv->zone));

	/* NOTE: occur-in-time-range should take recurrences
	 * into account - eds bug? Needs verification.
	 */
	query = g_strdup_printf ("(occur-in-time-range? "
		"(make-time \"%s\") (make-time \"%s\"))",
		month_begin, month_end);
	/* (or (occur-in-time-range?
	 * (make-time \"%s\") (make-time \"%s\"))
	 * (has-recurrences?))
	 */

#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_QUERY)
		g_debug ("Query: %s", query);
#endif

	/* Remove old events */
	dates_view_remove_calendar_events (view, cal, FALSE);
	
	/* Fire synthetic change to remove unselected data from a selected
	 * event.
	 */
	if (priv->selected_event && (priv->selected_event->parent->cal == cal))
		dates_view_refresh_event (view, priv->selected_event->parent);
	
	if (cal->calview)
		g_object_unref (cal->calview);

	/* Get a live calendar query */
	if (!e_cal_get_query (cal->ecal, query, &cal->calview, NULL))
		g_warning ("Failed to get calendar query");
	else {
		/* Attach signals and start view */
		g_signal_connect (cal->calview, "objects-added",
			G_CALLBACK (dates_view_objects_changed), view);
		g_signal_connect (cal->calview, "objects-modified",
			G_CALLBACK (dates_view_objects_changed), view);
		g_signal_connect (cal->calview, "objects-removed",
			G_CALLBACK (dates_view_objects_removed), view);
			
		e_cal_view_start (cal->calview);
	}
	/* NOTE: Freeing this query results in a double-free */
	/* not freeing it results in a leak */
	g_free (query);
}

static void
dates_view_update_all_queries (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	GList *c;
	
	/* The visible span might've changed when all queries are updated, so
	 * cache it here.
	 */
	dates_view_get_visible_span (view, &priv->start, &priv->end);
	dates_view_get_visible_cspan (view, &priv->cstart, &priv->cend);
	
#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_QUERY)
		g_debug ("Calendar span: %d/%d/%d -> %d/%d/%d",
			priv->cstart.day, priv->cstart.month, priv->cstart.year,
			priv->cend.day, priv->cend.month, priv->cend.year);
#endif

	for (c = priv->calendars; c; c = c->next) {
		dates_view_update_query (
			view, (DatesViewCalendar *)c->data);
	}
}

/** dates_view_add_calendar:
 *
 * @view:	The #DatesView widget to add a calendar to
 * @ECal:	The #ECal to add
 *
 * This adds a calendar to the #DatesView widget.
 **/
void
dates_view_add_calendar (DatesView *view, ECal *ecal)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	DatesViewCalendar *cal;
	ESource *source;
	guint32 colour;
/*	GError *error = NULL;*/
	
	g_return_if_fail (E_IS_CAL (ecal));
	
	/* Make sure widget is realized before the possibility of handling
	 * events.
	 */
	if (!GTK_WIDGET_REALIZED (GTK_WIDGET (view)))
		gtk_widget_realize (GTK_WIDGET (view));

	cal = g_new0 (DatesViewCalendar, 1);
	cal->ecal = g_object_ref (ecal);
	/* NOTE: This causes a crash using eds-dbus? Investigate */
/*	if (!e_cal_set_default_timezone (ecal, priv->zone, &error)) {
		g_warning ("Error setting default calendar timezone:\n\t\"%s\"",
			error->message);
		g_error_free (error);
	}*/
	priv->calendars = g_list_prepend (priv->calendars, cal);

	/* Get calendar colours */
	source = e_cal_get_source (ecal);
	cal->gc = gdk_gc_new (
		GDK_DRAWABLE (priv->main->window));
	cal->dark_gc = gdk_gc_new (
		GDK_DRAWABLE (priv->main->window));
	cal->text_gc = gdk_gc_new (
		GDK_DRAWABLE (priv->main->window));

	if (e_source_get_color (source, &colour)) {
		GdkColor gcolour, dgcolour, tgcolour;
		gcolour.red = ((colour & 0xFF0000) >> 16) * 0x101;
		gcolour.green = ((colour & 0xFF00) >> 8)  * 0x101;
		gcolour.blue = (colour & 0xFF) * 0x101;

		/* This is simplified sRGB -> LAB conversion; we do not need it
		 * entirely precise, as we are only going to chose between
		 * black or white text based on the L value.
		 *
		 * (If we discover that there are some colours where the error
		 * due to simplification becomes signficant we could always
		 * adjust the decision threshold to be more/less than 50, but
		 * I doubt it will be necessary.)
		 */

		/* the 0xffff corresponds to 16-bit colour */
		double y = (  0.2126 * (double)gcolour.red
			    + 0.7152 * (double)gcolour.green
			    + 0.0722 * (double)gcolour.blue)/
			(12.92*(double)0xffff);

		double L = 116.0 * pow (y*3, 1.0/3.0) - 16.0;

#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_DRAW)
		g_debug ("e-source has colour set; L = %g", L);
#endif
		if (L > 50)
		{
			tgcolour.red   = 0;
			tgcolour.green = 0;
			tgcolour.blue  = 0;
		} else {
			tgcolour.red   = 0xffff;
			tgcolour.green = 0xffff;
			tgcolour.blue  = 0xffff;
		}
		
		dgcolour = gcolour;
		dgcolour.red /= 1.5;
		dgcolour.green /= 1.5;
		dgcolour.blue /= 1.5;
		if (gdk_colormap_alloc_color (
		    gdk_gc_get_colormap (cal->gc),
		    &gcolour, TRUE, TRUE)) {
			gdk_gc_set_foreground (
				cal->gc, &gcolour);
		}
		if (gdk_colormap_alloc_color (
		    gdk_gc_get_colormap (cal->gc),
		    &dgcolour, TRUE, TRUE)) {
			gdk_gc_set_foreground (
				cal->dark_gc, &dgcolour);
		}
		if (gdk_colormap_alloc_color (
		    gdk_gc_get_colormap (cal->text_gc),
		    &tgcolour, TRUE, TRUE)) {
			gdk_gc_set_foreground (
				cal->text_gc, &tgcolour);
		}
		
	} else {
		/* If no source colour set, use gtk theme colours */
#ifdef DEBUG
		if (priv->debug & DATES_VIEW_DEBUG_DRAW)
			g_debug ("e-source does not have colour set - using theme");
#endif
		gdk_gc_copy (cal->gc, priv->main->style->light_gc[
			GTK_STATE_SELECTED]);
		gdk_gc_copy (cal->dark_gc, priv->main->style->dark_gc[
			GTK_STATE_SELECTED]);
		gdk_gc_copy (cal->text_gc, priv->main->style->fg_gc[
			GTK_STATE_NORMAL]);
	}
		
	dates_view_update_query (view, cal);
	
	dates_view_redraw (view);
}

static void
dates_view_free_calendar (DatesViewCalendar *cal)
{
	g_object_unref (cal->calview);
	g_object_unref (cal->ecal);
	gdk_gc_unref (cal->gc);
	gdk_gc_unref (cal->dark_gc);
	gdk_gc_unref (cal->text_gc);
	g_free (cal);
}

/** dates_view_remove_calendar:
 *
 * @view:	The #DatesView widget to remove a calendar from
 * @ECal:	The #ECal to remove
 *
 * This removes a particular calendar on the #DatesView widget.
 **/
void
dates_view_remove_calendar (DatesView *view, ECal *ecal)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	GList *c;
	DatesViewCalendar *cal;

	c = g_list_find_custom (priv->calendars, ecal,
		dates_view_find_calendar_cb);
	cal = c ? (DatesViewCalendar *)c->data : NULL;
	if (cal) {
		dates_view_remove_calendar_events (view, cal, TRUE);
		priv->calendars = g_list_remove (priv->calendars, cal);
		dates_view_free_calendar (cal);
		dates_view_redraw (view);
	}
}

/** dates_view_remove_all_calendars:
 *
 * @view:	The #DatesView widget to remove calendars from
 *
 * This removes all active calendars on the #DatesView widget.
 **/
void
dates_view_remove_all_calendars (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	while (priv->calendars) {
		DatesViewCalendar *cal =
			(DatesViewCalendar *)priv->calendars->data;
		dates_view_remove_calendar_events (view, cal, TRUE);
		priv->calendars = g_list_remove (priv->calendars, cal);
		dates_view_free_calendar (cal);
	}
	dates_view_redraw (view);
}

/** dates_view_get_selected_period:
 *
 * @view:	The #DatesView widget to retrieve the selected time period from
 * @period:	A pointer to a struct icalperiodtype to store the result
 *
 * This fills an #icalperiodtype that represents the currently selected time.
 **/
gboolean
dates_view_get_selected_period (DatesView *view, struct icalperiodtype *period)
{
	struct icaltimetype time;
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	
	if ((!priv->dragbox) || (priv->months != 0) ||
	    (priv->start_select == priv->end_select)) return FALSE;
	
	time = *(dates_view_get_date (view));
	time.is_date = FALSE;
	period->duration = icaldurationtype_null_duration ();
	period->duration.hours = 24 * priv->start_select;
	period->duration.minutes = (gint)(24 * 60 * priv->start_select) % 60;
	period->start = icaltime_add (time, period->duration);
	icaltime_adjust (&period->start, 0, 0, dates_view_snap_time (
		period->start.minute, priv->snap), 0);
	period->duration.hours = (gint)(24 * priv->end_select) -
		period->duration.hours;
	period->duration.minutes = (gint)(24 * 60 * priv->end_select) % 60;
	period->end = icaltime_add (period->start, period->duration);
	icaltime_adjust (&period->end, 0, 0, dates_view_snap_time (
		period->end.minute, priv->snap), 0);
	
	return TRUE;
}

/** dates_view_set_selected_event:
 *
 * @view:	The #DatesView widget to set the selected event on.
 * @uri_uid:	The calendar uri and uid of the event concatenated.
 *
 * This sets the currently selected #ECalComponent. This can be set to NULL to 
 * unselect an event.
 **/
gboolean
dates_view_set_selected_event (DatesView *view, const gchar *uri_uid,
			       const gchar *rid)
{
	gboolean returnval = TRUE;
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);

	/* TODO: Do something with rid here */		
	if (priv->selected_event) {
		/* Return success if event already selected */
		if (strcmp (uri_uid,
		    priv->selected_event->parent->uri_uid) == 0)
			return TRUE;
			
		priv->selected_event = NULL;
		priv->unselected = TRUE;
		g_signal_emit (view, signals[EVENT_SELECTED], 0);
	}
	
	if (uri_uid) {
		DatesViewEvent *event;
		
		if ((event = dates_view_find_event (view, uri_uid))) {
			priv->start_select = priv->end_select = 0;
			/* TODO: Check RID to get the right part of the event */
			priv->selected_event = event->draw_data->data;
			priv->unselected = FALSE;
			g_signal_emit (view, signals[EVENT_SELECTED], 0);
		} else
			returnval = FALSE;
	}
	dates_view_redraw (view);
	return returnval;
}

static gboolean
dates_view_get_event_cb (ECalComponent *comp, time_t start, time_t end,
			     gpointer user_data)
{
	struct icaltime_span *span = *(gpointer *)user_data;

	if ((span->start == start) && (span->end == end)) {
		/*g_debug ("Found event instance");*/
		*(gpointer *)user_data = g_object_ref (comp);
		return FALSE;
	}
	return TRUE;
}

/** dates_view_get_selected_event:
 *
 * @view:	The #DatesView widget to get the selected event on.
 *
 * This returns the currently selected #ECalComponent, or NULL if there isn't 
 * any. This must be g_object_unref'd after its been finished with.
 **/
ECalComponent *
dates_view_get_selected_event (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	gpointer pointer;

	if (!priv->selected_event) return NULL;
	
	if (e_cal_component_has_recurrences (priv->selected_event->
	    parent->comp)) {
		struct icaltime_span span;
		span.start = priv->selected_event->start;
		span.end = priv->selected_event->end;
		pointer = &span;
		e_cal_generate_instances_for_object (
			priv->selected_event->parent->cal->ecal,
			e_cal_component_get_icalcomponent (
				priv->selected_event->parent->comp),
			span.start, span.end,
			dates_view_get_event_cb, &pointer);
		if (pointer != &span)
			return (ECalComponent *)pointer;
		else {
			g_warning ("Couldn't find recurring event instance");
			return (ECalComponent *)g_object_ref (
				priv->selected_event->parent->comp);
		}
	} else
		return (ECalComponent *)g_object_ref (
			priv->selected_event->parent->comp);
}

/** dates_view_get_selected_event_cal:
 *
 * @view:	The #DatesView widget to get the selected event's calendar on.
 *
 * This returns the currently selected #ECalComponent's #ECal, or NULL if there 
 * isn't any.
 **/
ECal *
dates_view_get_selected_event_cal (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	if (priv->selected_event)
		return priv->selected_event->parent->cal->ecal;
	else
		return NULL;
}

/** dates_view_set_read_only:
 *
 * @view:	The #DatesView widget to alter the read-only property of.
 * @read_only:	Whether the widget should disallow editing.
 *
 * This changes the read-only property of the #DatesView widget.
 **/
void
dates_view_set_read_only (DatesView *view, gboolean read_only)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	priv->read_only = read_only;
}

/** dates_view_set_use_dragbox:
 *
 * @view:	The #DatesView widget to alter the read-only property of.
 * @enable:	Whether to allow events to be created using a drag-box.
 *
 * This enables or disables the ability to use a drag-box to create new events.
 **/
void
dates_view_set_use_dragbox (DatesView *view, gboolean enable)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	priv->dragbox = enable;
}

/** dates_view_set_single_click:
 *
 * @view:	The #DatesView widget to alter the read-only property of.
 * @enable:	Whether to use single-click event activation.
 *
 * This enables or disables single-click event activation.
 **/
void
dates_view_set_single_click (DatesView *view, gboolean enable)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	priv->single_click = enable;
}

/** dates_view_set_snap:
 *
 * @view:	The #DatesView widget to alter the snap property of.
 * @snap:	The multiple, in minutes, to snap events to.
 *
 * This sets the minutes to snap to when adjusting events/selecting ranges.
 **/
void
dates_view_set_snap (DatesView *view, guint snap)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	priv->snap = snap;
}

/** dates_view_set_week_start:
 *
 * @view:	The #DatesView widget to set the week start day of.
 * @day:	The day of the week (0-6, corresponding to Sunday-Saturday)
 *
 * This changes the first day of the week (i.e. the left-most day displayed on 
 * a calendar month) of the #DatesView widget.
 */
void
dates_view_set_week_start (DatesView *view, guint day)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	priv->week_start = day % 7;
	dates_view_update_all_queries (view);
	dates_view_refresh_day_layouts (view);
	dates_view_redraw (view);
}

/** dates_view_set_months_in_row:
 * @view:	The #DatesView widget to alter the view of
 * @months:	The amount of months that should be displayed in a single row
 *
 * Alters the @view so that a maximum of @months amount of months are shown
 * on a single row.
 **/
void
dates_view_set_months_in_row (DatesView *view, guint months)
{
	DatesViewPrivate *priv;

	g_return_if_fail (DATES_IS_VIEW (view));
	g_return_if_fail ((months >= 1) && (months <= 12));
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	priv->months_in_row = months;

	dates_view_update_all_queries (view);
	dates_view_redraw (view);
}

/** dates_view_set_visible_months:
 * @view:	The #DatesView widget to alter the view of
 * @months:	The amount of months that should be visible on the widget
 *
 * Alters the scale of the @view to show @months amount of months.
 * The view will center around the current date.
 **/
void
dates_view_set_visible_months (DatesView *view, guint months)
{
	DatesViewPrivate *priv;

	g_return_if_fail (DATES_IS_VIEW (view));
	g_return_if_fail ((months >= 0) && (months <= 12));

	priv = DATES_VIEW_GET_PRIVATE (view);	

	priv->months = months;

	dates_view_update_scrollability (view);

	if (months > 0) {
		/* Clear selection */
		priv->start_select = priv->end_select = 0;
		if (GTK_WIDGET_VISIBLE (priv->side)) {
			gtk_widget_hide (priv->side);
		}
		if (GTK_WIDGET_VISIBLE (priv->top) && priv->use_list) {
			gtk_widget_hide (priv->top);
		}
	} else {
		if (!GTK_WIDGET_VISIBLE (priv->side)) {
			gtk_widget_show (priv->side);
		}
	}

	dates_view_update_all_queries (view);
	dates_view_redraw (view);
}

/** dates_view_set_visible_days:
 * @view:	The #DatesView widget to alter the view of
 * @days:	The amount of days that should be visible on the widget
 *
 * Alters the scale of the @view to show @days amount of days.
 * The view will center around the current date.
 **/
void
dates_view_set_visible_days (DatesView *view, guint days)
{
	DatesViewPrivate *priv;

	g_return_if_fail (DATES_IS_VIEW (view));
	g_return_if_fail ((days >= 1) && (days <= 7));
	
	priv = DATES_VIEW_GET_PRIVATE (view);	
	if (days == priv->days) return;
	priv->days = days;
	
	if (days > 1) {
		if (!GTK_WIDGET_VISIBLE (priv->top) &&
		    !((priv->use_list) && (priv->months == 1))) {
			gtk_widget_show (priv->top);
		}
	} else {
		if (GTK_WIDGET_VISIBLE (priv->top)) {
			gtk_widget_hide (priv->top);
		}
	}

	dates_view_update_all_queries (view);
	dates_view_redraw (view);
}

/** dates_view_set_visible_hours:
 * @view:	The #DatesView widget to alter the view of
 * @hours:	The amount of hours that should be visible on the widget
 *
 * Alters the scale of the @view to show @hours amount of hours.
 * The view will center around the current date.
 **/
void
dates_view_set_visible_hours (DatesView *view, guint hours)
{
	DatesViewPrivate *priv;

	g_return_if_fail (DATES_IS_VIEW (view));
	g_return_if_fail ((hours >= 1) && (hours <= 24));
	
	priv = DATES_VIEW_GET_PRIVATE (view);	

	priv->hours = hours;

	dates_view_update_scrollability (view);

	dates_view_update_all_queries (view);
	dates_view_redraw (view);
}

/** dates_view_set_date:
 * @view:	The #DatesView widget to alter the view of
 * @date:	The date to view
 *
 * Alters the position of the @view to center on @date.
 **/
void
dates_view_set_date (DatesView *view, icaltimetype *date)
{
	DatesViewPrivate *priv;
	struct icaltimetype start, end;
	icaltimetype new_date;
	
	g_return_if_fail (DATES_IS_VIEW (view));
	g_return_if_fail (date);
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	new_date = icaltime_normalize (*date);
	if (icaltime_compare_date_only (new_date, *priv->date) == 0) return;

	g_memmove (priv->date, &new_date, sizeof (icaltimetype));
	dates_view_get_visible_span (view, &start, &end);

#ifdef DEBUG
	if (priv->debug & DATES_VIEW_DEBUG_QUERY)
		g_debug ("Old start: %d/%d/%d, new start: %d/%d/%d\n"
			"Old end: %d/%d/%d, new end: %d/%d/%d",
			priv->start.day, priv->start.month, priv->start.year,
			start.day, start.month, start.year,
			priv->end.day, priv->end.month, priv->end.year,
			end.day, end.month, end.year);
#endif

	/* Only update queries if visible span has changed */
	if ((icaltime_compare_date_only (start, priv->start) != 0) ||
	    (icaltime_compare_date_only (end, priv->end) != 0))
	    dates_view_update_all_queries (view);
	
	g_signal_emit (view, signals[DATE_CHANGED], 0);
	dates_view_redraw (view);
}

/** dates_view_set_use_list:
 * @view:	The #DatesView widget to alter the view of
 * @use_list:	Whether to use a list when viewing a single month.
 *
 * Enables or disables use of a list when viewing a single month.
 **/
void
dates_view_set_use_list (DatesView *view, gboolean use_list)
{
	DatesViewPrivate *priv;

	g_return_if_fail (DATES_IS_VIEW (view));

	priv = DATES_VIEW_GET_PRIVATE (view);
	
	priv->use_list = use_list;
	dates_view_redraw (view);
}

/** dates_view_set_use_24h:
 * @view:	The #DatesView widget to alter the view of
 * @use_list:	Whether to use 24-hour time.
 *
 * Sets 12-hour or 24-hour time viewing preferences.
 **/
void
dates_view_set_use_24h (DatesView *view, gboolean use_24h)
{
	DatesViewPrivate *priv;

	g_return_if_fail (DATES_IS_VIEW (view));

	priv = DATES_VIEW_GET_PRIVATE (view);
	
	priv->use_24h = use_24h;
	dates_view_refresh_time_layouts (view);
	dates_view_redraw (view);
}

/** dates_view_get_read_only:
 *
 * @view:	The #DatesView widget to retrieve the read-only property of.
 *
 * Retrieves the read-only property of the #DatesView widget.
 *
 * Return value: Whether the widget should disallow editing of events.
 **/
gboolean
dates_view_get_read_only (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	return priv->read_only;
}

/** dates_view_get_use_dragbox:
 *
 * @view:	The #DatesView widget to retrieve the use_dragbox property of.
 *
 * Retrieves the use_dragbox property of the #DatesView widget.
 *
 * Return value: Whether the widget allows creating new events with a drag-box.
 **/
gboolean
dates_view_get_use_dragbox (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	return priv->dragbox;
}

/** dates_view_get_single_click:
 *
 * @view:	The #DatesView widget to retrieve the single_click property of.
 *
 * Retrieves the single_click property of the #DatesView widget.
 *
 * Return value: Whether the widget is using single-click event activation.
 **/
gboolean
dates_view_get_single_click (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	return priv->single_click;
}

/** dates_view_get_snap:
 *
 * @view:	The #DatesView widget to retrieve the snap property of.
 *
 * Retrieves the snap property of the #DatesView widget.
 *
 * Return value: The minutes to snap to when adjusting events/selecting ranges.
 **/
guint
dates_view_get_snap (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	return priv->snap;
}
/** dates_view_get_week_start:
 *
 * @view:	The #DatesView widget to get the week start day of.
 *
 * This retrieves the first day of the week (i.e. the left-most day displayed 
 * on a calendar month) of the #DatesView widget.
 *
 * Return value: A number representing the starting day of the week, ranging 
 * from 0 (Sunday) to 6 (Saturday).
 */
guint
dates_view_get_week_start (DatesView *view)
{
	DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
	return priv->week_start;
}

/** dates_view_get_months_in_row:
 * @view:	The #DatesView widget to get the months displayed in a row of
 *
 * Retrieves the maximum amount of months to display in a single row.
 **/
guint
dates_view_get_months_in_row (DatesView *view)
{
	DatesViewPrivate *priv;

	g_return_val_if_fail (DATES_IS_VIEW (view), 0);
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	return priv->months_in_row;
}

/** dates_view_get_visible_months:
 * @view:	The #DatesView widget to get the visible months of
 *
 * Retrieves the amount of visible months at the current scale.
 *
 * Return value: The amount of visible months at the current scale.
 **/
guint
dates_view_get_visible_months (DatesView *view)
{
	DatesViewPrivate *priv;

	g_return_val_if_fail (DATES_IS_VIEW (view), 13);
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	return priv->months;
}

/** dates_view_get_visible_days:
 * @view:	The #DatesView widget to get the visible days of
 *
 * Retrieves the amount of visible months at the current scale.
 *
 * Return value: The amount of visible months at the current scale.
 **/
guint
dates_view_get_visible_days (DatesView *view)
{
	DatesViewPrivate *priv;

	g_return_val_if_fail (DATES_IS_VIEW (view), 0);
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	return priv->days;
}

/** dates_view_get_visible_hours:
 * @view:	The #DatesView widget to get the visible hours of
 *
 * Retrieves the amount of visible days at the current scale.
 *
 * Return value: The amount of visible days at the current scale.
 **/
guint
dates_view_get_visible_hours (DatesView *view)
{
	DatesViewPrivate *priv;

	g_return_val_if_fail (DATES_IS_VIEW (view), 0);
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	return priv->hours;
}

/** dates_view_get_date:
 * @view:	The #DatesView widget to get the selected date of
 *
 * Retrieves the current active date of @view
 *
 * Return value: @view's active date
 **/
const icaltimetype *
dates_view_get_date (DatesView *view)
{
	DatesViewPrivate *priv;

	g_return_val_if_fail (DATES_IS_VIEW (view), NULL);
	
	priv = DATES_VIEW_GET_PRIVATE (view);
	
	return priv->date;
}

/** dates_view_get_use_list:
 * @view:	The #DatesView widget to get the view preference of
 *
 * Retrieves the use_list property of @view
 *
 * Return value: Whether @view uses a list when viewing a single month.
 **/
gboolean
dates_view_get_use_list (DatesView *view)
{
	DatesViewPrivate *priv;

	g_return_val_if_fail (DATES_IS_VIEW (view), FALSE);

	priv = DATES_VIEW_GET_PRIVATE (view);
	
	return priv->use_list;
}

/** dates_view_get_use_24h:
 * @view:	The #DatesView widget to get the time-display preference of
 *
 * Retrieves the use_24h property of @view
 *
 * Return value: Whether @view uses 12-hour or 24-hour time.
 **/
gboolean
dates_view_get_use_24h (DatesView *view)
{
	DatesViewPrivate *priv;

	g_return_val_if_fail (DATES_IS_VIEW (view), FALSE);

	priv = DATES_VIEW_GET_PRIVATE (view);
	
	return priv->use_24h;
}

/** dates_view_set_scroll_adjustments:
 * @view:	The #DatesView widget to set the adjusters on
 * @hadjustment:	The adjuster to use for the horizontal adjustment
 * 			(ignored)
 * @vadjustment:	The adjuster to use for the vertical adjustment
 *
 * This function is hooked into the set_scroll_adjustments signal and is used
 * by a #GtkScrolledWindow to provide the #GtkAdjustment objects to control the
 * scrolling of the #DatesView widget
 */
static void
dates_view_set_scroll_adjustments (DatesView *view, GtkAdjustment *hadjustment,
		GtkAdjustment *vadjustment)
{
	DatesViewPrivate *priv;

	g_return_if_fail (DATES_IS_VIEW (view));

	priv = DATES_VIEW_GET_PRIVATE (view);

	/* Set our internal pointer to the adjustment */
	priv->adjust = vadjustment;

	/* 
	 * Connect a signal to deal with the change in the value of the
	 * adjustment
	 */
	if (vadjustment != NULL)
		g_signal_connect (G_OBJECT (priv->adjust), "value-changed",
				  G_CALLBACK (dates_view_scroll_value_changed),
				  view);
}

/** dates_view_update_scrollability
 * @view:	The #DatesView widget to update the scrolling on
 *
 * This function tests whether the view should be scrollable, based on the
 * viewing properties (e.g. number of hours visible.) The widget will then act
 * appropriately.
 *
 * This function also updates the range for the adjuster with the side effect
 * of showing/hiding the scrollbars in the surrounding widget as appropriate.
 * And setting the appropriate range for their use.
 *
 */
static void
dates_view_update_scrollability (DatesView *view)
{
	DatesViewPrivate *priv;
	guint hours;
	guint months;

	g_return_if_fail (DATES_IS_VIEW (view));

	priv = DATES_VIEW_GET_PRIVATE (view);

	hours = priv->hours;
	months = priv->months;

	if (hours < 24)
	{
		priv->scrollable = TRUE;

		/* Modify the range based on the number of visible hours */
		priv->adjust->lower = 0;

		/* 
		 * The upper is 24 - hours since there will be 'hours' hours
		 * shown from the top of the view 
		 */
		priv->adjust->upper = 24 - hours;

		/* Focus is roughly in the middle */
		priv->adjust->value = (24 - hours) / 2;

		/* Set sensible increments */
		priv->adjust->step_increment = 1;
		priv->adjust->page_increment = 1;

		gtk_adjustment_changed (priv->adjust);
		gtk_adjustment_value_changed (priv->adjust);
	} else {
		if (months > 0)
		{
			priv->scrollable = TRUE;

			if (months == 12)
			{
				/* 
				 * In year view: show 5 years forward &
				 * back.
				 */
				priv->adjust->upper = 5;
				priv->adjust->value = 0;
				priv->adjust->lower = -5;
				priv->adjust->step_increment = 1;
				priv->adjust->page_increment = 1;
			} else {
				/* 
				 * In month view: show 1 year forward & back
				 */
				priv->adjust->upper = 12;
				priv->adjust->lower = -12;
				priv->adjust->value = 0;
				priv->adjust->step_increment = 1;
				priv->adjust->page_increment = 1;
			}

			gtk_adjustment_changed (priv->adjust);
			gtk_adjustment_value_changed (priv->adjust);
		} else {
			priv->scrollable = FALSE;

			/* Modify the range of the GtkAdjuster at priv->adjust so that
			 * there is no range to scroll over. This will have the effect
			 * of making the scrollbar disappear if the surrounding
			 * GtkScrolledWindow has a policy of GTK_POLICY_AUTOMATIC.
			 */
			priv->adjust->upper = 0;
			priv->adjust->lower = 0;
			priv->adjust->value = 0;

			gtk_adjustment_changed (priv->adjust);
			gtk_adjustment_value_changed (priv->adjust);
		}
	}
}
