Logo Search packages:      
Sourcecode: dates version File versions  Download package

dates_view.c

/* -*- mode:C; c-file-style:"linux"; tab-width:8; -*- */
/* 
 *  DatesView - An electronic calendar widget optimised for embedded devices.
 *
 *  Principal author    : Chris Lord <chris@o-hand.com>
 *  Maemo port          : Tomas Frydrych <tf@o-hand.com>
 *
 *  Copyright (c) 2005 - 2006 OpenedHand Ltd - http://o-hand.com
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 */

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

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

#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,
} 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 },
};
#endif

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

typedef struct {
      DatesView *parent;
      DatesViewCalendar *cal;
      ECalComponent *comp;
      const gchar *uri_uid;
      GList *draw_data;
      PangoLayout *summary;
      PangoLayout *details;
      GList *list_refs; /* List of lists the event is in */
#if 0
        gint custom_font_size;
#endif
} 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
{
      GdkRectangle *mrect;
      GList *drects;
} DatesViewRegion;

typedef enum {
      DATES_VIEW_NONE = 0x0,
      DATES_VIEW_MOVE = 0x1,
      DATES_VIEW_SIZE = 0x2,
      DATES_VIEW_PICK = 0x4,
      DATES_VIEW_SEL = 0x8,
} DatesViewOperation;

typedef struct
{
#if 0
      /* Variables required for animation */
      gdouble oldzoomx;
      gdouble oldzoomy;
      gdouble zoom_difference;
      gboolean zooming;
      gint oldpanx;
      gint oldpany;
      gint pan_difference;
      gboolean panning;
      gboolean animate;
      gboolean panels_moved;
#endif
  
      /* Variables for holding date information */
      guint hours;
      guint days;
      guint months;
      guint months_in_row;
      icaltimetype *date;
      icaltimezone *zone;
      icaltimezone *utc;
      icaltimetype now;
      icaltimetype start;     /* Start of visible span */
      icaltimetype end; /* End of visible span */
      guint week_start;
      gboolean use_24h;
      
      /* 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;
      /**/
      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;
      
      /* Variables for rendering */
      PangoLayout *date_layouts[31];
      PangoLayout *bdate_layouts[31];
      PangoLayout *time_layouts[23];
      PangoLayout *month_layouts[12];
      PangoLayout *day_layouts[7];
      PangoLayout *abday_layouts[7];
      gint time_layouts_height;
      PangoFontDescription *font;
  
      gboolean draw_selected;
      gboolean done;
      GdkPixbuf *recur_pixbuf;
/*    GdkPixbuf *readonly_pixbuf;*/

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

      /* Variables for holding calendar and event information */
      GList *events;
      GConfClient *gconf_client;
      GList *calendars;
      GList *event_days[12][31];
      
      /* For debugging */
      guint debug;
}
DatesViewPrivate;

enum {
      DATE_CHANGED,
      EVENT_SELECTED,
      EVENT_MOVED,
      EVENT_SIZED,
      COMMIT_EVENT,
      EVENT_ACTIVATED,
      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
};

static guint signals[LAST_SIGNAL] = { 0 };

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

#if 0
static gboolean   dates_view_main_configure_event     (GtkWidget *widget,
                                     GdkEventConfigure *event,
                                     DatesView  *view);
#endif
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);

static GtkWidgetClass *parent_class = NULL;

G_DEFINE_TYPE(DatesView, dates_view, GTK_TYPE_TABLE);

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 when viewing a single "
                   "month."),
                  FALSE,
                  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);
      
      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;
            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);
            case PROP_USE_LIST:
                  g_value_set_boolean (value, priv->use_list);
            default:
                  G_OBJECT_WARN_INVALID_PROPERTY_ID (
                        object, prop_id, pspec);
                  break;
      }
}

/* ********* Next 2 functions stolen from gnome-panel clock applet ********* */

