Logo Search packages:      
Sourcecode: dates version File versions

dates_view.c

/*
 *  DatesView - An electronic calendar widget optimised for embedded devices.
 *
 *  Chris Lord <chris@o-hand.com>
 *  Tomas Frydrych <tf@o-hand.com>
 *  Rob Bradford <rob@o-hand.com>
 *
 *  Copyright (c) 2005 - 2007 OpenedHand Ltd - http://www.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"

#ifdef WITH_HILDON
#     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)
      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)
{
      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);     
      if (months == priv->months) return;
      priv->months = months;

      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);     
      if (hours == priv->hours) return;
      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;

      g_return_if_fail (DATES_IS_VIEW (view));

      priv = DATES_VIEW_GET_PRIVATE (view);

      hours = priv->hours;

      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 {
            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);
      }
}

Generated by  Doxygen 1.6.0   Back to index