static gchar *
calendar_client_config_get_timezone (GConfClient *gconf_client)
{
  char *location;

  location = gconf_client_get_string (gconf_client,
                                      CALENDAR_GCONF_TIMEZONE,
                                      NULL);

  return location;
}
static icaltimezone *
calendar_client_config_get_icaltimezone (GConfClient *gconf_client)
{
  char         *location;
  icaltimezone *zone = NULL;
      
  location = calendar_client_config_get_timezone (gconf_client);
  if (!location)
    return icaltimezone_get_utc_timezone ();

  zone = icaltimezone_get_builtin_timezone (location);
  g_free (location);
      
  return zone;
}

/* ************************************************************************** */

static gboolean
calendar_client_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
       */
#ifndef WITH_HILDON
      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;
#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]);
      }
      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]);
            g_object_unref (priv->day_layouts[i]);
      }
    
      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->font)
            pango_font_description_free (priv->font); 
            
      G_OBJECT_CLASS (parent_class)->finalize (object);
}

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 (priv->zone);
      dates_view_redraw (view, TRUE);
      
      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);
}

static void
dates_view_get_visible_span (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 = ((7 + (icaltime_day_of_week (*priv->date)-1) -
                        priv->week_start) % 7) + 1;
                  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);
      }
}

static void
dates_view_init (DatesView *view)
{
      DatesViewPrivate *priv;
      PangoRectangle rect;
      GdkColor colour;
      guint i;
#ifdef DEBUG
      const gchar *debug;
#endif      
      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 ();
      priv->zone =
            calendar_client_config_get_icaltimezone (priv->gconf_client);
      priv->use_24h = calendar_client_config_get_24h (priv->gconf_client);
      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->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;
#if 0
      priv->zoom_difference = 0;
      priv->zooming = FALSE;
      priv->oldzoomx = 1.0;
      priv->oldzoomy = 1.0;
      priv->pan_difference = 0;
      priv->panning = FALSE;
      priv->oldpanx = 0;
      priv->oldpany = 0;
      priv->draw_selected = FALSE;
#endif
      priv->dragbox = TRUE;
      priv->single_click = TRUE;
      priv->double_click = FALSE;
      priv->snap = 30;
      priv->use_list = FALSE;
    
      priv->start_select = 0;
      priv->end_select = 0;
      priv->recur_pixbuf = gtk_widget_render_icon (GTK_WIDGET (view),
            "gtk-refresh", GTK_ICON_SIZE_MENU, NULL);
/*    priv->readonly_pixbuf = gtk_widget_render_icon (GTK_WIDGET (view),
            "gtk-stop", GTK_ICON_SIZE_MENU, NULL);*/

#ifdef WITH_HILDON
      priv->font = pango_font_description_from_string(DEFAULT_FONT_STRING);
#else
      priv->font = NULL;
#endif
      
      for (i = 0; i < 31; i++) {
            gchar *text = g_strdup_printf (
                  "<small>%d</small>", i+1);
            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);
      }
      priv->time_layouts_height = 0;
      for (i = 0; i < 23; i++) {
            gchar *text;
            if (priv->use_24h) {
                  text = g_strdup_printf (
                        "<small>%2d:00</small>", i + 1);
            } else {
                  text = g_strdup_printf (
                        "<small>%2d:00 %s</small>", i % 12 + 1,
                        i < 11 ? _("AM") : _("PM"));
            }
            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;
      }
      for (i = 0; i < 7; i++) {
            int day_no = (priv->week_start + i) % 7;
            gchar *day_markup = g_strdup_printf (
                  "<small>%s</small>",
                  nl_langinfo (DAY_1 + day_no));
            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);

            day_markup = g_strdup_printf (
                  "<small>%s</small>",
                  nl_langinfo (ABDAY_1 + day_no));
            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 */
            pango_layout_get_extents (priv->day_layouts[i], NULL, &rect);
            pango_layout_get_extents (priv->abday_layouts[i], NULL, &rect);
      }
      for (i = 0; i < 12; i++) {
            gint j;
            gchar *month_markup = g_strdup_printf (
                  "<small>%s</small>",
                  nl_langinfo (MON_1 + 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);
      }

      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->vscroll = gtk_vscrollbar_new (priv->adjust);
      priv->date = g_new (icaltimetype, 1);
      *priv->date = icaltime_today ();
      dates_view_get_visible_span (view, &priv->start, &priv->end);
      
      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);
      gtk_table_attach (GTK_TABLE (view), priv->vscroll, 2, 3, 1, 2,
                    GTK_FILL, GTK_FILL, 0, 0);

      pango_layout_get_extents (priv->day_layouts[6], NULL, &rect);
      gtk_widget_set_size_request (priv->top, -1, PANGO_PIXELS (rect.height)
            + (BORDER * 2));
      pango_layout_get_extents (priv->time_layouts[22], NULL, &rect);
      gtk_widget_set_size_request (priv->side,
            PANGO_PIXELS (rect.width) + (BORDER * 2), -1);

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

#if 0
      g_signal_connect (G_OBJECT (priv->main), "configure-event",
                    G_CALLBACK (dates_view_main_configure_event), view);
#endif
      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);
      
      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);
      g_signal_connect (G_OBJECT (priv->adjust), "value-changed",
                    G_CALLBACK (dates_view_scroll_value_changed), 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);

#if 0
      /* Don't animate the first transition */
      priv->panels_moved = FALSE;
      priv->animate = FALSE;
#endif
}

void
dates_view_redraw (DatesView *view, gboolean do_animation)
{
      DatesViewPrivate *priv;
      priv = DATES_VIEW_GET_PRIVATE (view);

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

      if (GTK_WIDGET_REALIZED (view)) {
#if 0
            priv->animate = do_animation;
#endif
            gdk_window_invalidate_rect (GTK_WIDGET (view)->window,
                  &GTK_WIDGET (view)->allocation, TRUE);
      }
}

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);
            }
#if 0
            event->custom_font_size = 0;
#endif
            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) {
                  GSList *text_list;

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

                  if (priv->font) {
                        pango_layout_set_font_description (
                              event->details, priv->font);
                  }
                  
                  if (text_list) {
                        ECalComponentText *desc = text_list->data;
                        
                        if (desc->value) {
                              gchar *string =
                                  g_markup_printf_escaped (
                                    "<span size=\"x-small\">"
                                    "%s</span>", desc->value);
                              pango_layout_set_markup (
                                    event->details, string, -1);
                              g_free (string);
                        }
                        
                        e_cal_component_free_text_list (text_list);
                  }
                  
                  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;
      time_t rend, end2, abs_start;
      gboolean first = TRUE;

      view = event->parent;
      priv = DATES_VIEW_GET_PRIVATE (view);
      
      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 (priv->start),
            icaltime_as_timet (priv->end),
            (ECalRecurInstanceFn)dates_view_event_file_cb,
            event, e_cal_resolve_tzid_cb, event->cal->ecal,
            priv->utc);
}

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, FALSE);
}

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);
                  g_free (uri_uid);
            } else
                  g_warning ("Event doesn't exist?");

      }

      dates_view_fit_events (view);
      dates_view_redraw (view, FALSE);
}

static 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 void
dates_view_add_region (DatesView    *view,
                   guint            month,
                   guint            day,
                   GdkRectangle     *rect)
{
      DatesViewPrivate *priv;
      
      priv = DATES_VIEW_GET_PRIVATE (view);
      
      if (!priv->regions[month-1].mrect) {
            priv->regions[month-1].mrect = g_new0 (GdkRectangle, 1);
            priv->regions[month-1].drects = NULL;
      }
      if (day == 0) {
            g_memmove (priv->regions[month-1].mrect,
                     rect, sizeof (GdkRectangle));
      } else {
            GdkRectangle *drect;
            gint i = day - g_list_length (priv->regions[month-1].drects);
            for (; i > 0; i--)
                  priv->regions[month-1].drects =
                        g_list_append (priv->regions[month-1].drects,
                              g_new0 (GdkRectangle, 1));
            
            drect = g_list_nth_data (priv->regions[month-1].drects, day-1);
            g_memmove (drect, rect, sizeof (GdkRectangle));
      }
}

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

      /* Don't allow getting of invisible regions */
      if (priv->start.year == priv->end.year) {
            if ((month < priv->start.month) || (month > priv->end.month))
                  return NULL;
            if (day != 0) {
                  if (((month == priv->start.month) &&
                       (day < priv->start.day)) ||
                      ((month == priv->end.month) &&
                       (day > priv->end.day)))
                        return NULL;
            }
      }
      
      if (!priv->regions[month-1].mrect)
            return NULL;
      
      if (day == 0)
            return priv->regions[month-1].mrect;
      else
            return g_list_nth_data (priv->regions[month-1].drects, day-1);
}

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, 0))) continue;
            
            if (dates_view_point_in (mrect, x, y)) {
                  *month = i;
                  *day = 0;
                  guint j;
                  
                  for (j = 1; j <= 31; 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;
}

#if 0
static gboolean
dates_view_main_configure_event (GtkWidget *widget, GdkEventConfigure *event,
                         DatesView *view)
{
      /* Disable animation when resizing */
      DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
      if (!priv->panels_moved)
            priv->animate = FALSE;
      else
            priv->panels_moved = FALSE;
      
      return FALSE;
}
#endif

static void
dates_view_scroll_value_changed     (GtkAdjustment *adjust, DatesView *view)
{
      dates_view_redraw (view, FALSE);
}

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 = ((7 + (icaltime_day_of_week (*priv->date)-1) -
                        priv->week_start) % 7) + 1;
                  sdom = (7 + time_day_of_week (
                        1, priv->date->month - 1, priv->date->year) -
                        priv->week_start) % 7;
                  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 (GTK_WIDGET_VISIBLE (priv->vscroll))
                        *pany += gtk_adjustment_get_value (priv->adjust)
                               * ((height / (gdouble)weeks) / 24.0);
            }
      }
#if 0
      /* Animation (copied from dates_view_calc_zoom) */
      if (!priv->panning) {
            priv->pan_difference = MAX (ABS (*panx - priv->oldpanx),
                  ABS (*pany - priv->oldpany)) / FRAMES;
            if (priv->pan_difference > 0)
                  priv->panning = TRUE;
      }

      if ((priv->panning) && (priv->animate)) {
            gboolean donex, doney;
            
            if (ABS (priv->oldpanx - *panx) < priv->pan_difference)
                  donex = TRUE;
            else
                  donex = FALSE;
            if (ABS (priv->oldpany - *pany) < priv->pan_difference)
                  doney = TRUE;
            else
                  doney = FALSE;
            
            if (!donex) {
                  if (priv->oldpanx < *panx)
                        *panx = priv->oldpanx + priv->pan_difference;
                  else
                        *panx = priv->oldpanx - priv->pan_difference;
            }
            if (!doney) {
                  if (priv->oldpany < *pany)
                        *pany = priv->oldpany + priv->pan_difference;
                  else
                        *pany = priv->oldpany - priv->pan_difference;
            }
                        
            if ((donex) && (doney))
                  priv->panning = FALSE;
            else {
                  dates_view_redraw (view, TRUE);
            }
      }
      priv->oldpanx = *panx;
      priv->oldpany = *pany;
#endif
}

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 + ((7 + time_day_of_week (
                  1, priv->date->month - 1, priv->date->year) -
                  priv->week_start) % 7);
            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)));
      }
#if 0
      /* Animation */
      if (!priv->zooming) {
            priv->zoom_difference = MAX (ABS (*zoomx - priv->oldzoomx),
                  ABS (*zoomy - priv->oldzoomy)) / (gdouble)FRAMES;
            if (priv->zoom_difference > 0)
                  priv->zooming = TRUE;
      }
      
      if ((priv->zooming) && (priv->animate)) {
            gboolean donex, doney;
            
            if (ABS (priv->oldzoomx - *zoomx) < priv->zoom_difference)
                  donex = TRUE;
            else
                  donex = FALSE;
            if (ABS (priv->oldzoomy - *zoomy) < priv->zoom_difference)
                  doney = TRUE;
            else
                  doney = FALSE;
            
            if (!donex) {
                  if (priv->oldzoomx < *zoomx)
                        *zoomx = priv->oldzoomx + priv->zoom_difference;
                  else
                        *zoomx = priv->oldzoomx - priv->zoom_difference;
            }
            if (!doney) {
                  if (priv->oldzoomy < *zoomy)
                        *zoomy = priv->oldzoomy + priv->zoom_difference;
                  else
                        *zoomy = priv->oldzoomy - priv->zoom_difference;
            }
                        
            if ((donex) && (doney))
                  priv->zooming = FALSE;
            else {
                  dates_view_redraw (view, TRUE);
            }
      }
      priv->oldzoomx = *zoomx;
      priv->oldzoomy = *zoomy;
#endif
}

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

      /* Draw emblems (recurrence, read-only, etc.) */
      if (e_cal_component_has_recurrences (event->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);
            } else {
                  /* TODO: Create/draw a label if the
                   * pixbuf doesn't exist?
                   */
            }
      }
      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, 0)))
            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;
                  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;
                  
                  /* 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, 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,
                        priv->main->style->fg_gc[GTK_STATE_NORMAL],
                        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)
{
      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;
      
      priv = DATES_VIEW_GET_PRIVATE (view);

      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 \"%s\" (%d/%d/%d-%d:%d)"
                  "-(%d/%d/%d-%d:%d)",
                  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

      /* Get the target rectangle */
      if (!(drectp = dates_view_get_region (view,
            start.month, start.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);
                        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;
      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, 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) {
#if 0
            PangoRectangle r;
            gint layout_height, layout_width;
#endif
            pango_layout_set_width (event->summary,
                  (x2 - (BORDER * 2)) * PANGO_SCALE);
            layout_gc = priv->main->style->fg_gc[GTK_STATE_NORMAL];
            /* Clip to event rectangle */
            gdk_gc_set_clip_rectangle (layout_gc, rect);
            
#if 0
            /* Make sure text fits inside rectangle on hildon */
            /* TODO: Adapt this code to work generally */
            pango_layout_get_pixel_size (
                  event->summary, &layout_width, &layout_height);

            /*g_debug ("Event rect height %d, layout height %d",
               y2,
               layout_height);*/

            /* pango_layout_get_pixel_size () is buggy and
             * returns unreliable values, so the constant here
             * is heuristically determined; above it we use the
             * default font; below it we take percentage of the
             * pixel height and treat it as point size; again,
             * this is a heuristic value which works on the 770.
             */
            if (y2 < 20) {
                  PangoFontDescription *pfd = NULL;
                  gchar *s = NULL;
                  gint font_size = y2 * 75 / 100;

                  if (font_size != event->custom_font_size) {
                        s = g_strdup_printf ("sans, %d.0", font_size);

                        pfd = pango_font_description_from_string(s);
                        /*g_debug ("font string [%s]", s);*/

                        if (pfd) {
                              pango_layout_set_font_description (
                                    event->summary, pfd);

                              /* works around a pango bug -- see
                               * comments in dates_view_init ()
                               */
                              pango_layout_get_extents (
                                    event->summary, NULL, &r);

                              event->custom_font_size = font_size;
                              /*g_debug ("New font size %d",
                                    font_size);*/
                        }
                  }

                  if (pfd) pango_font_description_free (pfd);
                  if (s) g_free (s);
                  
            } else if (event->custom_font_size != 0) {
                  pango_layout_set_font_description (
                        event->summary, NULL);

                  /* works around a pango bug -- see comments in
                   * dates_view_init ()
                   */
                  pango_layout_get_extents (
                        event->summary, NULL, &r);

                  event->custom_font_size = 0;
            }
#endif
            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;
      GdkGC *gc;
      guint i, 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

      /* 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);
#if 0
      if ((!priv->zooming) && (!priv->panning))
            priv->animate = FALSE;
#endif
      panx = dpanx * zoomx;
      pany = dpany * zoomy;
      mw *= zoomx;
      mh *= zoomy;
            
      year = priv->date->year;
      i_end = (priv->start.year != priv->end.year) ? 12 : priv->end.month;
      for (i = priv->start.month, 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, 0, &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) {
                        gc = widget->style->fg_gc[GTK_STATE_NORMAL];
                        gdk_gc_set_line_attributes (gc, 2,
                              GDK_LINE_SOLID, GDK_CAP_BUTT,
                              GDK_JOIN_MITER);
                  } else {
                        gc = widget->style->dark_gc[GTK_STATE_NORMAL];
                  }
                  
                  /* 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,
                               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, gc, FALSE,
                        rect.x + BORDER, rect.y + BORDER,
                        rect.width - (2 * BORDER), rect.height -
                              (2 * BORDER));
                  
                  gdk_gc_set_line_attributes (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->start.month) ? priv->start.day : 1;
            k_end = (i == i_end) ?
                  ((priv->months <= 1) ?
                        priv->end.day : dim + 1) : dim + 1;
            sdow = (7 + time_day_of_week (1, i-1, year) -
                  priv->week_start) % 7;
            for (k = k_start, l = ((k_start + sdow - 1) / 7);
                 k < k_end; k++) {
                  GdkRectangle drect;
                  gint dow = (7 + time_day_of_week (k, i-1, year) -
                        priv->week_start) % 7;
                  gint dim_ceil = dim + sdow;
                  
                  /* 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;
                              gc = widget->style->
                                    dark_gc[GTK_STATE_SELECTED];
                              gdk_draw_rectangle (widget->window,
                                    gc, FALSE,
                                    drect.x + BORDER - 1,
                                    y - 1,
                                    drect.width - (BORDER * 2) + 1,
                                    height + 1);
                              gc = widget->style->
                                    mid_gc[GTK_STATE_SELECTED];
                              gdk_draw_rectangle (widget->window,
                                    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));
                              gc = widget->style->
                                    dark_gc[GTK_STATE_SELECTED];
                              gdk_draw_rectangle (widget->window,
                                    gc, FALSE,
                                    drect.x + BORDER - 1,
                                    lower - 1,
                                    drect.width - (BORDER * 2) + 1,
                                    height + 1);
                              gc = widget->style->
                                    mid_gc[GTK_STATE_SELECTED];
                              gdk_draw_rectangle (widget->window,
                                    gc, TRUE,
                                    drect.x + BORDER,
                                    lower,
                                    drect.width - (BORDER * 2),
                                    height);
                        }
                  }
                  
                  /* Set line-width/colour */
                  if ((priv->date->day == k) &&
                      (priv->date->month == i)) {                       
                        gc = widget->style->fg_gc[
                              GTK_STATE_NORMAL];
                        gdk_gc_set_line_attributes (gc, 2,
                              GDK_LINE_SOLID, GDK_CAP_BUTT,
                              GDK_JOIN_MITER);
                  } else {
                        if ((year == priv->now.year) &&
                            (i == priv->now.month) &&
                            (k == priv->now.day))
                              gc = widget->style->mid_gc[
                                    GTK_STATE_ACTIVE];
                        else
                              gc = widget->style->dark_gc[
                                    GTK_STATE_NORMAL];
                  }
                  
                  if (((!priv->use_list) && (priv->months <= 1)) ||
                      (priv->months == 0)) {
                        GList *e;

                        /* Draw border */
                        gdk_draw_rectangle (
                              widget->window, gc, FALSE,
                              drect.x, drect.y,
                              drect.width, drect.height);
                        
                        /* 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;

                              for (m = skip; m <= 23; m += skip) {
                                    gdk_draw_line (
                                          widget->window,
                                          widget->style->dark_gc[
                                              GTK_STATE_NORMAL],
                                          drect.x,
                                          drect.y + floor (
                                            mh4 * m),
                                          drect.x + drect.width,
                                          drect.y + floor (
                                            mh4 * m));
                              }
                        }

                        /* Draw events */
                        for (e = priv->event_days[i-1][k-1];
                             e; e = e->next) {
                              dates_view_draw_event (view,
                                    (DatesViewEventData *)
                                          e->data);
                        }
                        
                        if ((year == priv->now.year) &&
                            (i == priv->now.month) &&
                            (k == priv->now.day) &&
                            (priv->months == 0)) {
                              /* Draw line at current time */
                              gdouble day_progress;
                              gdouble line;

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

                              line = drect.y + (
                                    day_progress * drect.height);
                              gdk_gc_set_line_attributes (
                                    widget->style->fg_gc[
                                          GTK_STATE_ACTIVE],
                                    1, GDK_LINE_ON_OFF_DASH,
                                    GDK_CAP_BUTT, GDK_JOIN_MITER);
                              gdk_draw_line (widget->window,
                                    widget->style->fg_gc[
                                          GTK_STATE_ACTIVE],
                                    drect.x, line,
                                    drect.x + drect.width, line);
                        }

                        /* Draw date in corner */
                        /* NOTE: Don't draw this while animating, makes
                         * things slow
                         */
#if 0
                        if (!priv->animate)
#endif
                        {
                              pango_layout_set_width (
                                    priv->date_layouts[k-1],
                                    (drect.width - (BORDER * 2)) *
                                    PANGO_SCALE);
                              gdk_draw_layout (widget->window, gc,
                                    drect.x + (BORDER / 2),
                                    drect.y + (BORDER / 2),
                                    priv->date_layouts[k-1]);
                        }

                        gdk_gc_set_line_attributes (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, gc,
                              (drect.x + (drect.width/2)) -
                               PANGO_PIXELS (layout_rect.width/2),
                              (drect.y + (drect.height/2)) -
                               PANGO_PIXELS (layout_rect.height/2),
                              layout);
                  }
                  
                  if (dow == 6) l++;
            }
            
            if (i % priv->months_in_row == 0) j++;
      }
      
#if 0
      /* Enable animation after rendering a frame */
      priv->animate = TRUE;
#endif
      /* Don't mess up the XOR 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 (GTK_WIDGET_VISIBLE (priv->vscroll)) {
            gtk_widget_event (priv->vscroll, (GdkEvent *)event);
      }
      
      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;
                  
                  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, FALSE);
                  }

                  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, FALSE);
#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, FALSE);
                  }
                  
                  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;
            }
            
            if (in_region && (priv->operation == DATES_VIEW_NONE)) {
                  new_date.month = month;
                  if (day != 0) {
                        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 gboolean
dates_view_main_button_release (GtkWidget *widget,
                        GdkEventButton    *event,
                        DatesView   *view)
{
      DatesViewPrivate *priv;
      
      priv = DATES_VIEW_GET_PRIVATE (view);
      
      if (event->button == 1) {
            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, event->x, event->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 ?
                                    mouse_date.day -
                                    priv->selected_event->
                                          date.day : 0;

                              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 dragged/sized.
                         */
                        if ((!moved) && (!sized)) {
                              new_date.month = mouse_date.month;
                              if (mouse_date.day != 0)
                                    new_date.day = mouse_date.day;
                              dates_view_set_date (view, &new_date);
                        }
                        dates_view_redraw (view, TRUE);
                  }
                  
                  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, TRUE);
            }
            priv->operation = DATES_VIEW_NONE;
      }
      
      return FALSE;
}

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, FALSE);
            return FALSE;
      }

      gdk_x11_display_grab (gdk_drawable_get_display (priv->main->window));
      if (priv->first_move) {
            dates_view_redraw (view, FALSE);
            /* 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_XOR);
            gdk_gc_set_function (priv->selected_event->parent->
                  cal->gc, GDK_XOR);
      } else {
            gint old_x, old_y;
            
            gdk_gc_set_function (priv->selected_event->parent->cal->dark_gc,
                  GDK_XOR);
            gdk_gc_set_function (priv->selected_event->parent->
                  cal->gc, GDK_XOR);

            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);
                  }
            } else
                  dates_view_draw_event (view, priv->selected_event);
            priv->current_x = old_x;
            priv->current_y = old_y;
      }
      
      /* Only redraw selected event using XOR */
      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);
            }
      } else
            dates_view_draw_event (view, priv->selected_event);
      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;

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

static gboolean
dates_view_top_expose (GtkWidget      *widget,
                   GdkEventExpose *event,
                   DatesView      *view)
{
      DatesViewPrivate *priv;
      gint i, j, visible_months, offset;
      gdouble width, moffset;
      
      priv = DATES_VIEW_GET_PRIVATE (view);
      
      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 = ((7 + icaltime_day_of_week (*priv->date) -
            priv->week_start - 1) % 7) + 1 -
            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;
            
                  if(visible_months <= 1)
                        playout = priv->day_layouts[i+offset];
                  else
                        playout = priv->abday_layouts[i+offset];
                  
                  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 (
                        widget->style->dark_gc[GTK_STATE_NORMAL],
                        &clip_rect);
                  gdk_draw_layout (
                        widget->window,
                        widget->style->dark_gc[GTK_STATE_NORMAL],
                        (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 (
            widget->style->dark_gc[GTK_STATE_NORMAL],
            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 (GTK_WIDGET_VISIBLE (priv->vscroll)) {
            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->dark_gc[GTK_STATE_NORMAL],
                  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;
                        new_date = icaltime_normalize (new_date);
                        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;
                        new_date = icaltime_normalize (new_date);
                        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, FALSE);
                        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;

      month_begin = isodate_from_time_t (
            icaltime_as_timet_with_zone (priv->start, priv->zone));
      month_end = isodate_from_time_t (
            icaltime_as_timet_with_zone (priv->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 */
      /* g_free (query);*/
}

static void
dates_view_update_all_queries (DatesView *view)
{
      DatesViewPrivate *priv = DATES_VIEW_GET_PRIVATE (view);
      GList *c;
      
      /* The visible span must've changed when all queries are updated, so
       * cache it here.
       */
      dates_view_get_visible_span (view, &priv->start, &priv->end);
      
      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));

      if (e_source_get_color (source, &colour)) {
            GdkColor gcolour, dgcolour;
            gcolour.red = (colour & 0xFF0000) >> 8;
            gcolour.green = (colour & 0xFF00);
            gcolour.blue = (colour & 0xFF) << 8;
            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);
            }
      } else {
            /* If no source colour set, use gtk theme colours */
            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]);
      }
            
      dates_view_update_query (view, cal);
      
      dates_view_redraw (view, TRUE);
}

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);
      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, TRUE);
      }
}

/** 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, TRUE);
}

/** 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, FALSE);
      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_redraw (view, FALSE);
}

/** 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, FALSE);
}

/** 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)) {
#if 0
                  priv->panels_moved = TRUE;
#endif
                  gtk_widget_hide (priv->side);
            }
            if (GTK_WIDGET_VISIBLE (priv->top) && priv->use_list) {
#if 0
                  priv->panels_moved = TRUE;
#endif
                  gtk_widget_hide (priv->top);
            }
      } else {
            if (!GTK_WIDGET_VISIBLE (priv->side)) {
#if 0
                  priv->panels_moved = TRUE;
#endif
                  gtk_widget_show (priv->side);
            }
      }
      
      dates_view_update_all_queries (view);
      dates_view_redraw (view, TRUE);
}

/** 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))) {
#if 0
                  priv->panels_moved = TRUE;
#endif
                  gtk_widget_show (priv->top);
            }
      } else {
            if (GTK_WIDGET_VISIBLE (priv->top)) {
#if 0
                  priv->panels_moved = TRUE;
#endif
                  gtk_widget_hide (priv->top);
            }
      }

      dates_view_update_all_queries (view);
      dates_view_redraw (view, TRUE);
}

/** 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)
{
      GValue *val = g_new0 (GValue, 1);
      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;

      g_value_init (val, G_TYPE_DOUBLE);
      g_value_set_double (val, 25-hours);
      g_object_set_property (G_OBJECT (priv->adjust), "upper", val);
      g_free (val);
      
      if (hours < 24) {
            if (!GTK_WIDGET_VISIBLE (priv->vscroll)) {
#if 0
                  priv->panels_moved = TRUE;
#endif
                  gtk_widget_show (priv->vscroll);
            }
      } else {
            if (GTK_WIDGET_VISIBLE (priv->vscroll)) {
#if 0
                  priv->panels_moved = TRUE;
#endif
                  gtk_widget_hide (priv->vscroll);
            }
      }

      dates_view_update_all_queries (view);
      dates_view_redraw (view, TRUE);
}

/** 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;
      
      g_return_if_fail (DATES_IS_VIEW (view));
      g_return_if_fail (date);
      
      priv = DATES_VIEW_GET_PRIVATE (view);
      if (icaltime_compare_date_only (*date, *priv->date) == 0) return;

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

      /* 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, TRUE);
}

/** 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, FALSE);
}

/** 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 set 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 alter the view 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 alter the view 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 alter the view 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 alter the view 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 alter the view 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 alter the view 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;
}

Generated by  Doxygen 1.6.0   Back to index