diff --git a/configure.ac b/configure.ac
index b3c0b24..f4469c0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -66,6 +66,10 @@ GJS_MIN_VERSION=0.7.8
 MUTTER_MIN_VERSION=2.91.4
 GTK_MIN_VERSION=2.91.7
 GIO_MIN_VERSION=2.25.9
+LIBECAL_REQUIRED=1.6.0
+LIBEDATASERVER_REQUIRED=1.2.0
+LIBEDATASERVERUI_REQUIRED=1.2.0
+
 
 # Collect more than 20 libraries for a prize!
 PKG_CHECK_MODULES(MUTTER_PLUGIN, gio-2.0 >= $GIO_MIN_VERSION
@@ -113,6 +117,10 @@ PKG_CHECK_EXISTS([gnome-bluetooth-1.0 >= 2.90.0],
 	 AC_SUBST([HAVE_BLUETOOTH],[0])
 	 AC_MSG_RESULT([no])])
 
+PKG_CHECK_MODULES(LIBECAL, libecal-1.2 >= $LIBECAL_REQUIRED libedataserver-1.2 >= $LIBEDATASERVER_REQUIRED libedataserverui-1.2 >= $LIBEDATASERVERUI_REQUIRED)
+AC_SUBST(LIBECAL_CFLAGS)
+AC_SUBST(LIBECAL_LIBS)
+
 MUTTER_BIN_DIR=`$PKG_CONFIG --variable=exec_prefix mutter-plugins`/bin
 # FIXME: metacity-plugins.pc should point directly to its .gir file
 MUTTER_LIB_DIR=`$PKG_CONFIG --variable=libdir mutter-plugins`
diff --git a/data/Makefile.am b/data/Makefile.am
index 4e73e03..8d7eedf 100644
--- a/data/Makefile.am
+++ b/data/Makefile.am
@@ -25,6 +25,8 @@ dist_images_DATA =				\
 themedir = $(pkgdatadir)/theme
 dist_theme_DATA =				\
 	theme/add-workspace.svg			\
+	theme/calendar-arrow-left.svg		\
+	theme/calendar-arrow-right.svg		\
 	theme/close-window.svg			\
 	theme/close.svg				\
 	theme/corner-ripple.png			\
diff --git a/data/gs-applications.menu b/data/gs-applications.menu
index b26ed00..cb4db03 100644
--- a/data/gs-applications.menu
+++ b/data/gs-applications.menu
@@ -77,6 +77,10 @@
 		<OnlyUnallocated/>
 		<Include>
 			<And>
+				<Or>
+					<Category>Documentation</Category>
+					<Not><Category>Core</Category></Not>
+				</Or>
 				<Not><Category>Settings</Category></Not>
 				<Not><Category>Screensaver</Category></Not>
 			</And>
diff --git a/data/theme/calendar-arrow-left.svg b/data/theme/calendar-arrow-left.svg
new file mode 100644
index 0000000..d5d97b3
--- /dev/null
+++ b/data/theme/calendar-arrow-left.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48+devel r9942 custom"
+   sodipodi:docname="New document 4">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="8.984481"
+     inkscape:cy="5.6224906"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     borderlayer="true"
+     inkscape:showpageshadow="false"
+     inkscape:window-width="930"
+     inkscape:window-height="681"
+     inkscape:window-x="1892"
+     inkscape:window-y="272"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid17403"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1036.3622)">
+    <path
+       sodipodi:type="star"
+       style="fill:#5f5f5f;fill-opacity:1;stroke:#5f5f5f;stroke-width:0.43015847;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+       id="path18028"
+       sodipodi:sides="3"
+       sodipodi:cx="84.5"
+       sodipodi:cy="337.5"
+       sodipodi:r1="5"
+       sodipodi:r2="2.5"
+       sodipodi:arg1="0.52359878"
+       sodipodi:arg2="1.5707963"
+       inkscape:flatsided="true"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="M 88.830127,340 80.169873,340 84.5,332.5 z"
+       transform="matrix(0,1.3621708,0.99186247,0,-325.48222,929.32667)" />
+  </g>
+</svg>
diff --git a/data/theme/calendar-arrow-right.svg b/data/theme/calendar-arrow-right.svg
new file mode 100644
index 0000000..545da7e
--- /dev/null
+++ b/data/theme/calendar-arrow-right.svg
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="16"
+   height="16"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.48+devel r9942 custom"
+   sodipodi:docname="arrow-left.svg">
+  <defs
+     id="defs4" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1"
+     inkscape:cx="7.7366092"
+     inkscape:cy="6.4536271"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     borderlayer="true"
+     inkscape:showpageshadow="false"
+     inkscape:window-width="930"
+     inkscape:window-height="681"
+     inkscape:window-x="1892"
+     inkscape:window-y="272"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid17403"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-1036.3622)">
+    <path
+       sodipodi:type="star"
+       style="fill:#5f5f5f;fill-opacity:1;stroke:#5f5f5f;stroke-width:0.43015847;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
+       id="path18028"
+       sodipodi:sides="3"
+       sodipodi:cx="84.5"
+       sodipodi:cy="337.5"
+       sodipodi:r1="5"
+       sodipodi:r2="2.5"
+       sodipodi:arg1="0.52359878"
+       sodipodi:arg2="1.5707963"
+       inkscape:flatsided="true"
+       inkscape:rounded="0"
+       inkscape:randomized="0"
+       d="M 88.830127,340 80.169873,340 84.5,332.5 z"
+       transform="matrix(0,1.3621708,-0.99186247,0,342.48324,929.32667)" />
+  </g>
+</svg>
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index cf6f1eb..f16e337 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -674,6 +674,17 @@ StTooltip StLabel {
 
 /* Calendar popup */
 
+#calendarArea {
+    /* this is the width of the entire popup */
+    width: 600px;
+}
+
+.calendar-vertical-separator {
+    -stipple-width: 1px;
+    -stipple-color: #505050;
+    width: 1.5em;
+}
+
 #calendarPopup {
     border-radius: 5px;
     background: rgba(0,0,0,0.9);
@@ -686,37 +697,177 @@ StTooltip StLabel {
 }
 
 .calendar {
-    spacing-rows: 5px;
-    spacing-columns: 3px;
+    padding: .4em 1.75em;
+    spacing-rows: 0px;
+    spacing-columns: 0px;
 }
 
 .calendar-change-month {
+    color: #666666;
+    font-size: 10px;
     padding: 2px;
 }
 
 .calendar-change-month:hover {
-    background: #314a6c;
+    background: #999999;
     border-radius: 5px;
 }
 
 .calendar-change-month:active {
-    background: #213050;
+    background: #aaaaaa;
     border-radius: 5px;
 }
 
+.calendar-change-month-back {
+    width: 20px;
+    height: 20px;
+    background-image: url("calendar-arrow-left.svg");
+}
+.calendar-change-month-back:hover {
+    background: #999999;
+    background-image: url("calendar-arrow-left.svg");
+}
+.calendar-change-month-back:active {
+    background: #aaaaaa;
+    background-image: url("calendar-arrow-left.svg");
+}
+
+.calendar-change-month-forward {
+    width: 20px;
+    height: 20px;
+    background-image: url("calendar-arrow-right.svg");
+}
+.calendar-change-month-forward:hover {
+    background: #999999;
+    background-image: url("calendar-arrow-right.svg");
+}
+.calendar-change-month-forward:active {
+    background: #aaaaaa;
+    background-image: url("calendar-arrow-right.svg");
+}
+
+.datemenu-date-label {
+    padding: .4em 1.75em;
+    font-size: 16px;
+    color: #ffffff;
+}
+
+.calendar-day-base {
+    font-size: 10px;
+    text-align: center;
+    width: 24px;
+    height: 24px;
+}
+
+.calendar-day-base:hover {
+    background: #777777;
+}
+
+.calendar-day-base:active {
+    background: #555555;
+}
+
+.calendar-day-heading {
+    color: #666666;
+}
+
+.calendar-week-number {
+    color: #666666;
+    font-weight: bold;
+}
+
+/* Hack used in lieu of border-collapse - see calendar.js */
 .calendar-day {
-    padding: 1px 2px;
+    border: 1px solid #333333;
+    color: #cccccc;
+    border-top-width: 0;
+    border-left-width: 0;
+}
+.calendar-day-top {
+    border-top-width: 1px;
+}
+.calendar-day-left {
+    border-left-width: 1px;
+}
+
+.calendar-work-day {
+}
+
+.calendar-nonwork-day {
+    background: #181818;
 }
 
 .calendar-today {
+    color: #ffffff;
     font-weight: bold;
-    background: #ffffff;
-    color: black;
-    border-radius: 5px;
+    background-gradient-direction: vertical;
+    background-gradient-start: #3c3c3c;
+    background-gradient-end: #131313;
 }
 
 .calendar-other-month-day {
-    color: #cccccc;
+    color: #333333;
+}
+
+.calendar-day-with-events {
+    font-weight: bold;
+}
+
+.events-header-vbox {
+    spacing: 10px;
+}
+
+.events-header {
+    height: 40px;
+}
+
+.events-header-hbox {
+    spacing: 8px;
+    padding: 0.3em;
+}
+
+.events-day-header {
+    font-size: 14px;
+    color: rgba(153, 153, 153, 1.0);
+}
+
+.events-day-dayname {
+    font-size: 12px;
+    color: rgba(153, 153, 153, 1.0);
+    text-align: left;
+}
+
+.events-day-time {
+    font-size: 12px;
+    font-weight: bold;
+    color: #fff;
+    text-align: right;
+}
+
+.events-day-task {
+    font-size: 12px;
+    color: rgba(153, 153, 153, 1.0);
+}
+
+.events-day-name-box {
+    width: 20px;
+}
+
+.events-time-box {
+    width: 70px;
+}
+
+.events-event-box {
+}
+
+.open-calendar {
+    padding-bottom: 12px;
+    padding-left: 12px;
+    height: 40px;
+}
+
+.open-calendar:hover {
+    background: #666;
 }
 
 .url-highlighter {
@@ -895,10 +1046,6 @@ StTooltip StLabel {
     padding-left: 4px;
 }
 
-.calendar-calendarweek {
-    color: #666666;
-}
-
 /* App Switcher */
 #altTabPopup {
     padding: 8px;
diff --git a/js/Makefile.am b/js/Makefile.am
index 3a7a2da..21683af 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -19,6 +19,7 @@ nobase_dist_js_DATA = 	\
 	ui/chrome.js		\
 	ui/ctrlAltTab.js	\
 	ui/dash.js		\
+	ui/dateMenu.js		\
 	ui/dnd.js		\
 	ui/docDisplay.js	\
 	ui/endSessionDialog.js	\
diff --git a/js/ui/calendar.js b/js/ui/calendar.js
index 8f58f69..e79702d 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -4,19 +4,76 @@ const Clutter = imports.gi.Clutter;
 const Gio = imports.gi.Gio;
 const Lang = imports.lang;
 const St = imports.gi.St;
+const Signals = imports.signals;
 const Pango = imports.gi.Pango;
 const Gettext_gtk30 = imports.gettext.domain('gtk30');
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+const Mainloop = imports.mainloop;
+const Shell = imports.gi.Shell;
 
 const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
 const WEEKDATE_HEADER_WIDTH_DIGITS = 3;
 const SHOW_WEEKDATE_KEY = 'show-weekdate';
 
+// in org.gnome.desktop.interface
+const CLOCK_FORMAT_KEY        = 'clock-format';
+
 function _sameDay(dateA, dateB) {
     return (dateA.getDate() == dateB.getDate() &&
             dateA.getMonth() == dateB.getMonth() &&
             dateA.getYear() == dateB.getYear());
 }
 
+function _sameYear(dateA, dateB) {
+    return (dateA.getYear() == dateB.getYear());
+}
+
+/* TODO: maybe needs config - right now we assume that Saturday and
+ * Sunday are non-work days (not true in e.g. Israel, it's Sunday and
+ * Monday there)
+ */
+function _isWorkDay(date) {
+    return date.getDay() != 0 && date.getDay() != 6;
+}
+
+function _getBeginningOfDay(date) {
+    let ret = new Date(date.getTime());
+    ret.setHours(0);
+    ret.setMinutes(0);
+    ret.setMilliseconds(0);
+    return ret;
+}
+
+function _getEndOfDay(date) {
+    let ret = new Date(date.getTime());
+    ret.setHours(23);
+    ret.setMinutes(59);
+    ret.setMilliseconds(999);
+    return ret;
+}
+
+function _formatEventTime(event, clockFormat) {
+    let ret;
+    if (event.allDay) {
+        /* Translators: Shown in calendar event list for all day events */
+        ret = _("All Day");
+    } else {
+        switch (clockFormat) {
+        case '24h':
+            ret = event.date.toLocaleFormat('%H:%M');
+            break;
+
+        default:
+            /* explicit fall-through */
+        case '12h':
+            ret = event.date.toLocaleFormat('%l:%M %p');
+            break;
+        }
+    }
+    return ret;
+}
+
 function _getCalendarWeekForDate(date) {
     // Based on the algorithms found here:
     // http://en.wikipedia.org/wiki/Talk:ISO_week_date
@@ -43,12 +100,259 @@ function _getDigitWidth(actor){
     return width;
 }
 
-function Calendar() {
+function _getCalendarDayAbbreviation(dayNumber) {
+    let abbreviations = [
+        /* Translators: Calendar grid abbreviation for Sunday.
+         *
+         * NOTE: These abbreviations are always shown together and in
+         * order, e.g. "S M T W T F S".
+         */
+        _("S"),
+        /* Translators: Calendar grid abbreviation for Monday */
+        _("M"),
+        /* Translators: Calendar grid abbreviation for Tuesday */
+        _("T"),
+        /* Translators: Calendar grid abbreviation for Wednesday */
+        _("W"),
+        /* Translators: Calendar grid abbreviation for Thursday */
+        _("T"),
+        /* Translators: Calendar grid abbreviation for Friday */
+        _("F"),
+        /* Translators: Calendar grid abbreviation for Saturday */
+        _("S")
+    ];
+    return abbreviations[dayNumber];
+}
+
+function _getEventDayAbbreviation(dayNumber) {
+    let abbreviations = [
+        /* Translators: Event list abbreviation for Sunday.
+         *
+         * NOTE: These abbreviations are normally not shown together
+         * so they need to be unique (e.g. Tuesday and Thursday cannot
+         * both be 'T').
+         */
+        _("Su"),
+        /* Translators: Event list abbreviation for Monday */
+        _("M"),
+        /* Translators: Event list abbreviation for Tuesday */
+        _("T"),
+        /* Translators: Event list abbreviation for Wednesday */
+        _("W"),
+        /* Translators: Event list abbreviation for Thursday */
+        _("Th"),
+        /* Translators: Event list abbreviation for Friday */
+        _("F"),
+        /* Translators: Event list abbreviation for Saturday */
+        _("S")
+    ];
+    return abbreviations[dayNumber];
+}
+
+// Abstraction for an appointment/event in a calendar
+
+function CalendarEvent(date, summary, allDay) {
+    this._init(date, summary, allDay);
+}
+
+CalendarEvent.prototype = {
+    _init: function(date, summary, allDay) {
+        this.date = date;
+        this.summary = summary;
+        this.allDay = allDay;
+    }
+};
+
+// Interface for appointments/events - e.g. the contents of a calendar
+//
+
+// First, an implementation with no events
+function EmptyEventSource() {
     this._init();
 }
 
-Calendar.prototype = {
+EmptyEventSource.prototype = {
+    _init: function() {
+    },
+
+    requestRange: function(begin, end) {
+    },
+
+    getEvents: function(begin, end) {
+        let result = [];
+        return result;
+    },
+
+    hasEvents: function(day) {
+        return false;
+    }
+};
+Signals.addSignalMethods(EmptyEventSource.prototype);
+
+// Second, wrap native Evolution event source
+function EvolutionEventSource() {
+    this._init();
+}
+
+EvolutionEventSource.prototype = {
+    _init: function() {
+        this._native = new Shell.EvolutionEventSource();
+        this._native.connect('changed', Lang.bind(this, function() {
+            this.emit('changed');
+        }));
+    },
+
+    requestRange: function(begin, end) {
+        this._native.request_range(begin.getTime(), end.getTime());
+    },
+
+    getEvents: function(begin, end) {
+        let result = [];
+        let nativeEvents = this._native.get_events(begin.getTime(), end.getTime());
+        for (let n = 0; n < nativeEvents.length; n++) {
+            let nativeEvent = nativeEvents[n];
+            result.push(new CalendarEvent(new Date(nativeEvent.msec_begin), nativeEvent.summary, nativeEvent.all_day));
+        }
+        return result;
+    },
+
+    hasEvents: function(day) {
+        let dayBegin = _getBeginningOfDay(day);
+        let dayEnd = _getEndOfDay(day);
+
+        let events = this.getEvents(dayBegin, dayEnd);
+
+        if (events.length == 0)
+            return false;
+
+        return true;
+    }
+};
+Signals.addSignalMethods(EvolutionEventSource.prototype);
+
+// Finally, an implementation with fake events
+function FakeEventSource() {
+    this._init();
+}
+
+FakeEventSource.prototype = {
     _init: function() {
+
+        this._fakeEvents = [];
+
+        // Generate fake events
+        //
+        let midnightToday = _getBeginningOfDay(new Date());
+        let summary = '';
+
+        // '10-oclock pow-wow' is an event occuring IN THE PAST every four days at 10am
+        for (let n = 0; n < 10; n++) {
+            let t = new Date(midnightToday.getTime() - n * 4 * 86400 * 1000);
+            t.setHours(10);
+            summary = '10-oclock pow-wow (n=' + n + ')';
+            this._fakeEvents.push(new CalendarEvent(t, summary, false));
+        }
+
+        // '11-oclock thing' is an event occuring every three days at 11am
+        for (let n = 0; n < 10; n++) {
+            let t = new Date(midnightToday.getTime() + n * 3 * 86400 * 1000);
+            t.setHours(11);
+            summary = '11-oclock thing (n=' + n + ')';
+            this._fakeEvents.push(new CalendarEvent(t, summary, false));
+        }
+
+        // 'Weekly Meeting' is an event occuring every seven days at 1:45pm (two days displaced)
+        for (let n = 0; n < 5; n++) {
+            let t = new Date(midnightToday.getTime() + (n * 7 + 2) * 86400 * 1000);
+            t.setHours(13);
+            t.setMinutes(45);
+            summary = 'Weekly Meeting (n=' + n + ')';
+            this._fakeEvents.push(new CalendarEvent(t, summary, false));
+        }
+
+        // 'Fun All Day' is an all-day event occuring every fortnight (three days displayed)
+        for (let n = 0; n < 10; n++) {
+            let t = new Date(midnightToday.getTime() + (n * 14 + 3) * 86400 * 1000);
+            summary = 'Fun All Day (n=' + n + ')';
+            this._fakeEvents.push(new CalendarEvent(t, summary, true));
+        }
+
+        // 'Get Married' is an event that actually reflects reality (Dec 4, 2010) :-)
+        this._fakeEvents.push(new CalendarEvent(new Date(2010, 11, 4, 16, 0), 'Get Married', false));
+
+        // ditto for 'NE Patriots vs NY Jets'
+        this._fakeEvents.push(new CalendarEvent(new Date(2010, 11, 6, 20, 30), 'NE Patriots vs NY Jets', false));
+
+        // An event for tomorrow @6:30pm that is added/removed every five
+        // seconds (to check that the ::changed signal works)
+        let transientEventDate = new Date(midnightToday.getTime() + 86400 * 1000);
+        transientEventDate.setHours(18);
+        transientEventDate.setMinutes(30);
+        transientEventDate.setSeconds(0);
+        Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
+        this._includeTransientEvent = false;
+        this._transientEvent = new CalendarEvent(transientEventDate, 'A Transient Event', false);
+        this._transientEventCounter = 1;
+    },
+
+    _updateTransientEvent: function() {
+        this._includeTransientEvent = !this._includeTransientEvent;
+        this._transientEventCounter = this._transientEventCounter + 1;
+        this._transientEvent.summary = 'A Transient Event (' + this._transientEventCounter + ')';
+        this.emit('changed');
+        Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
+    },
+
+    requestRange: function(begin, end) {
+    },
+
+    getEvents: function(begin, end) {
+        let result = [];
+        //log('begin:' + begin);
+        //log('end:  ' + end);
+        for(let n = 0; n < this._fakeEvents.length; n++) {
+            let event = this._fakeEvents[n];
+            if (event.date >= begin && event.date <= end) {
+                result.push(event);
+            }
+            //log('when:' + event.date + ' summary:' + event.summary);
+        }
+        if (this._includeTransientEvent && this._transientEvent.date >= begin && this._transientEvent.date <= end)
+            result.push(this._transientEvent);
+        result.sort(function(event1, event2) {
+            return event1.date.getTime() - event2.date.getTime();
+        });
+        return result;
+    },
+
+    hasEvents: function(day) {
+        let dayBegin = _getBeginningOfDay(day);
+        let dayEnd = _getEndOfDay(day);
+
+        let events = this.getEvents(dayBegin, dayEnd);
+
+        if (events.length == 0)
+            return false;
+
+        return true;
+    }
+
+};
+Signals.addSignalMethods(FakeEventSource.prototype);
+
+// Calendar:
+// @eventSource: is an object implementing the EventSource API, e.g. the
+// requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
+function Calendar(eventSource) {
+    this._init(eventSource);
+}
+
+Calendar.prototype = {
+    _init: function(eventSource) {
+        this._eventSource = eventSource;
+
+        this._eventSource.connect('changed', Lang.bind(this, this._update));
+
         // FIXME: This is actually the fallback method for GTK+ for the week start;
         // GTK+ by preference uses nl_langinfo (NL_TIME_FIRST_WEEKDAY). We probably
         // should add a C function so we can do the full handling.
@@ -71,6 +375,7 @@ Calendar.prototype = {
         }
 
         // Find the ordering for month/year in the calendar heading
+        this._headerFormatWithoutYear = '%B';
         switch (Gettext_gtk30.gettext('calendar:MY')) {
         case 'calendar:MY':
             this._headerFormat = '%B %Y';
@@ -85,7 +390,7 @@ Calendar.prototype = {
         }
 
         // Start off with the current date
-        this.date = new Date();
+        this.selectedDate = new Date();
 
         this.actor = new St.Table({ homogeneous: false,
                                     style_class: 'calendar',
@@ -100,9 +405,10 @@ Calendar.prototype = {
 
     // Sets the calendar to show a specific date
     setDate: function(date) {
-        if (!_sameDay(date, this.date)) {
-            this.date = date;
+        if (!_sameDay(date, this.selectedDate)) {
+            this.selectedDate = date;
             this._update();
+            this.emit('selected-date-changed', new Date(this.selectedDate));
         }
     },
 
@@ -116,45 +422,36 @@ Calendar.prototype = {
                        { row: 0, col: 0, col_span: offsetCols + 7 });
 
         this.actor.connect('style-changed', Lang.bind(this, this._onStyleChange));
-        let [backlabel, forwardlabel] = ['&lt;', '&gt;'];
-        if (St.Widget.get_default_direction () == St.TextDirection.RTL) {
-            [backlabel, forwardlabel] = [forwardlabel, backlabel];
-        }
 
-        let back = new St.Button({ label: backlabel, style_class: 'calendar-change-month'  });
+        let back = new St.Button({ style_class: 'calendar-change-month-back' });
         this._topBox.add(back);
-        back.connect('clicked', Lang.bind(this, this._prevMonth));
+        back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
 
-        this._dateLabel = new St.Label();
+        this._dateLabel = new St.Label({style_class: 'calendar-change-month'});
         this._topBox.add(this._dateLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
 
-        let forward = new St.Button({ label: forwardlabel, style_class: 'calendar-change-month' });
+        let forward = new St.Button({ style_class: 'calendar-change-month-forward' });
         this._topBox.add(forward);
-        forward.connect('clicked', Lang.bind(this, this._nextMonth));
+        forward.connect('clicked', Lang.bind(this, this._onNextMonthButtonClicked));
 
+        // Add weekday labels...
+        //
         // We need to figure out the abbreviated localized names for the days of the week;
         // we do this by just getting the next 7 days starting from right now and then putting
         // them in the right cell in the table. It doesn't matter if we add them in order
-        let iter = new Date(this.date);
+        let iter = new Date(this.selectedDate);
         iter.setSeconds(0); // Leap second protection. Hah!
         iter.setHours(12);
-
-        if (this._useWeekdate) {
-            this._weekdateHeader = new St.Label();
-            this.actor.add(this._weekdateHeader,
-                              { row: 1,
-                                col: 0,
-                                x_fill: false, x_align: St.Align.MIDDLE });
-            this._setWeekdateHeaderWidth();
-        } else {
-            this._weekdateHeader = null;
-        }
-
         for (let i = 0; i < 7; i++) {
-            this.actor.add(new St.Label({ text: iter.toLocaleFormat('%a') }),
+            // Could use iter.toLocaleFormat('%a') but that normally gives three characters
+            // and we want, ideally, a single character for e.g. S M T W T F S
+            let customDayAbbrev = _getCalendarDayAbbreviation(iter.getDay());
+            let label = new St.Label({ text: customDayAbbrev });
+            label.style_class = 'calendar-day-base calendar-day-heading';
+            this.actor.add(label,
                            { row: 1,
                              col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7,
-                             x_fill: false, x_align: St.Align.END });
+                             x_fill: false, x_align: St.Align.MIDDLE });
             iter.setTime(iter.getTime() + MSECS_IN_DAY);
         }
 
@@ -178,33 +475,35 @@ Calendar.prototype = {
         switch (event.get_scroll_direction()) {
         case Clutter.ScrollDirection.UP:
         case Clutter.ScrollDirection.LEFT:
-            this._prevMonth();
+            this._onPrevMonthButtonClicked();
             break;
         case Clutter.ScrollDirection.DOWN:
         case Clutter.ScrollDirection.RIGHT:
-            this._nextMonth();
+            this._onNextMonthButtonClicked();
             break;
         }
     },
 
-    _prevMonth: function() {
-        if (this.date.getMonth() == 0) {
-            this.date.setMonth(11);
-            this.date.setFullYear(this.date.getFullYear() - 1);
+    _onPrevMonthButtonClicked: function() {
+        let newDate = new Date(this.selectedDate);
+        if (newDate.getMonth() == 0) {
+            newDate.setMonth(11);
+            newDate.setFullYear(newDate.getFullYear() - 1);
         } else {
-            this.date.setMonth(this.date.getMonth() - 1);
+            newDate.setMonth(newDate.getMonth() - 1);
         }
-        this._update();
+        this.setDate(newDate);
    },
 
-    _nextMonth: function() {
-        if (this.date.getMonth() == 11) {
-            this.date.setMonth(0);
-            this.date.setFullYear(this.date.getFullYear() + 1);
+    _onNextMonthButtonClicked: function() {
+        let newDate = new Date(this.selectedDate);
+        if (newDate.getMonth() == 11) {
+            newDate.setMonth(0);
+            newDate.setFullYear(newDate.getFullYear() + 1);
         } else {
-            this.date.setMonth(this.date.getMonth() + 1);
+            newDate.setMonth(newDate.getMonth() + 1);
         }
-        this._update();
+        this.setDate(newDate);
     },
 
     _onSettingsChange: function() {
@@ -214,7 +513,12 @@ Calendar.prototype = {
     },
 
     _update: function() {
-        this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat);
+        let now = new Date();
+
+        if (_sameYear(this.selectedDate, now))
+            this._dateLabel.text = this.selectedDate.toLocaleFormat(this._headerFormatWithoutYear);
+        else
+            this._dateLabel.text = this.selectedDate.toLocaleFormat(this._headerFormat);
 
         // Remove everything but the topBox and the weekday labels
         let children = this.actor.get_children();
@@ -222,45 +526,200 @@ Calendar.prototype = {
             children[i].destroy();
 
         // Start at the beginning of the week before the start of the month
-        let iter = new Date(this.date);
-        iter.setDate(1);
-        iter.setSeconds(0);
-        iter.setHours(12);
-        let daysToWeekStart = (7 + iter.getDay() - this._weekStart) % 7;
-        iter.setTime(iter.getTime() - daysToWeekStart * MSECS_IN_DAY);
-
-        let now = new Date();
-
+        let beginDate = new Date(this.selectedDate);
+        beginDate.setDate(1);
+        beginDate.setSeconds(0);
+        beginDate.setHours(12);
+        let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7;
+        beginDate.setTime(beginDate.getTime() - daysToWeekStart * MSECS_IN_DAY);
+
+        let iter = new Date(beginDate);
         let row = 2;
         while (true) {
-            let label = new St.Label({ text: iter.getDate().toString() });
-            if (_sameDay(now, iter))
-                label.style_class = 'calendar-day calendar-today';
-            else if (iter.getMonth() != this.date.getMonth())
-                label.style_class = 'calendar-day calendar-other-month-day';
+            let button = new St.Button({ label: iter.getDate().toString() });
+
+            let iterStr = iter.toUTCString();
+            button.connect('clicked', Lang.bind(this, function() {
+                let newly_selectedDate = new Date(iterStr);
+                this.setDate(newly_selectedDate);
+            }));
+
+            let styleClass;
+            let hasEvents;
+            hasEvents = this._eventSource.hasEvents(iter);
+            styleClass = 'calendar-day-base calendar-day';
+            if (_isWorkDay(iter))
+                styleClass += ' calendar-work-day'
             else
-                label.style_class = 'calendar-day';
+                styleClass += ' calendar-nonwork-day'
+
+            // Hack used in lieu of border-collapse - see gnome-shell.css
+            if (row == 2)
+                styleClass = 'calendar-day-top ' + styleClass;
+            if (iter.getDay() == 0)
+                styleClass = 'calendar-day-left ' + styleClass;
+
+            if (_sameDay(now, iter))
+                styleClass += ' calendar-today';
+            else if (iter.getMonth() != this.selectedDate.getMonth())
+                styleClass += ' calendar-other-month-day';
+
+            if (_sameDay(this.selectedDate, iter))
+                button.add_style_pseudo_class('active');
+
+            if (hasEvents)
+                styleClass += ' calendar-day-with-events'
+
+            button.style_class = styleClass;
 
             let offsetCols = this._useWeekdate ? 1 : 0;
-            this.actor.add(label,
-                           { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7,
-                             x_fill: false, x_align: St.Align.END });
+            this.actor.add(button,
+                           { row: row, col: offsetCols + (7 + iter.getDay() - this._weekStart) % 7 });
 
             if (this._useWeekdate && iter.getDay() == 4) {
                 let label = new St.Label({ text: _getCalendarWeekForDate(iter).toString(),
-                                           style_class: 'calendar-day calendar-calendarweek'});
+                                           style_class: 'calendar-day-base calendar-week-number'});
                 this.actor.add(label,
-                              { row: row, col: 0,
-		                        x_fill: false, x_align: St.Align.MIDDLE });
+                               { row: row, col: 0, y_align: St.Align.MIDDLE });
             }
 
             iter.setTime(iter.getTime() + MSECS_IN_DAY);
             if (iter.getDay() == this._weekStart) {
                 // We stop on the first "first day of the week" after the month we are displaying
-                if (iter.getMonth() > this.date.getMonth() || iter.getYear() > this.date.getYear())
+                if (iter.getMonth() > this.selectedDate.getMonth() || iter.getYear() > this.selectedDate.getYear())
                     break;
                 row++;
             }
         }
+        // Signal to the event source that we are interested in events
+        // only from this date range
+        this._eventSource.requestRange(beginDate, iter);
+    }
+};
+
+Signals.addSignalMethods(Calendar.prototype);
+
+function EventsList(eventSource) {
+    this._init(eventSource);
+}
+
+EventsList.prototype = {
+    _init: function(eventSource) {
+        this.actor = new St.BoxLayout({ vertical: true, style_class: 'events-header-vbox'});
+        this._date = new Date();
+        this._eventSource = eventSource;
+        this._eventSource.connect('changed', Lang.bind(this, this._update));
+        this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
+        this._desktopSettings.connect('changed', Lang.bind(this, this._update));
+        this._update();
+    },
+
+    _addEvent: function(dayNameBox, timeBox, eventTitleBox, includeDayName, day, time, desc) {
+        if (includeDayName) {
+            dayNameBox.add(new St.Label( { style_class: 'events-day-dayname',
+                                           text: day } ),
+                           { x_fill: true } );
+        }
+        timeBox.add(new St.Label( { style_class: 'events-day-time',
+                                    text: time} ),
+                    { x_fill: true } );
+        eventTitleBox.add(new St.Label( { style_class: 'events-day-task',
+                                          text: desc} ));
+    },
+
+    _addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
+        let events = this._eventSource.getEvents(begin, end);
+
+        let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
+
+        if (events.length == 0 && !showNothingScheduled)
+            return;
+
+        let vbox = new St.BoxLayout( {vertical: true} );
+        this.actor.add(vbox);
+
+        vbox.add(new St.Label({ style_class: 'events-day-header', text: header }));
+        let box = new St.BoxLayout({style_class: 'events-header-hbox'});
+        let dayNameBox = new St.BoxLayout({ vertical: true, style_class: 'events-day-name-box' });
+        let timeBox = new St.BoxLayout({ vertical: true, style_class: 'events-time-box' });
+        let eventTitleBox = new St.BoxLayout({ vertical: true, style_class: 'events-event-box' });
+        box.add(dayNameBox, {x_fill: false});
+        box.add(timeBox, {x_fill: false});
+        box.add(eventTitleBox, {expand: true});
+        vbox.add(box);
+
+        for (let n = 0; n < events.length; n++) {
+            let event = events[n];
+            let dayString = _getEventDayAbbreviation(event.date.getDay());
+            let timeString = _formatEventTime(event, clockFormat);
+            let summaryString = event.summary;
+            this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString);
+        }
+
+        if (events.length == 0 && showNothingScheduled) {
+            let now = new Date();
+            /* Translators: Text to show if there are no events */
+            let nothingEvent = new CalendarEvent(now, _("Nothing Scheduled"), true);
+            let timeString = _formatEventTime(nothingEvent, clockFormat);
+            this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingEvent.summary);
+        }
+    },
+
+    _showOtherDay: function(day) {
+        this.actor.destroy_children();
+
+        let dayBegin = _getBeginningOfDay(day);
+        let dayEnd = _getEndOfDay(day);
+
+        let dayString;
+        let now = new Date();
+        if (_sameYear(day, now))
+            dayString = day.toLocaleFormat('%A, %B %d');
+        else
+            dayString = day.toLocaleFormat('%A, %B %d, %Y');
+        this._addPeriod(dayString, dayBegin, dayEnd, false, true);
+    },
+
+    _showToday: function() {
+        this.actor.destroy_children();
+
+        let now = new Date();
+        let dayBegin = _getBeginningOfDay(now);
+        let dayEnd = _getEndOfDay(now);
+        this._addPeriod(_("Today"), dayBegin, dayEnd, false, true);
+
+        dayBegin.setDate(dayBegin.getDate() + 1);
+        dayEnd.setDate(dayEnd.getDate() + 1);
+        this._addPeriod(_("Tomorrow"), dayBegin, dayEnd, false, true);
+
+        if (dayEnd.getDay() == 6 || dayEnd.getDay() == 0) {
+            dayBegin.setDate(dayEnd.getDate() + 1);
+            dayEnd.setDate(dayBegin.getDate() + 6 - dayBegin.getDay());
+
+            this._addPeriod(_("Next week"), dayBegin, dayEnd, true, false);
+            return;
+        } else {
+            let d = 6 - dayEnd.getDay() - 1;
+            dayBegin.setDate(dayBegin.getDate() + 1);
+            dayEnd.setDate(dayEnd.getDate() + 1 + d);
+            this._addPeriod(_("This week"), dayBegin, dayEnd, true, false);
+        }
+    },
+
+    // Sets the event list to show events from a specific date
+    setDate: function(date) {
+        if (!_sameDay(date, this._date)) {
+            this._date = date;
+            this._update();
+        }
+    },
+
+    _update: function() {
+        let today = new Date();
+        if (_sameDay (this._date, today)) {
+            this._showToday();
+        } else {
+            this._showOtherDay(this._date);
+        }
     }
 };
diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js
new file mode 100644
index 0000000..b790e11
--- /dev/null
+++ b/js/ui/dateMenu.js
@@ -0,0 +1,225 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Cairo = imports.cairo;
+const Clutter = imports.gi.Clutter;
+const Shell = imports.gi.Shell;
+const St = imports.gi.St;
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+
+const Util = imports.misc.util;
+const Main = imports.ui.main;
+const PanelMenu = imports.ui.panelMenu;
+const PopupMenu = imports.ui.popupMenu;
+const Calendar = imports.ui.calendar;
+
+// in org.gnome.desktop.interface
+const CLOCK_FORMAT_KEY        = 'clock-format';
+
+// in org.gnome.shell.clock
+const CLOCK_SHOW_DATE_KEY     = 'show-date';
+const CLOCK_SHOW_SECONDS_KEY  = 'show-seconds';
+
+function _onVertSepRepaint (area)
+{
+    let cr = area.get_context();
+    let themeNode = area.get_theme_node();
+    let [width, height] = area.get_surface_size();
+    let found;
+    let stippleWidth = 1.0;
+    let stippleColor = new Clutter.Color();
+    [found, stippleWidth] = themeNode.lookup_length('-stipple-width', false);
+    themeNode.lookup_color('-stipple-color', false, stippleColor);
+    let x = Math.floor(width/2) + 0.5;
+    cr.moveTo(x, 0);
+    cr.lineTo(x, height);
+    Clutter.cairo_set_source_color(cr, stippleColor);
+    cr.setDash([1, 3], 1); // Hard-code for now
+    cr.setLineWidth(stippleWidth);
+    cr.stroke();
+};
+
+function DateMenuButton() {
+    this._init();
+}
+
+DateMenuButton.prototype = {
+    __proto__: PanelMenu.Button.prototype,
+
+    _init: function() {
+        let item;
+        let hbox;
+        let vbox;
+
+        //this._eventSource = new Calendar.EmptyEventSource();
+        //this._eventSource = new Calendar.FakeEventSource();
+        this._eventSource = new Calendar.EvolutionEventSource();
+        // TODO: write e.g. EvolutionEventSource
+
+        PanelMenu.Button.prototype._init.call(this, St.Align.START);
+
+        this._clock = new St.Label();
+        this.actor.set_child(this._clock);
+
+        hbox = new St.BoxLayout({name: 'calendarArea'});
+        this.menu.addActor(hbox);
+
+        // Fill up the first column
+
+        vbox = new St.BoxLayout({vertical: true});
+        hbox.add(vbox);
+
+        // Date
+        this._date = new St.Label();
+        this._date.style_class = 'datemenu-date-label';
+        vbox.add(this._date);
+
+        this._eventList = new Calendar.EventsList(this._eventSource);
+
+        // Calendar
+        this._calendar = new Calendar.Calendar(this._eventSource);
+        this._calendar.connect('selected-date-changed',
+                               Lang.bind(this, function(calendar, date) {
+                                   this._eventList.setDate(date);
+                               }));
+        vbox.add(this._calendar.actor);
+
+        //item = new St.Button({style_class: 'popup-menu-item', label: 'foobar'});
+        //vbox.add(item);
+        item = new PopupMenu.PopupSeparatorMenuItem();
+        item.actor.remove_actor(item._drawingArea);
+        vbox.add(item._drawingArea);
+        item = new PopupMenu.PopupMenuItem(_("Date and Time Settings"));
+        item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
+        vbox.add(item.actor);
+
+        // Add vertical separator
+
+        item = new St.DrawingArea({ style_class: 'calendar-vertical-separator',
+                                    pseudo_class: 'highlighted' });
+        item.connect('repaint', Lang.bind(this, _onVertSepRepaint));
+        hbox.add(item);
+
+        // Fill up the second column
+        //
+        vbox = new St.BoxLayout({vertical: true});
+        hbox.add(vbox);
+
+        // Event list
+        vbox.add(this._eventList.actor);
+
+        item = new PopupMenu.PopupMenuItem(_("Open Calendar"));
+        item.connect('activate', Lang.bind(this, this._onOpenCalendarActivate));
+        vbox.add(item.actor, {y_align : St.Align.END, expand : true, y_fill : false});
+
+        // Whenever the menu is opened, select today
+        this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
+            if (isOpen) {
+                let now = new Date();
+                this._calendar.setDate(now);
+                // No need to update this._eventList as ::selected-date-changed
+                // signal will fire
+            }
+        }));
+
+        // Done with hbox for calendar and event list
+
+        // Add separator
+        //item = new PopupMenu.PopupSeparatorMenuItem();
+        //this.menu.addMenuItem(item);
+
+        // Add button to get to the Date and Time settings
+        //this.menu.addAction(_("Date and Time Settings"),
+        //                    Lang.bind(this, this._onPreferencesActivate));
+
+        // Track changes to clock settings
+        this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
+        this._clockSettings = new Gio.Settings({ schema: 'org.gnome.shell.clock' });
+        this._desktopSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
+        this._clockSettings.connect('changed', Lang.bind(this, this._updateClockAndDate));
+
+        // Start the clock
+        this._updateClockAndDate();
+    },
+
+    _updateClockAndDate: function() {
+        let format = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
+        let showDate = this._clockSettings.get_boolean(CLOCK_SHOW_DATE_KEY);
+        let showSeconds = this._clockSettings.get_boolean(CLOCK_SHOW_SECONDS_KEY);
+
+        let clockFormat;
+        let dateFormat;
+
+        switch (format) {
+            case '24h':
+                if (showDate)
+	            /* Translators: This is the time format with date used
+                       in 24-hour mode. */
+                    clockFormat = showSeconds ? _("%a %b %e, %R:%S")
+                                              : _("%a %b %e, %R");
+                else
+	            /* Translators: This is the time format without date used
+                       in 24-hour mode. */
+                    clockFormat = showSeconds ? _("%a %R:%S")
+                                              : _("%a %R");
+                break;
+            case '12h':
+            default:
+                if (showDate)
+	            /* Translators: This is a time format with date used
+                       for AM/PM. */
+                    clockFormat = showSeconds ? _("%a %b %e, %l:%M:%S %p")
+                                              : _("%a %b %e, %l:%M %p");
+                else
+	            /* Translators: This is a time format without date used
+                       for AM/PM. */
+                    clockFormat = showSeconds ? _("%a %l:%M:%S %p")
+                                              : _("%a %l:%M %p");
+                break;
+        }
+
+        let displayDate = new Date();
+        let msecRemaining;
+        if (showSeconds) {
+            msecRemaining = 1000 - displayDate.getMilliseconds();
+            if (msecRemaining < 50) {
+                displayDate.setSeconds(displayDate.getSeconds() + 1);
+                msecRemaining += 1000;
+            }
+        } else {
+            msecRemaining = 60000 - (1000 * displayDate.getSeconds() +
+                                     displayDate.getMilliseconds());
+            if (msecRemaining < 500) {
+                displayDate.setMinutes(displayDate.getMinutes() + 1);
+                msecRemaining += 60000;
+            }
+        }
+
+        this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
+
+        /* Translators: This is the date format to use when the calendar popup is
+         * shown - it is shown just below the time in the shell (e.g. "Tue 9:29 AM").
+         */
+        dateFormat = _("%A %B %e, %Y");
+        this._date.set_text(displayDate.toLocaleFormat(dateFormat));
+
+        Mainloop.timeout_add(msecRemaining, Lang.bind(this, this._updateClockAndDate));
+        return false;
+    },
+
+    _onPreferencesActivate: function() {
+        this.menu.close();
+        Util.spawnDesktop('gnome-datetime-panel');
+    },
+
+    _onOpenCalendarActivate: function() {
+        this.menu.close();
+        // TODO: pass '-c calendar' (to force the calendar at startup)
+        // TODO: pass the selected day
+        Util.spawnDesktop('evolution');
+    },
+};
diff --git a/js/ui/main.js b/js/ui/main.js
index 1d456af..fb293f3 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -265,10 +265,8 @@ function _relayout() {
 
     // To avoid updating the position and size of the workspaces
     // in the overview, we just hide the overview. The positions
-    // will be updated when it is next shown. We do the same for
-    // the calendar popdown.
+    // will be updated when it is next shown.
     overview.hide();
-    panel.hideCalendar();
 }
 
 // metacity-clutter currently uses the same prefs as plain metacity,
diff --git a/js/ui/panel.js b/js/ui/panel.js
index c1baf2c..37b1714 100644
--- a/js/ui/panel.js
+++ b/js/ui/panel.js
@@ -17,6 +17,7 @@ const Overview = imports.ui.overview;
 const PopupMenu = imports.ui.popupMenu;
 const PanelMenu = imports.ui.panelMenu;
 const StatusMenu = imports.ui.statusMenu;
+const DateMenu = imports.ui.dateMenu;
 const Main = imports.ui.main;
 const Tweener = imports.ui.tweener;
 
@@ -496,121 +497,6 @@ AppMenuButton.prototype = {
 
 Signals.addSignalMethods(AppMenuButton.prototype);
 
-function ClockButton() {
-    this._init();
-}
-
-ClockButton.prototype = {
-    _init: function() {
-        this.actor = new St.Bin({ style_class: 'panel-button',
-                                  reactive: true,
-                                  can_focus: true,
-                                  x_fill: true,
-                                  y_fill: false,
-                                  track_hover: true });
-        this.actor._delegate = this;
-        this.actor.connect('button-press-event',
-                           Lang.bind(this, this._toggleCalendar));
-
-        this._clock = new St.Label();
-        this.actor.set_child(this._clock);
-
-        this._calendarPopup = null;
-
-        this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
-        this._clockSettings = new Gio.Settings({ schema: 'org.gnome.shell.clock' });
-
-        this._desktopSettings.connect('changed', Lang.bind(this, this._updateClock));
-        this._clockSettings.connect('changed', Lang.bind(this, this._updateClock));
-
-        // Start the clock
-        this._updateClock();
-    },
-
-    closeCalendar: function() {
-        if (!this._calendarPopup || !this._calendarPopup.isOpen)
-            return;
-
-        this._calendarPopup.hide();
-
-        this.actor.remove_style_pseudo_class('pressed');
-    },
-
-    openCalendar: function() {
-        this._calendarPopup.show();
-
-        this.actor.add_style_pseudo_class('pressed');
-    },
-
-    _toggleCalendar: function() {
-        if (this._calendarPopup == null) {
-            this._calendarPopup = new CalendarPopup();
-            this._calendarPopup.actor.hide();
-        }
-
-        if (!this._calendarPopup.isOpen)
-            this.openCalendar();
-        else
-            this.closeCalendar();
-    },
-
-    _updateClock: function() {
-        let format = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
-        let showDate = this._clockSettings.get_boolean(CLOCK_SHOW_DATE_KEY);
-        let showSeconds = this._clockSettings.get_boolean(CLOCK_SHOW_SECONDS_KEY);
-
-        let clockFormat;
-        switch (format) {
-            case '24h':
-                if (showDate)
-	            /* Translators: This is the time format with date used
-                       in 24-hour mode. */
-                    clockFormat = showSeconds ? _("%a %b %e, %R:%S")
-                                              : _("%a %b %e, %R");
-                else
-	            /* Translators: This is the time format without date used
-                       in 24-hour mode. */
-                    clockFormat = showSeconds ? _("%a %R:%S")
-                                              : _("%a %R");
-                break;
-            case '12h':
-            default:
-                if (showDate)
-	            /* Translators: This is a time format with date used
-                       for AM/PM. */
-                    clockFormat = showSeconds ? _("%a %b %e, %l:%M:%S %p")
-                                              : _("%a %b %e, %l:%M %p");
-                else
-	            /* Translators: This is a time format without date used
-                       for AM/PM. */
-                    clockFormat = showSeconds ? _("%a %l:%M:%S %p")
-                                              : _("%a %l:%M %p");
-                break;
-        }
-
-        let displayDate = new Date();
-        let msecRemaining;
-        if (showSeconds) {
-            msecRemaining = 1000 - displayDate.getMilliseconds();
-            if (msecRemaining < 50) {
-                displayDate.setSeconds(displayDate.getSeconds() + 1);
-                msecRemaining += 1000;
-            }
-        } else {
-            msecRemaining = 60000 - (1000 * displayDate.getSeconds() +
-                                     displayDate.getMilliseconds());
-            if (msecRemaining < 500) {
-                displayDate.setMinutes(displayDate.getMinutes() + 1);
-                msecRemaining += 60000;
-            }
-        }
-
-        this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
-        Mainloop.timeout_add(msecRemaining, Lang.bind(this, this._updateClock));
-        return false;
-    }
-};
-
 function Panel() {
     this._init();
 }
@@ -807,9 +693,9 @@ Panel.prototype = {
         this._menus.addMenu(appMenuButton.menu);
 
         /* center */
-
-        this._clockButton = new ClockButton();
-        this._centerBox.add(this._clockButton.actor, { y_fill: true });
+        this._dateMenu = new DateMenu.DateMenuButton();
+        this._centerBox.add(this._dateMenu.actor, { y_fill: true });
+        this._menus.addMenu(this._dateMenu.menu);
 
         /* right */
 
@@ -898,10 +784,6 @@ Panel.prototype = {
         this._rightBox.add(this._statusmenu.actor);
     },
 
-    hideCalendar: function() {
-        this._clockButton.closeCalendar();
-    },
-
     startupAnimation: function() {
         this.actor.y = -this.actor.height;
         Tweener.addTween(this.actor,
diff --git a/js/ui/workspace.js b/js/ui/workspace.js
index ddd82bc..e44f558 100644
--- a/js/ui/workspace.js
+++ b/js/ui/workspace.js
@@ -1325,10 +1325,6 @@ Workspace.prototype = {
                       Lang.bind(this, function() {
                           this._windowIsZooming = false;
                       }));
-        win.connect('size-changed',
-                    Lang.bind(this, function() {
-                        this.positionWindows(0);
-                    }));
 
         this.actor.add_actor(clone.actor);
 
diff --git a/po/et.po b/po/et.po
index cfbad84..bf15eb8 100644
--- a/po/et.po
+++ b/po/et.po
@@ -13,8 +13,8 @@ msgstr ""
 "Project-Id-Version: gnome-shell MASTER\n"
 "Report-Msgid-Bugs-To: http://bugzilla.gnome.org/enter_bug.cgi?product=gnome-"
 "shell&component=general\n"
-"POT-Creation-Date: 2011-01-21 18:46+0000\n"
-"PO-Revision-Date: 2011-01-24 08:04+0200\n"
+"POT-Creation-Date: 2011-01-15 00:23+0000\n"
+"PO-Revision-Date: 2011-01-15 15:45+0200\n"
 "Last-Translator: Ivar Smolin <okul@linux.ee>\n"
 "Language-Team: Estonian <gnome-et@linux.ee>\n"
 "MIME-Version: 1.0\n"
@@ -70,7 +70,6 @@ msgstr "Kui määratud, kuvatakse kalendris kuupäeva ISO nädalavormingus."
 msgid "List of desktop file IDs for favorite applications"
 msgstr "Lemmikrakenduste töölauafailide ID-de loend"
 
-#, no-c-format
 msgid ""
 "Sets the GStreamer pipeline used to encode recordings. It follows the syntax "
 "used for gst-launch. The pipeline should have an unconnected sink pad where "
@@ -79,9 +78,7 @@ msgid ""
 "pipeline can also take care of its own output - this might be used to send "
 "the output to an icecast server via shout2send or similar. When unset or set "
 "to an empty value, the default pipeline will be used. This is currently "
-"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and "
-"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess "
-"at the optimal thread count on the system."
+"'videorate ! theoraenc ! oggmux' and records to Ogg Theora."
 msgstr ""
 
 msgid "Show date in clock"
@@ -129,9 +126,6 @@ msgstr "Keelatavate laienduste UUID-d"
 msgid "Whether to collect stats about applications usage"
 msgstr ""
 
-msgid "disabled OpenSearch providers"
-msgstr ""
-
 msgid "Clip the crosshairs at the center"
 msgstr "Niitristi keskel on auk"
 
@@ -299,24 +293,24 @@ msgstr ""
 
 #, c-format
 msgid "%s will be logged out automatically in %d seconds."
-msgstr "%s logitakse %d sekundi pärast automaatselt välja."
+msgstr ""
 
 #, c-format
 msgid "You will be logged out automatically in %d seconds."
-msgstr "Sind logitakse %d sekundi pärast automaatselt välja."
+msgstr ""
 
 msgid "Logging out of the system."
 msgstr "Süsteemist väljalogimine"
 
 msgid "Shut Down"
-msgstr "Seiska"
+msgstr "Lülita välja"
 
 msgid "Click Shut Down to quit these applications and shut down the system."
 msgstr ""
 
 #, c-format
 msgid "The system will shut down automatically in %d seconds."
-msgstr "Süsteem seisatakse automaatselt %d sekundi pärast."
+msgstr ""
 
 msgid "Shutting down the system."
 msgstr "Süsteemi seiskamine."
@@ -591,9 +585,6 @@ msgstr "Palun sisesta seadme poolt öeldav PIN-kood."
 msgid "OK"
 msgstr "Olgu"
 
-msgid "Localization Settings"
-msgstr "Lokaliseerimissätted"
-
 msgid "Power Settings"
 msgstr "Toitesätted..."
 
diff --git a/po/nb.po b/po/nb.po
index eb2dba7..2cf41fc 100644
--- a/po/nb.po
+++ b/po/nb.po
@@ -2,14 +2,14 @@
 # Copyright (C) 2009 THE PACKAGE'S COPYRIGHT HOLDER
 # This file is distributed under the same license as the gnome-shell package.
 #
-# Kjartan Maraas <kmaraas@gnome.org>, 2009-2011.
+# Kjartan Maraas <kmaraas@gnome.org>, 2009-2010.
 # Torstein Adolf Winterseth <kvikende@fsfe.org>, 2010.
 msgid ""
 msgstr ""
 "Project-Id-Version: gnome-shell 2.91.x\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2011-01-25 20:46+0100\n"
-"PO-Revision-Date: 2011-01-25 20:47+0100\n"
+"POT-Creation-Date: 2011-01-15 12:41+0100\n"
+"PO-Revision-Date: 2011-01-15 12:44+0100\n"
 "Last-Translator: Torstein Adolf Winterseth <kvikende@fsfe.org>\n"
 "Language-Team: Norwegian Bokmål <i18n-nb@lister.ping.uio.no>\n"
 "Language: \n"
@@ -73,8 +73,7 @@ msgstr ""
 msgid "List of desktop file IDs for favorite applications"
 msgstr ""
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:12
-#, no-c-format
+#: ../data/org.gnome.shell.gschema.xml.in.h:11
 msgid ""
 "Sets the GStreamer pipeline used to encode recordings. It follows the syntax "
 "used for gst-launch. The pipeline should have an unconnected sink pad where "
@@ -83,47 +82,45 @@ msgid ""
 "pipeline can also take care of its own output - this might be used to send "
 "the output to an icecast server via shout2send or similar. When unset or set "
 "to an empty value, the default pipeline will be used. This is currently "
-"'videorate ! vp8enc quality=10 speed=2 threads=%T ! queue ! webmmux' and "
-"records to WEBM using the VP8 codec. %T is used as a placeholder for a guess "
-"at the optimal thread count on the system."
+"'videorate ! theoraenc ! oggmux' and records to Ogg Theora."
 msgstr ""
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:13
+#: ../data/org.gnome.shell.gschema.xml.in.h:12
 msgid "Show date in clock"
 msgstr "Vis dato i klokken"
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:14
+#: ../data/org.gnome.shell.gschema.xml.in.h:13
 msgid "Show the week date in the calendar"
 msgstr "Vis dato for uken i kalender"
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:15
+#: ../data/org.gnome.shell.gschema.xml.in.h:14
 msgid "Show time with seconds"
 msgstr "Vis tid med sekunder"
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:16
+#: ../data/org.gnome.shell.gschema.xml.in.h:15
 msgid ""
 "The applications corresponding to these identifiers will be displayed in the "
 "favorites area."
 msgstr ""
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:17
+#: ../data/org.gnome.shell.gschema.xml.in.h:16
 msgid ""
 "The filename for recorded screencasts will be a unique filename based on the "
 "current date, and use this extension. It should be changed when recording to "
 "a different container format."
 msgstr ""
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:18
+#: ../data/org.gnome.shell.gschema.xml.in.h:17
 msgid ""
 "The framerate of the resulting screencast recordered by GNOME Shell's "
 "screencast recorder in frames-per-second."
 msgstr ""
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:19
+#: ../data/org.gnome.shell.gschema.xml.in.h:18
 msgid "The gstreamer pipeline used to encode the screencast"
 msgstr ""
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:20
+#: ../data/org.gnome.shell.gschema.xml.in.h:19
 msgid ""
 "The shell normally monitors active applications in order to present the most "
 "used ones (e.g. in launchers). While this data will be kept private, you may "
@@ -131,18 +128,14 @@ msgid ""
 "remove already saved data."
 msgstr ""
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:21
+#: ../data/org.gnome.shell.gschema.xml.in.h:20
 msgid "Uuids of extensions to disable"
 msgstr "Uuider på utvidelser som skal slås av"
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:22
+#: ../data/org.gnome.shell.gschema.xml.in.h:21
 msgid "Whether to collect stats about applications usage"
 msgstr "Om det skal samles statistikk om bruk av programmer"
 
-#: ../data/org.gnome.shell.gschema.xml.in.h:23
-msgid "disabled OpenSearch providers"
-msgstr ""
-
 #: ../data/org.gnome.accessibility.magnifier.gschema.xml.in.h:1
 msgid "Clip the crosshairs at the center"
 msgstr ""
@@ -278,33 +271,33 @@ msgstr "Klarte ikke å lese kommando:"
 msgid "No such application"
 msgstr "Programmet finnes ikke"
 
-#: ../js/misc/util.js:143 ../js/ui/runDialog.js:351
+#: ../js/misc/util.js:143 ../js/ui/runDialog.js:364
 #, c-format
 msgid "Execution of '%s' failed:"
 msgstr "Kjøring av «%s» feilet:"
 
 #. Translators: Filter to display all applications
-#: ../js/ui/appDisplay.js:164
+#: ../js/ui/appDisplay.js:155
 msgid "All"
 msgstr "Alle"
 
-#: ../js/ui/appDisplay.js:245
+#: ../js/ui/appDisplay.js:236
 msgid "APPLICATIONS"
 msgstr "PROGRAMMER"
 
-#: ../js/ui/appDisplay.js:275
+#: ../js/ui/appDisplay.js:266
 msgid "PREFERENCES"
 msgstr "BRUKERVALG"
 
-#: ../js/ui/appDisplay.js:572
+#: ../js/ui/appDisplay.js:563
 msgid "New Window"
 msgstr "Nytt vindu"
 
-#: ../js/ui/appDisplay.js:576
+#: ../js/ui/appDisplay.js:567
 msgid "Remove from Favorites"
 msgstr "Fjern fra favoritter"
 
-#: ../js/ui/appDisplay.js:577
+#: ../js/ui/appDisplay.js:568
 msgid "Add to Favorites"
 msgstr "Legg til i favoritter"
 
@@ -337,7 +330,7 @@ msgstr "Logg ut …"
 
 #: ../js/ui/endSessionDialog.js:65
 msgid "Click Log Out to quit these applications and log out of the system."
-msgstr "Klikk Logg ut for å avslutte disse programmene og logge ut av systemet."
+msgstr ""
 
 #: ../js/ui/endSessionDialog.js:66
 #, c-format
@@ -359,7 +352,7 @@ msgstr "Avslutt"
 
 #: ../js/ui/endSessionDialog.js:75
 msgid "Click Shut Down to quit these applications and shut down the system."
-msgstr "Klikk Avslutt for å avslutte disse programmene og stenge ned systemet."
+msgstr ""
 
 #: ../js/ui/endSessionDialog.js:76
 #, c-format
@@ -376,7 +369,7 @@ msgstr "Start på nytt"
 
 #: ../js/ui/endSessionDialog.js:85
 msgid "Click Restart to quit these applications and restart the system."
-msgstr "Klikk Start på nytt for å avslutte disse programmene og starte systemet på nytt."
+msgstr ""
 
 #: ../js/ui/endSessionDialog.js:86
 #, c-format
@@ -391,7 +384,7 @@ msgstr "Starter systemet på nytt."
 msgid "Confirm"
 msgstr "Bekreft"
 
-#: ../js/ui/endSessionDialog.js:400 ../js/ui/status/bluetooth.js:470
+#: ../js/ui/endSessionDialog.js:400 ../js/ui/status/bluetooth.js:469
 msgid "Cancel"
 msgstr "Avbryt"
 
@@ -425,88 +418,88 @@ msgstr "Vis kildekode"
 msgid "Web Page"
 msgstr "Nettside"
 
-#: ../js/ui/messageTray.js:1765
+#: ../js/ui/messageTray.js:1748
 msgid "System Information"
 msgstr "Systeminformasjon"
 
-#: ../js/ui/overview.js:88
+#: ../js/ui/overview.js:75
 msgid "Undo"
 msgstr "Angre"
 
-#: ../js/ui/overview.js:159
+#: ../js/ui/overview.js:140
 msgid "Windows"
 msgstr "Vinduer"
 
-#: ../js/ui/overview.js:162
+#: ../js/ui/overview.js:143
 msgid "Applications"
 msgstr "Programmer"
 
 #. TODO - _quit() doesn't really work on apps in state STARTING yet
-#: ../js/ui/panel.js:483
+#: ../js/ui/panel.js:479
 #, c-format
 msgid "Quit %s"
 msgstr "Avslutt %s"
 
 #. Translators: This is the time format with date used
 #. in 24-hour mode.
-#: ../js/ui/panel.js:568
+#: ../js/ui/panel.js:564
 msgid "%a %b %e, %R:%S"
 msgstr "%a %e %b, %R.%S"
 
-#: ../js/ui/panel.js:569
+#: ../js/ui/panel.js:565
 msgid "%a %b %e, %R"
 msgstr "%a %e %b, %R"
 
 #. Translators: This is the time format without date used
 #. in 24-hour mode.
-#: ../js/ui/panel.js:573
+#: ../js/ui/panel.js:569
 msgid "%a %R:%S"
 msgstr "%a %R.%S"
 
-#: ../js/ui/panel.js:574
+#: ../js/ui/panel.js:570
 msgid "%a %R"
 msgstr "%a %R"
 
 #. Translators: This is a time format with date used
 #. for AM/PM.
-#: ../js/ui/panel.js:581
+#: ../js/ui/panel.js:577
 msgid "%a %b %e, %l:%M:%S %p"
 msgstr "%a %e %b, %l.%M.%S %p"
 
-#: ../js/ui/panel.js:582
+#: ../js/ui/panel.js:578
 msgid "%a %b %e, %l:%M %p"
 msgstr "%a %e %b, %l.%M %p"
 
 #. Translators: This is a time format without date used
 #. for AM/PM.
-#: ../js/ui/panel.js:586
+#: ../js/ui/panel.js:582
 msgid "%a %l:%M:%S %p"
 msgstr "%a %l.%M.%S %p"
 
-#: ../js/ui/panel.js:587
+#: ../js/ui/panel.js:583
 msgid "%a %l:%M %p"
 msgstr "%a %l.%M %p"
 
 #. Button on the left side of the panel.
 #. Translators: If there is no suitable word for "Activities" in your language, you can use the word for "Overview".
-#: ../js/ui/panel.js:732
+#: ../js/ui/panel.js:728
 msgid "Activities"
 msgstr "Aktiviteter"
 
-#: ../js/ui/placeDisplay.js:106
+#: ../js/ui/placeDisplay.js:112
 #, c-format
 msgid "Failed to unmount '%s'"
 msgstr "Klarte ikke å avmontere «%s»"
 
-#: ../js/ui/placeDisplay.js:109
+#: ../js/ui/placeDisplay.js:115
 msgid "Retry"
 msgstr "Prøv igjen"
 
-#: ../js/ui/placeDisplay.js:150
+#: ../js/ui/placeDisplay.js:160
 msgid "Connect to..."
 msgstr "Koble til …"
 
-#: ../js/ui/placeDisplay.js:386
+#: ../js/ui/placeDisplay.js:559
 msgid "PLACES & DEVICES"
 msgstr "STEDER & ENHETER"
 
@@ -519,7 +512,7 @@ msgstr "STEDER & ENHETER"
 msgid "toggle-switch-us"
 msgstr "toggle-switch-intl"
 
-#: ../js/ui/runDialog.js:209
+#: ../js/ui/runDialog.js:222
 msgid "Please enter a command:"
 msgstr "Oppgi en kommando:"
 
@@ -603,7 +596,7 @@ msgstr "Høy kontrast"
 msgid "Large Text"
 msgstr "Stor tekst"
 
-#: ../js/ui/status/bluetooth.js:42 ../js/ui/status/bluetooth.js:241
+#: ../js/ui/status/bluetooth.js:42 ../js/ui/status/bluetooth.js:240
 msgid "Bluetooth"
 msgstr "Bluetooth"
 
@@ -619,110 +612,106 @@ msgstr "Send filer til enhet..."
 msgid "Setup a New Device..."
 msgstr "Sett opp en ny enhet..."
 
-#: ../js/ui/status/bluetooth.js:95
+#: ../js/ui/status/bluetooth.js:94
 msgid "Bluetooth Settings"
 msgstr "Innstillinger for Bluetooth"
 
-#: ../js/ui/status/bluetooth.js:192
+#: ../js/ui/status/bluetooth.js:191
 msgid "Connection"
 msgstr "Tilkobling"
 
-#: ../js/ui/status/bluetooth.js:228
+#: ../js/ui/status/bluetooth.js:227
 msgid "Send Files..."
 msgstr "Send filer..."
 
-#: ../js/ui/status/bluetooth.js:233
+#: ../js/ui/status/bluetooth.js:232
 msgid "Browse Files..."
 msgstr "Bla gjennom filer..."
 
-#: ../js/ui/status/bluetooth.js:242
+#: ../js/ui/status/bluetooth.js:241
 msgid "Error browsing device"
 msgstr "Feil under lesing av enhet"
 
-#: ../js/ui/status/bluetooth.js:243
+#: ../js/ui/status/bluetooth.js:242
 #, c-format
 msgid "The requested device cannot be browsed, error is '%s'"
 msgstr "Kan ikke bla gjennom forespurt enhet. Feilen er «%s»"
 
-#: ../js/ui/status/bluetooth.js:251
+#: ../js/ui/status/bluetooth.js:250 ../js/ui/status/keyboard.js:78
 msgid "Keyboard Settings"
 msgstr "Innstillinger for tastatur"
 
-#: ../js/ui/status/bluetooth.js:256
+#: ../js/ui/status/bluetooth.js:255
 msgid "Mouse Settings"
 msgstr "Innstillinger for mus"
 
-#: ../js/ui/status/bluetooth.js:263 ../js/ui/status/volume.js:63
+#: ../js/ui/status/bluetooth.js:262 ../js/ui/status/volume.js:63
 msgid "Sound Settings"
 msgstr "Innstillinger for lyd"
 
-#: ../js/ui/status/bluetooth.js:337 ../js/ui/status/bluetooth.js:371
-#: ../js/ui/status/bluetooth.js:411 ../js/ui/status/bluetooth.js:444
+#: ../js/ui/status/bluetooth.js:336 ../js/ui/status/bluetooth.js:370
+#: ../js/ui/status/bluetooth.js:410 ../js/ui/status/bluetooth.js:443
 msgid "Bluetooth Agent"
 msgstr "Bluetooth-agent"
 
-#: ../js/ui/status/bluetooth.js:372
+#: ../js/ui/status/bluetooth.js:371
 #, c-format
 msgid "Authorization request from %s"
 msgstr "Forespørsel om autorisering fra %s"
 
-#: ../js/ui/status/bluetooth.js:378
+#: ../js/ui/status/bluetooth.js:377
 #, c-format
 msgid "Device %s wants access to the service '%s'"
 msgstr "Enhet %s vil ha tilgang til tjenesten «%s»"
 
-#: ../js/ui/status/bluetooth.js:380
+#: ../js/ui/status/bluetooth.js:379
 msgid "Always grant access"
 msgstr "Alltid gi tilgang"
 
-#: ../js/ui/status/bluetooth.js:381
+#: ../js/ui/status/bluetooth.js:380
 msgid "Grant this time only"
 msgstr "Gi tilgang kun denne ene gangen"
 
-#: ../js/ui/status/bluetooth.js:382
+#: ../js/ui/status/bluetooth.js:381
 msgid "Reject"
 msgstr "Avvis"
 
-#: ../js/ui/status/bluetooth.js:412
+#: ../js/ui/status/bluetooth.js:411
 #, c-format
 msgid "Pairing confirmation for %s"
 msgstr "Bekreftelse for tilkobling for %s"
 
-#: ../js/ui/status/bluetooth.js:418 ../js/ui/status/bluetooth.js:452
+#: ../js/ui/status/bluetooth.js:417 ../js/ui/status/bluetooth.js:451
 #, c-format
 msgid "Device %s wants to pair with this computer"
 msgstr "Enhet %s vil koble seg sammen med denne datamaskinen"
 
-#: ../js/ui/status/bluetooth.js:419
+#: ../js/ui/status/bluetooth.js:418
 #, c-format
 msgid "Please confirm whether the PIN '%s' matches the one on the device."
 msgstr "Vennligst bekreft om PIN «%s» er lik den som brukes på enheten."
 
-#: ../js/ui/status/bluetooth.js:421
+#: ../js/ui/status/bluetooth.js:420
 msgid "Matches"
 msgstr "Stemmer overens"
 
-#: ../js/ui/status/bluetooth.js:422
+#: ../js/ui/status/bluetooth.js:421
 msgid "Does not match"
 msgstr "Stemmer ikke overens"
 
-#: ../js/ui/status/bluetooth.js:445
+#: ../js/ui/status/bluetooth.js:444
 #, c-format
 msgid "Pairing request for %s"
 msgstr "Forespørsel om tilkobling for %s"
 
-#: ../js/ui/status/bluetooth.js:453
+#: ../js/ui/status/bluetooth.js:452
 msgid "Please enter the PIN mentioned on the device."
 msgstr "Vennligst oppgi PIN som oppgitt på enheten."
 
-#: ../js/ui/status/bluetooth.js:469
+#: ../js/ui/status/bluetooth.js:468
 msgid "OK"
 msgstr "OK"
 
-#: ../js/ui/status/keyboard.js:78
-msgid "Localization Settings"
-msgstr "Innstillinger for lokalisering"
-
 #: ../js/ui/status/power.js:85
 msgid "Power Settings"
 msgstr "Innstillinger for strøm"
@@ -857,14 +846,14 @@ msgstr "%s er ferdig startet"
 msgid "'%s' is ready"
 msgstr "«%s» er klar"
 
-#: ../js/ui/workspacesView.js:243
+#: ../js/ui/workspacesView.js:244
 msgid ""
 "Can't add a new workspace because maximum workspaces limit has been reached."
 msgstr ""
 "Kan ikke legge til nytt arbeidsområde fordi grensen for maksimalt antall "
 "arbeidsområder er nådd."
 
-#: ../js/ui/workspacesView.js:259
+#: ../js/ui/workspacesView.js:260
 msgid "Can't remove the first workspace."
 msgstr "Kan ikke fjerne første arbeidsområde"
 
@@ -890,32 +879,32 @@ msgstr[1] "%u innganger"
 msgid "System Sounds"
 msgstr "Systemlyder"
 
-#: ../src/shell-global.c:1365
+#: ../src/shell-global.c:1233
 msgid "Less than a minute ago"
 msgstr "Mindre enn ett minutt siden"
 
-#: ../src/shell-global.c:1369
+#: ../src/shell-global.c:1237
 #, c-format
 msgid "%d minute ago"
 msgid_plural "%d minutes ago"
 msgstr[0] "%d minutt siden"
 msgstr[1] "%d minutter siden"
 
-#: ../src/shell-global.c:1374
+#: ../src/shell-global.c:1242
 #, c-format
 msgid "%d hour ago"
 msgid_plural "%d hours ago"
 msgstr[0] "%d time siden"
 msgstr[1] "%d timer siden"
 
-#: ../src/shell-global.c:1379
+#: ../src/shell-global.c:1247
 #, c-format
 msgid "%d day ago"
 msgid_plural "%d days ago"
 msgstr[0] "%d dag siden"
 msgstr[1] "%d dager siden"
 
-#: ../src/shell-global.c:1384
+#: ../src/shell-global.c:1252
 #, c-format
 msgid "%d week ago"
 msgid_plural "%d weeks ago"
diff --git a/src/Makefile-calendar-client.am b/src/Makefile-calendar-client.am
new file mode 100644
index 0000000..a79f2cd
--- /dev/null
+++ b/src/Makefile-calendar-client.am
@@ -0,0 +1,20 @@
+
+noinst_LTLIBRARIES += libcalendar-client.la
+
+libcalendar_client_la_SOURCES = 							\
+	calendar-client/calendar-client.h	calendar-client/calendar-client.c	\
+	calendar-client/calendar-debug.h						\
+	calendar-client/calendar-sources.c	calendar-client/calendar-sources.h	\
+	$(NULL)
+
+libcalendar_client_la_CFLAGS = 			\
+	-I$(top_srcdir)/src			\
+	-DPREFIX=\""$(prefix)"\"		\
+	-DLIBDIR=\""$(libdir)"\"		\
+	-DDATADIR=\""$(datadir)"\"		\
+	-DG_DISABLE_DEPRECATED			\
+	-DG_LOG_DOMAIN=\"CalendarClient\"	\
+	$(LIBECAL_CFLAGS)			\
+	$(NULL)
+
+libcalendar_client_la_LIBADD = $(LIBECAL_LIBS)
diff --git a/src/Makefile.am b/src/Makefile.am
index 5bfa8c7..a7a38d8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -27,6 +27,7 @@ include Makefile-gdmuser.am
 include Makefile-st.am
 include Makefile-tray.am
 include Makefile-gvc.am
+include Makefile-calendar-client.am
 
 gnome_shell_cflags =				\
 	$(MUTTER_PLUGIN_CFLAGS)			\
@@ -89,6 +90,8 @@ libgnome_shell_la_SOURCES =		\
 	shell-doc-system.c		\
 	shell-drawing.c			\
 	shell-embedded-window.c		\
+	shell-evolution-event-source.h	\
+	shell-evolution-event-source.c	\
 	shell-generic-container.c	\
 	shell-gtk-embed.c		\
 	shell-global.c			\
@@ -212,7 +215,9 @@ libgnome_shell_la_LIBADD =	\
 	libst-1.0.la       	\
 	libgdmuser-1.0.la	\
 	libtray.la		\
-	libgvc.la
+	libgvc.la		\
+	libcalendar-client.la	\
+	$(NULL)
 
 libgnome_shell_la_CPPFLAGS = $(gnome_shell_cflags)
 
diff --git a/src/calendar-client/README b/src/calendar-client/README
new file mode 100644
index 0000000..ad9b5e3
--- /dev/null
+++ b/src/calendar-client/README
@@ -0,0 +1 @@
+Please keep in sync with gnome-panel.
diff --git a/src/calendar-client/calendar-client.c b/src/calendar-client/calendar-client.c
new file mode 100644
index 0000000..bbdb473
--- /dev/null
+++ b/src/calendar-client/calendar-client.c
@@ -0,0 +1,2169 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark@skynet.ie>
+ *     William Jon McCann  <mccann@jhu.edu>
+ *     Martin Grimme  <martin@pycage.de>
+ *     Christian Kellner  <gicmo@xatom.net>
+ */
+
+#include <config.h>
+
+#include "calendar-client.h"
+
+#include <libintl.h>
+#include <string.h>
+#define HANDLE_LIBICAL_MEMORY
+#include <libecal/e-cal.h>
+#include <libecal/e-cal-time-util.h>
+#include <libecal/e-cal-recur.h>
+
+#include "calendar-sources.h"
+
+#undef CALENDAR_ENABLE_DEBUG
+#include "calendar-debug.h"
+
+#define CALENDAR_CONFIG_PREFIX   "/apps/evolution/calendar"
+#define CALENDAR_CONFIG_TIMEZONE CALENDAR_CONFIG_PREFIX "/display/timezone"
+
+#ifndef _
+#define _(x) gettext(x)
+#endif
+
+#ifndef N_
+#define N_(x) x
+#endif
+
+#define CALENDAR_CLIENT_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_CLIENT, CalendarClientPrivate))
+
+typedef struct _CalendarClientQuery  CalendarClientQuery;
+typedef struct _CalendarClientSource CalendarClientSource;
+
+struct _CalendarClientQuery
+{
+  ECalView   *view;
+  GHashTable *events;
+};
+
+struct _CalendarClientSource
+{
+  CalendarClient      *client;
+  ECal                *source;
+
+  CalendarClientQuery  completed_query;
+  CalendarClientQuery  in_progress_query;
+
+  guint                changed_signal_id;
+
+  guint                query_completed : 1;
+  guint                query_in_progress : 1;
+};
+
+struct _CalendarClientPrivate
+{
+  CalendarSources     *calendar_sources;
+
+  GSList              *appointment_sources;
+  GSList              *task_sources;
+
+  icaltimezone        *zone;
+
+  guint                zone_listener;
+  GConfClient         *gconf_client;
+
+  guint                day;
+  guint                month;
+  guint                year;
+};
+
+static void calendar_client_class_init   (CalendarClientClass *klass);
+static void calendar_client_init         (CalendarClient      *client);
+static void calendar_client_finalize     (GObject             *object);
+static void calendar_client_set_property (GObject             *object,
+					  guint                prop_id,
+					  const GValue        *value,
+					  GParamSpec          *pspec);
+static void calendar_client_get_property (GObject             *object,
+					  guint                prop_id,
+					  GValue              *value,
+					  GParamSpec          *pspec);
+
+static GSList *calendar_client_update_sources_list         (CalendarClient       *client,
+							    GSList               *sources,
+							    GSList               *esources,
+							    guint                 changed_signal_id);
+static void    calendar_client_appointment_sources_changed (CalendarClient       *client);
+static void    calendar_client_task_sources_changed        (CalendarClient       *client);
+
+static void calendar_client_stop_query  (CalendarClient       *client,
+					 CalendarClientSource *source,
+					 CalendarClientQuery  *query);
+static void calendar_client_start_query (CalendarClient       *client,
+					 CalendarClientSource *source,
+					 const char           *query);
+
+static void calendar_client_source_finalize (CalendarClientSource *source);
+static void calendar_client_query_finalize  (CalendarClientQuery  *query);
+
+static void
+calendar_client_update_appointments (CalendarClient *client);
+static void
+calendar_client_update_tasks (CalendarClient *client);
+
+enum
+{
+  PROP_O,
+  PROP_DAY,
+  PROP_MONTH,
+  PROP_YEAR
+};
+
+enum
+{
+  APPOINTMENTS_CHANGED,
+  TASKS_CHANGED,
+  LAST_SIGNAL
+};
+
+static GObjectClass *parent_class = NULL;
+static guint         signals [LAST_SIGNAL] = { 0, };
+
+GType
+calendar_client_get_type (void)
+{
+  static GType client_type = 0;
+  
+  if (!client_type)
+    {
+      static const GTypeInfo client_info =
+      {
+	sizeof (CalendarClientClass),
+	NULL,		/* base_init */
+	NULL,		/* base_finalize */
+	(GClassInitFunc) calendar_client_class_init,
+	NULL,           /* class_finalize */
+	NULL,		/* class_data */
+	sizeof (CalendarClient),
+	0,		/* n_preallocs */
+	(GInstanceInitFunc) calendar_client_init,
+      };
+      
+      client_type = g_type_register_static (G_TYPE_OBJECT,
+					    "CalendarClient",
+					    &client_info, 0);
+    }
+  
+  return client_type;
+}
+
+static void
+calendar_client_class_init (CalendarClientClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize     = calendar_client_finalize;
+  gobject_class->set_property = calendar_client_set_property;
+  gobject_class->get_property = calendar_client_get_property;
+
+  g_type_class_add_private (klass, sizeof (CalendarClientPrivate));
+
+  g_object_class_install_property (gobject_class,
+				   PROP_DAY,
+				   g_param_spec_uint ("day",
+						      "Day",
+						      "The currently monitored day between 1 and 31 (0 denotes unset)",
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+				   PROP_MONTH,
+				   g_param_spec_uint ("month",
+						      "Month",
+						      "The currently monitored month between 0 and 11",
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  g_object_class_install_property (gobject_class,
+				   PROP_YEAR,
+				   g_param_spec_uint ("year",
+						      "Year",
+						      "The currently monitored year",
+						      0, G_MAXUINT, 0,
+						      G_PARAM_READWRITE));
+
+  signals [APPOINTMENTS_CHANGED] =
+    g_signal_new ("appointments-changed",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (CalendarClientClass, tasks_changed),
+		  NULL,
+		  NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE,
+		  0);
+
+  signals [TASKS_CHANGED] =
+    g_signal_new ("tasks-changed",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (CalendarClientClass, tasks_changed),
+		  NULL,
+		  NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE,
+		  0);
+}
+
+/* Timezone code adapted from evolution/calendar/gui/calendar-config.c */
+/* The current timezone, e.g. "Europe/London". It may be NULL, in which case
+   you should assume UTC. */
+static gchar *
+calendar_client_config_get_timezone (GConfClient *gconf_client)
+{
+  char *location;
+
+  location = gconf_client_get_string (gconf_client,
+                                      CALENDAR_CONFIG_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 void
+calendar_client_set_timezone (CalendarClient *client) 
+{
+  GSList *l;
+  GSList *esources;
+
+  client->priv->zone = calendar_client_config_get_icaltimezone (client->priv->gconf_client);
+
+  esources = calendar_sources_get_appointment_sources (client->priv->calendar_sources);
+  for (l = esources; l; l = l->next) {
+    ECal *source = l->data;
+			
+    e_cal_set_default_timezone (source, client->priv->zone, NULL);
+  }
+}
+
+static void
+calendar_client_timezone_changed_cb (GConfClient    *gconf_client,
+                                     guint           id,
+                                     GConfEntry     *entry,
+                                     CalendarClient *client)
+{
+  calendar_client_set_timezone (client);
+}
+
+static void
+cal_opened_cb (ECal                 *ecal,
+               ECalendarStatus       status,
+               CalendarClientSource *cl_source)
+{
+  ECalSourceType  s_type;
+  CalendarClient *client = cl_source->client;
+
+  s_type = e_cal_get_source_type (ecal);
+
+  if (status == E_CALENDAR_STATUS_BUSY &&
+      e_cal_get_load_state (ecal) == E_CAL_LOAD_NOT_LOADED)
+    {
+      e_cal_open_async (ecal, FALSE);
+      return;
+    }
+  
+  g_signal_handlers_disconnect_by_func (ecal, cal_opened_cb, cl_source);
+
+  if (status != E_CALENDAR_STATUS_OK)
+    {
+      if (s_type == E_CAL_SOURCE_TYPE_EVENT)
+        client->priv->appointment_sources = g_slist_remove (client->priv->appointment_sources,
+                                                            cl_source);
+      else
+        client->priv->task_sources = g_slist_remove (client->priv->task_sources,
+                                                     cl_source);
+
+      calendar_client_source_finalize (cl_source);
+      g_free (cl_source);
+
+      return;
+    }
+
+  if (s_type == E_CAL_SOURCE_TYPE_EVENT)
+    calendar_client_update_appointments (client);
+  else
+    calendar_client_update_tasks (client);
+}
+
+static void
+load_calendars (CalendarClient    *client,
+                CalendarEventType  type) 
+{
+  GSList *l, *clients;
+
+  switch (type)
+    {
+      case CALENDAR_EVENT_APPOINTMENT:
+        clients = client->priv->appointment_sources;
+        break;
+      case CALENDAR_EVENT_TASK:
+        clients = client->priv->task_sources;
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  for (l = clients; l != NULL; l = l->next)
+    {
+      ECal *ecal;	
+      CalendarClientSource *cl_source = l->data;
+
+      ecal = cl_source->source;
+
+      if (e_cal_get_load_state (ecal) == E_CAL_LOAD_LOADED)
+        continue;
+
+      g_signal_connect (G_OBJECT (ecal), "cal_opened",
+                        G_CALLBACK (cal_opened_cb), cl_source);
+      e_cal_open_async (ecal, TRUE);
+    }
+}
+
+static void
+calendar_client_init (CalendarClient *client)
+{
+  GSList *esources;
+
+  client->priv = CALENDAR_CLIENT_GET_PRIVATE (client);
+
+  client->priv->calendar_sources = calendar_sources_get ();
+  client->priv->gconf_client = gconf_client_get_default ();
+
+  esources = calendar_sources_get_appointment_sources (client->priv->calendar_sources);
+  client->priv->appointment_sources =
+    calendar_client_update_sources_list (client, NULL, esources, signals [APPOINTMENTS_CHANGED]);
+
+  esources = calendar_sources_get_task_sources (client->priv->calendar_sources);
+  client->priv->task_sources =
+    calendar_client_update_sources_list (client, NULL, esources, signals [TASKS_CHANGED]);
+ 
+  /* set the timezone before loading the clients */ 
+  calendar_client_set_timezone (client);
+  load_calendars (client, CALENDAR_EVENT_APPOINTMENT);
+  load_calendars (client, CALENDAR_EVENT_TASK);
+
+  g_signal_connect_swapped (client->priv->calendar_sources,
+			    "appointment-sources-changed",
+			    G_CALLBACK (calendar_client_appointment_sources_changed),
+			    client);
+  g_signal_connect_swapped (client->priv->calendar_sources,
+			    "task-sources-changed",
+			    G_CALLBACK (calendar_client_task_sources_changed),
+			    client);
+
+  gconf_client_add_dir (client->priv->gconf_client,
+			CALENDAR_CONFIG_PREFIX,
+			GCONF_CLIENT_PRELOAD_NONE,
+			NULL);
+
+  client->priv->zone_listener = gconf_client_notify_add (client->priv->gconf_client,
+                                                         CALENDAR_CONFIG_TIMEZONE,
+                                                         (GConfClientNotifyFunc) calendar_client_timezone_changed_cb,
+                                                         client, NULL, NULL);
+
+  client->priv->day   = -1;
+  client->priv->month = -1;
+  client->priv->year  = -1;
+}
+
+static void
+calendar_client_finalize (GObject *object)
+{
+  CalendarClient *client = CALENDAR_CLIENT (object);
+  GSList         *l;
+
+  if (client->priv->zone_listener)
+    {
+      gconf_client_notify_remove (client->priv->gconf_client,
+                                  client->priv->zone_listener);
+      client->priv->zone_listener = 0;
+    }
+
+  gconf_client_remove_dir (client->priv->gconf_client,
+                           CALENDAR_CONFIG_PREFIX,
+                           NULL);
+
+  if (client->priv->gconf_client)
+    g_object_unref (client->priv->gconf_client);
+  client->priv->gconf_client = NULL;
+
+  for (l = client->priv->appointment_sources; l; l = l->next)
+    {
+      calendar_client_source_finalize (l->data);
+      g_free (l->data);
+    }
+  g_slist_free (client->priv->appointment_sources);
+  client->priv->appointment_sources = NULL;
+
+  for (l = client->priv->task_sources; l; l = l->next)
+    {
+      calendar_client_source_finalize (l->data);
+      g_free (l->data);
+    }
+  g_slist_free (client->priv->task_sources);
+  client->priv->task_sources = NULL;
+
+  if (client->priv->calendar_sources)
+    g_object_unref (client->priv->calendar_sources);
+  client->priv->calendar_sources = NULL;
+
+  if (G_OBJECT_CLASS (parent_class)->finalize)
+    G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+calendar_client_set_property (GObject      *object,
+			      guint         prop_id,
+			      const GValue *value,
+			      GParamSpec   *pspec)
+{
+  CalendarClient *client = CALENDAR_CLIENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_DAY:
+      calendar_client_select_day (client, g_value_get_uint (value));
+      break;
+    case PROP_MONTH:
+      calendar_client_select_month (client,
+				    g_value_get_uint (value),
+				    client->priv->year);
+      break;
+    case PROP_YEAR:
+      calendar_client_select_month (client,
+				    client->priv->month,
+				    g_value_get_uint (value));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+static void
+calendar_client_get_property (GObject    *object,
+			      guint       prop_id,
+			      GValue     *value,
+			      GParamSpec *pspec)
+{
+  CalendarClient *client = CALENDAR_CLIENT (object);
+
+  switch (prop_id)
+    {
+    case PROP_DAY:
+      g_value_set_uint (value, client->priv->day);
+      break;
+    case PROP_MONTH:
+      g_value_set_uint (value, client->priv->month);
+      break;
+    case PROP_YEAR:
+      g_value_set_uint (value, client->priv->year);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+    }
+}
+
+CalendarClient *
+calendar_client_new (void)
+{
+  return g_object_new (CALENDAR_TYPE_CLIENT, NULL);
+}
+
+/* @day and @month can happily be out of range as
+ * mktime() will normalize them correctly. From mktime(3):
+ *
+ * "If structure members are outside their legal interval,
+ *  they will be normalized (so that, e.g., 40 October is
+ *  changed into 9 November)."
+ *
+ * "What?", you say, "Something useful in libc?"
+ */
+static inline time_t
+make_time_for_day_begin (int day,
+			 int month,
+			 int year)
+{
+  struct tm localtime_tm = { 0, };
+
+  localtime_tm.tm_mday  = day;
+  localtime_tm.tm_mon   = month;
+  localtime_tm.tm_year  = year - 1900;
+  localtime_tm.tm_isdst = -1;
+
+  return mktime (&localtime_tm);
+}
+
+static inline char *
+make_isodate_for_day_begin (int day,
+			    int month,
+			    int year)
+{
+  time_t utctime;
+
+  utctime = make_time_for_day_begin (day, month, year);
+
+  return utctime != -1 ? isodate_from_time_t (utctime) : NULL;
+}
+
+static time_t
+get_time_from_property (icalcomponent         *ical,
+			icalproperty_kind      prop_kind,
+			struct icaltimetype (* get_prop_func) (const icalproperty *prop),
+                        icaltimezone          *default_zone)
+{
+  icalproperty        *prop;
+  struct icaltimetype  ical_time;
+  icalparameter       *param;
+  icaltimezone        *timezone = NULL;
+  
+  prop = icalcomponent_get_first_property (ical, prop_kind);
+  if (!prop)
+    return 0;
+
+  ical_time = get_prop_func (prop);
+
+  param = icalproperty_get_first_parameter (prop, ICAL_TZID_PARAMETER);
+  if (param)
+    timezone = icaltimezone_get_builtin_timezone_from_tzid (icalparameter_get_tzid (param));
+  else if (icaltime_is_utc (ical_time))
+    timezone = icaltimezone_get_utc_timezone ();
+  else 
+    timezone = default_zone;
+
+  return icaltime_as_timet_with_zone (ical_time, timezone);
+}
+
+static char *
+get_ical_uid (icalcomponent *ical)
+{
+  return g_strdup (icalcomponent_get_uid (ical));
+}
+
+static char *
+get_ical_rid (icalcomponent *ical)
+{
+  icalproperty        *prop;
+  struct icaltimetype  ical_time;
+  
+  prop = icalcomponent_get_first_property (ical, ICAL_RECURRENCEID_PROPERTY);
+  if (!prop)
+    return NULL;
+
+  ical_time = icalproperty_get_recurrenceid (prop);
+
+  return icaltime_is_valid_time (ical_time) && !icaltime_is_null_time (ical_time) ? 
+    g_strdup (icaltime_as_ical_string (ical_time)) : NULL;
+}
+
+static char *
+get_ical_summary (icalcomponent *ical)
+{
+  icalproperty *prop;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_SUMMARY_PROPERTY);
+  if (!prop)
+    return NULL;
+
+  return g_strdup (icalproperty_get_summary (prop));
+}
+
+static char *
+get_ical_description (icalcomponent *ical)
+{
+  icalproperty *prop;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_DESCRIPTION_PROPERTY);
+  if (!prop)
+    return NULL;
+
+  return g_strdup (icalproperty_get_description (prop));
+}
+
+static inline time_t
+get_ical_start_time (icalcomponent *ical,
+                     icaltimezone  *default_zone)
+{
+  return get_time_from_property (ical,
+				 ICAL_DTSTART_PROPERTY,
+				 icalproperty_get_dtstart,
+                                 default_zone);
+}
+
+static inline time_t
+get_ical_end_time (icalcomponent *ical,
+                   icaltimezone  *default_zone)
+{
+  return get_time_from_property (ical,
+				 ICAL_DTEND_PROPERTY,
+				 icalproperty_get_dtend,
+                                 default_zone);
+}
+
+static gboolean
+get_ical_is_all_day (icalcomponent *ical,
+                     time_t         start_time,
+                     icaltimezone  *default_zone)
+{
+  icalproperty            *prop;
+  struct tm               *start_tm;
+  time_t                   end_time;
+  struct icaldurationtype  duration;
+  struct icaltimetype      start_icaltime;
+
+  start_icaltime = icalcomponent_get_dtstart (ical);
+  if (start_icaltime.is_date)
+    return TRUE;
+
+  start_tm = gmtime (&start_time);
+  if (start_tm->tm_sec  != 0 ||
+      start_tm->tm_min  != 0 ||
+      start_tm->tm_hour != 0)
+    return FALSE;
+
+  if ((end_time = get_ical_end_time (ical, default_zone)))
+    return (end_time - start_time) % 86400 == 0;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_DURATION_PROPERTY);
+  if (!prop)
+    return FALSE;
+
+  duration = icalproperty_get_duration (prop);
+
+  return icaldurationtype_as_int (duration) % 86400 == 0;
+}
+
+static inline time_t
+get_ical_due_time (icalcomponent *ical,
+                   icaltimezone  *default_zone)
+{
+  return get_time_from_property (ical,
+				 ICAL_DUE_PROPERTY,
+				 icalproperty_get_due,
+                                 default_zone);
+}
+
+static guint
+get_ical_percent_complete (icalcomponent *ical)
+{
+  icalproperty *prop;
+  icalproperty_status status;
+  int           percent_complete;
+
+  status = icalcomponent_get_status (ical);
+  if (status == ICAL_STATUS_COMPLETED)
+    return 100;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_COMPLETED_PROPERTY);
+  if (prop)
+    return 100;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_PERCENTCOMPLETE_PROPERTY);
+  if (!prop)
+    return 0;
+
+  percent_complete = icalproperty_get_percentcomplete (prop);
+
+  return CLAMP (percent_complete, 0, 100);
+}
+
+static inline time_t
+get_ical_completed_time (icalcomponent *ical,
+                         icaltimezone  *default_zone)
+{
+  return get_time_from_property (ical,
+				 ICAL_COMPLETED_PROPERTY,
+				 icalproperty_get_completed,
+                                 default_zone);
+}
+
+static int
+get_ical_priority (icalcomponent *ical)
+{
+  icalproperty *prop;
+
+  prop = icalcomponent_get_first_property (ical, ICAL_PRIORITY_PROPERTY);
+  if (!prop)
+    return -1;
+
+  return icalproperty_get_priority (prop);
+}
+
+static char *
+get_source_color (ECal *esource)
+{
+  ESource *source;
+
+  g_return_val_if_fail (E_IS_CAL (esource), NULL);
+
+  source = e_cal_get_source (esource);
+
+  return g_strdup (e_source_peek_color_spec (source));
+}
+
+static gchar *
+get_source_uri (ECal *esource)
+{
+    ESource *source;
+    gchar   *string;
+    gchar  **list;
+
+    g_return_val_if_fail (E_IS_CAL (esource), NULL);
+
+    source = e_cal_get_source (esource);
+    string = g_strdup (e_source_get_uri (source));
+    if (string) {
+        list = g_strsplit (string, ":", 2);
+        g_free (string);
+
+        if (list[0]) {
+            string = g_strdup (list[0]);
+            g_strfreev (list);
+            return string;
+        }
+	g_strfreev (list);
+    }
+    return NULL;
+}
+
+static inline int
+null_safe_strcmp (const char *a,
+		  const char *b)
+{
+  return (!a && !b) ? 0 : (a && !b) || (!a && b) ? 1 : strcmp (a, b);
+}
+
+static inline gboolean
+calendar_appointment_equal (CalendarAppointment *a,
+			    CalendarAppointment *b)
+{
+  GSList *la, *lb;
+
+  if (g_slist_length (a->occurrences) != g_slist_length (b->occurrences))
+      return FALSE;
+
+  for (la = a->occurrences, lb = b->occurrences; la && lb; la = la->next, lb = lb->next)
+    {
+      CalendarOccurrence *oa = la->data;
+      CalendarOccurrence *ob = lb->data;
+
+      if (oa->start_time != ob->start_time ||
+	  oa->end_time   != ob->end_time)
+	return FALSE;
+    }
+
+  return
+    null_safe_strcmp (a->uid,          b->uid)          == 0 &&
+    null_safe_strcmp (a->uri,          b->uri)          == 0 &&
+    null_safe_strcmp (a->summary,      b->summary)      == 0 &&
+    null_safe_strcmp (a->description,  b->description)  == 0 &&
+    null_safe_strcmp (a->color_string, b->color_string) == 0 &&
+    a->start_time == b->start_time                         &&
+    a->end_time   == b->end_time                           &&
+    a->is_all_day == b->is_all_day;
+}
+
+static void
+calendar_appointment_copy (CalendarAppointment *appointment,
+			   CalendarAppointment *appointment_copy)
+{
+  GSList *l;
+
+  g_assert (appointment != NULL);
+  g_assert (appointment_copy != NULL);
+
+  appointment_copy->occurrences = g_slist_copy (appointment->occurrences);
+  for (l = appointment_copy->occurrences; l; l = l->next)
+    {
+      CalendarOccurrence *occurrence = l->data;
+      CalendarOccurrence *occurrence_copy;
+
+      occurrence_copy             = g_new0 (CalendarOccurrence, 1);
+      occurrence_copy->start_time = occurrence->start_time;
+      occurrence_copy->end_time   = occurrence->end_time;
+
+      l->data = occurrence_copy;
+    }
+
+  appointment_copy->uid          = g_strdup (appointment->uid);
+  appointment_copy->uri          = g_strdup (appointment->uri);
+  appointment_copy->summary      = g_strdup (appointment->summary);
+  appointment_copy->description  = g_strdup (appointment->description);
+  appointment_copy->color_string = g_strdup (appointment->color_string);
+  appointment_copy->start_time   = appointment->start_time;
+  appointment_copy->end_time     = appointment->end_time;
+  appointment_copy->is_all_day   = appointment->is_all_day;
+}
+
+static void
+calendar_appointment_finalize (CalendarAppointment *appointment)
+{
+  GSList *l;
+
+  for (l = appointment->occurrences; l; l = l->next)
+    g_free (l->data);
+  g_slist_free (appointment->occurrences);
+  appointment->occurrences = NULL;
+
+  g_free (appointment->uid);
+  appointment->uid = NULL;
+
+  g_free (appointment->rid);
+  appointment->rid = NULL;
+
+  g_free (appointment->uri);
+  appointment->uri = NULL;
+
+  g_free (appointment->summary);
+  appointment->summary = NULL;
+
+  g_free (appointment->description);
+  appointment->description = NULL;
+
+  g_free (appointment->color_string);
+  appointment->color_string = NULL;
+
+  appointment->start_time = 0;
+  appointment->is_all_day = FALSE;
+}
+
+static void
+calendar_appointment_init (CalendarAppointment  *appointment,
+			   icalcomponent        *ical,
+                           CalendarClientSource *source,
+                           icaltimezone         *default_zone)
+{
+  appointment->uid          = get_ical_uid (ical);
+  appointment->rid          = get_ical_rid (ical);
+  appointment->uri          = get_source_uri (source->source);
+  appointment->summary      = get_ical_summary (ical);
+  appointment->description  = get_ical_description (ical);
+  appointment->color_string = get_source_color (source->source);
+  appointment->start_time   = get_ical_start_time (ical, default_zone);
+  appointment->end_time     = get_ical_end_time (ical, default_zone);
+  appointment->is_all_day   = get_ical_is_all_day (ical,
+                                                   appointment->start_time,
+                                                   default_zone);
+}
+
+static icaltimezone *
+resolve_timezone_id (const char *tzid,
+		     ECal       *source)
+{
+  icaltimezone *retval;
+
+  retval = icaltimezone_get_builtin_timezone_from_tzid (tzid);
+  if (!retval)
+    {
+      e_cal_get_timezone (source, tzid, &retval, NULL);
+    }
+
+  return retval;
+}
+
+static gboolean
+calendar_appointment_collect_occurrence (ECalComponent  *component,
+					 time_t          occurrence_start,
+					 time_t          occurrence_end,
+					 gpointer        data)
+{
+  CalendarOccurrence *occurrence;
+  GSList **collect_loc = data;
+
+  occurrence             = g_new0 (CalendarOccurrence, 1);
+  occurrence->start_time = occurrence_start;
+  occurrence->end_time   = occurrence_end;
+
+  *collect_loc = g_slist_prepend (*collect_loc, occurrence);
+
+  return TRUE;
+}
+
+static void
+calendar_appointment_generate_ocurrences (CalendarAppointment *appointment,
+					  icalcomponent       *ical,
+					  ECal                *source,
+					  time_t               start,
+					  time_t               end,
+                                          icaltimezone        *default_zone)
+{
+  ECalComponent *ecal;
+
+  g_assert (appointment->occurrences == NULL);
+
+  ecal = e_cal_component_new ();
+  e_cal_component_set_icalcomponent (ecal,
+				     icalcomponent_new_clone (ical));
+
+  e_cal_recur_generate_instances (ecal,
+				  start,
+				  end,
+				  calendar_appointment_collect_occurrence,
+				  &appointment->occurrences,
+				  (ECalRecurResolveTimezoneFn) resolve_timezone_id,
+				  source,
+				  default_zone);
+
+  g_object_unref (ecal);
+
+  appointment->occurrences = g_slist_reverse (appointment->occurrences);
+}
+
+static inline gboolean
+calendar_task_equal (CalendarTask *a,
+		     CalendarTask *b)
+{
+  return
+    null_safe_strcmp (a->uid,          b->uid)          == 0 &&
+    null_safe_strcmp (a->summary,      b->summary)      == 0 &&
+    null_safe_strcmp (a->description,  b->description)  == 0 &&
+    null_safe_strcmp (a->color_string, b->color_string) == 0 &&
+    a->start_time       == b->start_time                   &&
+    a->due_time         == b->due_time                     &&
+    a->percent_complete == b->percent_complete             &&
+    a->completed_time   == b->completed_time               &&
+    a->priority         == b->priority;
+}
+
+static void
+calendar_task_copy (CalendarTask *task,
+		    CalendarTask *task_copy)
+{
+  g_assert (task != NULL);
+  g_assert (task_copy != NULL);
+
+  task_copy->uid              = g_strdup (task->uid);
+  task_copy->summary          = g_strdup (task->summary);
+  task_copy->description      = g_strdup (task->description);
+  task_copy->color_string     = g_strdup (task->color_string);
+  task_copy->start_time       = task->start_time;
+  task_copy->due_time         = task->due_time;
+  task_copy->percent_complete = task->percent_complete;
+  task_copy->completed_time   = task->completed_time;
+  task_copy->priority         = task->priority;
+}
+
+static void
+calendar_task_finalize (CalendarTask *task)
+{
+  g_free (task->uid);
+  task->uid = NULL;
+
+  g_free (task->summary);
+  task->summary = NULL;
+
+  g_free (task->description);
+  task->description = NULL;
+
+  g_free (task->color_string);
+  task->color_string = NULL;
+
+  task->percent_complete = 0;
+}
+
+static void
+calendar_task_init (CalendarTask         *task,
+		    icalcomponent        *ical,
+                    CalendarClientSource *source,
+                    icaltimezone         *default_zone)
+{
+  task->uid              = get_ical_uid (ical);
+  task->summary          = get_ical_summary (ical);
+  task->description      = get_ical_description (ical);
+  task->color_string     = get_source_color (source->source);
+  task->start_time       = get_ical_start_time (ical, default_zone);
+  task->due_time         = get_ical_due_time (ical, default_zone);
+  task->percent_complete = get_ical_percent_complete (ical);
+  task->completed_time   = get_ical_completed_time (ical, default_zone);
+  task->priority         = get_ical_priority (ical);
+}
+
+void
+calendar_event_free (CalendarEvent *event)
+{
+  switch (event->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      calendar_appointment_finalize (CALENDAR_APPOINTMENT (event));
+      break;
+    case CALENDAR_EVENT_TASK:
+      calendar_task_finalize (CALENDAR_TASK (event));
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  g_free (event);
+}
+
+static CalendarEvent *
+calendar_event_new (icalcomponent        *ical,
+                    CalendarClientSource *source,
+                    icaltimezone         *default_zone)
+{
+  CalendarEvent *event;
+
+  event = g_new0 (CalendarEvent, 1);
+
+  switch (icalcomponent_isa (ical))
+    {
+    case ICAL_VEVENT_COMPONENT:
+      event->type = CALENDAR_EVENT_APPOINTMENT;
+      calendar_appointment_init (CALENDAR_APPOINTMENT (event),
+                                 ical,
+                                 source,
+                                 default_zone);
+      break;
+    case ICAL_VTODO_COMPONENT:
+      event->type = CALENDAR_EVENT_TASK;
+      calendar_task_init (CALENDAR_TASK (event),
+                          ical,
+                          source,
+                          default_zone);
+      break;
+    default:
+      g_warning ("Unknown calendar component type: %d\n",
+                 icalcomponent_isa (ical));
+      g_free (event);
+      return NULL;
+    }
+
+  return event;
+}
+
+static CalendarEvent *
+calendar_event_copy (CalendarEvent *event)
+{
+  CalendarEvent *retval;
+
+  if (!event)
+    return NULL;
+
+  retval = g_new0 (CalendarEvent, 1);
+
+  retval->type = event->type;
+
+  switch (event->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      calendar_appointment_copy (CALENDAR_APPOINTMENT (event),
+				 CALENDAR_APPOINTMENT (retval));
+      break;
+    case CALENDAR_EVENT_TASK:
+      calendar_task_copy (CALENDAR_TASK (event),
+			  CALENDAR_TASK (retval));
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  return retval;
+}
+
+static char *
+calendar_event_get_uid (CalendarEvent *event)
+{
+  switch (event->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      return g_strdup_printf ("%s%s", CALENDAR_APPOINTMENT (event)->uid, CALENDAR_APPOINTMENT (event)->rid ? CALENDAR_APPOINTMENT (event)->rid : ""); 
+      break;
+    case CALENDAR_EVENT_TASK:
+      return g_strdup (CALENDAR_TASK (event)->uid);
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+  return NULL;
+}
+
+static gboolean
+calendar_event_equal (CalendarEvent *a,
+		      CalendarEvent *b)
+{
+  if (!a && !b)
+    return TRUE;
+
+  if ((a && !b) || (!a && b))
+    return FALSE;
+
+  if (a->type != b->type)
+    return FALSE;
+
+  switch (a->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      return calendar_appointment_equal (CALENDAR_APPOINTMENT (a),
+					 CALENDAR_APPOINTMENT (b));
+    case CALENDAR_EVENT_TASK:
+      return calendar_task_equal (CALENDAR_TASK (a),
+				  CALENDAR_TASK (b));
+    default:
+      break;
+    }
+ 
+  g_assert_not_reached ();
+
+  return FALSE;
+}
+
+static void
+calendar_event_generate_ocurrences (CalendarEvent *event,
+				    icalcomponent *ical,
+				    ECal          *source,
+				    time_t         start,
+				    time_t         end,
+                                    icaltimezone  *default_zone)
+{
+  if (event->type != CALENDAR_EVENT_APPOINTMENT)
+    return;
+
+  calendar_appointment_generate_ocurrences (CALENDAR_APPOINTMENT (event),
+					    ical,
+					    source,
+					    start,
+					    end,
+                                            default_zone);
+}
+
+static inline void
+calendar_event_debug_dump (CalendarEvent *event)
+{
+#ifdef CALENDAR_ENABLE_DEBUG
+  switch (event->type)
+    {
+    case CALENDAR_EVENT_APPOINTMENT:
+      {
+	char   *start_str;
+	char   *end_str;
+	GSList *l;
+
+	start_str = CALENDAR_APPOINTMENT (event)->start_time ?
+	                    isodate_from_time_t (CALENDAR_APPOINTMENT (event)->start_time) :
+	                    g_strdup ("(undefined)");
+	end_str = CALENDAR_APPOINTMENT (event)->end_time ?
+	                    isodate_from_time_t (CALENDAR_APPOINTMENT (event)->end_time) :
+	                    g_strdup ("(undefined)");
+	  
+	dprintf ("Appointment: uid '%s', summary '%s', description '%s', "
+		 "start_time '%s', end_time '%s', is_all_day %s\n",
+		 CALENDAR_APPOINTMENT (event)->uid,
+		 CALENDAR_APPOINTMENT (event)->summary,
+		 CALENDAR_APPOINTMENT (event)->description,
+		 start_str,
+		 end_str,
+		 CALENDAR_APPOINTMENT (event)->is_all_day ? "(true)" : "(false)");
+
+	g_free (start_str);
+	g_free (end_str);
+
+	dprintf ("  Occurrences:\n");
+	for (l = CALENDAR_APPOINTMENT (event)->occurrences; l; l = l->next)
+	  {
+	    CalendarOccurrence *occurrence = l->data;
+
+	    start_str = occurrence->start_time ?
+	      isodate_from_time_t (occurrence->start_time) :
+	      g_strdup ("(undefined)");
+	    
+	    end_str = occurrence->end_time ?
+	      isodate_from_time_t (occurrence->end_time) :
+	      g_strdup ("(undefined)");
+
+	    dprintf ("    start_time '%s', end_time '%s'\n",
+		     start_str, end_str);
+
+	    g_free (start_str);
+	    g_free (end_str);
+	  }
+      }
+      break;
+    case CALENDAR_EVENT_TASK:
+      {
+	char *start_str;
+	char *due_str;
+	char *completed_str;
+
+	start_str = CALENDAR_TASK (event)->start_time ?
+	                    isodate_from_time_t (CALENDAR_TASK (event)->start_time) :
+	                    g_strdup ("(undefined)");
+	due_str = CALENDAR_TASK (event)->due_time ?
+	                    isodate_from_time_t (CALENDAR_TASK (event)->due_time) :
+	                    g_strdup ("(undefined)");
+	completed_str = CALENDAR_TASK (event)->completed_time ?
+	                    isodate_from_time_t (CALENDAR_TASK (event)->completed_time) :
+	                    g_strdup ("(undefined)");
+
+	dprintf ("Task: uid '%s', summary '%s', description '%s', "
+		 "start_time '%s', due_time '%s', percent_complete %d, completed_time '%s'\n",
+		 CALENDAR_TASK (event)->uid,
+		 CALENDAR_TASK (event)->summary,
+		 CALENDAR_TASK (event)->description,
+		 start_str,
+		 due_str,
+		 CALENDAR_TASK (event)->percent_complete,
+		 completed_str);
+
+	g_free (completed_str);
+      }
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+#endif
+}
+
+static inline CalendarClientQuery *
+goddamn_this_is_crack (CalendarClientSource *source,
+		       ECalView             *view,
+		       gboolean             *emit_signal)
+{
+  g_assert (view != NULL);
+
+  if (source->completed_query.view == view)
+    {
+      if (emit_signal)
+	*emit_signal = TRUE;
+      return &source->completed_query;
+    }
+  else if (source->in_progress_query.view == view)
+    {
+      if (emit_signal)
+	*emit_signal = FALSE;
+      return &source->in_progress_query;
+    }
+
+  g_assert_not_reached ();
+
+  return NULL;
+}
+
+static void
+calendar_client_handle_query_completed (CalendarClientSource *source,
+					ECalendarStatus       status,
+					ECalView             *view)
+{
+  CalendarClientQuery *query;
+
+  query = goddamn_this_is_crack (source, view, NULL);
+  
+  dprintf ("Query %p completed: %s\n", query, e_cal_get_error_message (status));
+
+  if (status != E_CALENDAR_STATUS_OK)
+    {
+      g_warning ("Calendar query failed: %s\n",
+		 e_cal_get_error_message (status));
+      calendar_client_stop_query (source->client, source, query);
+      return;
+    }
+
+  g_assert (source->query_in_progress != FALSE);
+  g_assert (query == &source->in_progress_query);
+
+  calendar_client_query_finalize (&source->completed_query);
+
+  source->completed_query = source->in_progress_query;
+  source->query_completed = TRUE;
+
+  source->query_in_progress        = FALSE;
+  source->in_progress_query.view   = NULL;
+  source->in_progress_query.events = NULL;
+
+  g_signal_emit (source->client, source->changed_signal_id, 0);
+}
+
+static void
+calendar_client_handle_query_result (CalendarClientSource *source,
+				     GList                *objects,
+				     ECalView             *view)
+{
+  CalendarClientQuery *query;
+  CalendarClient      *client;
+  gboolean             emit_signal;
+  gboolean             events_changed;
+  GList               *l;
+  time_t               month_begin;
+  time_t               month_end;
+
+  client = source->client;
+
+  query = goddamn_this_is_crack (source, view, &emit_signal);
+
+  dprintf ("Query %p result: %d objects:\n",
+	   query, g_list_length (objects));
+
+  month_begin = make_time_for_day_begin (1,
+					 client->priv->month,
+					 client->priv->year);
+
+  month_end = make_time_for_day_begin (1,
+				       client->priv->month + 1,
+				       client->priv->year);
+
+  events_changed = FALSE;
+  for (l = objects; l; l = l->next)
+    {
+      CalendarEvent *event;
+      CalendarEvent *old_event;
+      icalcomponent *ical = l->data;
+      char          *uid;
+      
+      event = calendar_event_new (ical, source, client->priv->zone);
+      if (!event)
+	      continue;
+
+      calendar_event_generate_ocurrences (event,
+					  ical,
+					  source->source,
+					  month_begin,
+					  month_end,
+                                          client->priv->zone);
+
+      uid = calendar_event_get_uid (event);
+      
+      old_event = g_hash_table_lookup (query->events, uid);
+
+      if (!calendar_event_equal (event, old_event))
+	{
+ 	  dprintf ("Event %s: ", old_event ? "modified" : "added");
+
+	  calendar_event_debug_dump (event);
+
+	  g_hash_table_replace (query->events, uid, event);
+
+	  events_changed = TRUE;
+	}
+      else
+	{
+	  g_free (uid);
+	}		
+    }
+
+  if (emit_signal && events_changed)
+    {
+      g_signal_emit (source->client, source->changed_signal_id, 0);
+    }
+}
+
+static gboolean
+check_object_remove (gpointer key,
+                     gpointer value,
+                     gpointer data)
+{
+  char             *uid = data;
+  ssize_t           len;
+
+  len = strlen (uid);
+  
+  if (len <= strlen (key) && strncmp (uid, key, len) == 0)
+    {
+      dprintf ("Event removed: ");
+
+      calendar_event_debug_dump (value);
+
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+static void
+calendar_client_handle_objects_removed (CalendarClientSource *source,
+					GList                *ids,
+					ECalView             *view)
+{
+  CalendarClientQuery *query;
+  gboolean             emit_signal;
+  gboolean             events_changed;
+  GList               *l;
+
+  query = goddamn_this_is_crack (source, view, &emit_signal);
+
+  events_changed = FALSE;
+  for (l = ids; l; l = l->next)
+    {
+      CalendarEvent   *event;
+      ECalComponentId *id = l->data;
+      char            *uid = g_strdup_printf ("%s%s", id->uid, id->rid ? id->rid : "");
+
+      if (!id->rid || !(*id->rid))
+	{
+	  int size = g_hash_table_size (query->events);
+
+	  g_hash_table_foreach_remove (query->events, check_object_remove, id->uid);
+
+		if (size != g_hash_table_size (query->events))
+			events_changed = TRUE;		
+	}
+      else if ((event = g_hash_table_lookup (query->events, uid)))
+	{
+	  dprintf ("Event removed: ");
+
+	  calendar_event_debug_dump (event);
+
+	  g_assert (g_hash_table_remove (query->events, uid));
+
+	  events_changed = TRUE;
+	}
+      g_free (uid);
+    }
+
+  if (emit_signal && events_changed)
+    {
+      g_signal_emit (source->client, source->changed_signal_id, 0);
+    }
+}
+
+static void
+calendar_client_query_finalize (CalendarClientQuery *query)
+{
+  if (query->view)
+    g_object_unref (query->view);
+  query->view = NULL;
+
+  if (query->events)
+    g_hash_table_destroy (query->events);
+  query->events = NULL;
+}
+
+static void
+calendar_client_stop_query (CalendarClient       *client,
+			    CalendarClientSource *source,
+			    CalendarClientQuery  *query)
+{
+  if (query == &source->in_progress_query)
+    {
+      dprintf ("Stopping in progress query %p\n", query);
+
+      g_assert (source->query_in_progress != FALSE);
+
+      source->query_in_progress = FALSE;
+    }
+  else if (query == &source->completed_query)
+    {
+      dprintf ("Stopping completed query %p\n", query);
+
+      g_assert (source->query_completed != FALSE);
+
+      source->query_completed = FALSE;
+    }
+  else
+    g_assert_not_reached ();
+  
+  calendar_client_query_finalize (query);
+}
+
+static void
+calendar_client_start_query (CalendarClient       *client,
+			     CalendarClientSource *source,
+			     const char           *query)
+{
+  ECalView *view = NULL;
+  GError   *error = NULL;
+
+  if (!e_cal_get_query (source->source, query, &view, &error))
+    {
+      g_warning ("Error preparing the query: '%s': %s\n",
+		 query, error->message);
+      g_error_free (error);
+      return;
+    }
+
+  g_assert (view != NULL);
+
+  if (source->query_in_progress)
+    calendar_client_stop_query (client, source, &source->in_progress_query);
+  
+  dprintf ("Starting query %p: '%s'\n", &source->in_progress_query, query);
+
+  source->query_in_progress        = TRUE;
+  source->in_progress_query.view   = view;
+  source->in_progress_query.events =
+    g_hash_table_new_full (g_str_hash,
+			   g_str_equal,
+			   g_free,
+			   (GDestroyNotify) calendar_event_free);
+
+  g_signal_connect_swapped (view, "objects-added",
+			    G_CALLBACK (calendar_client_handle_query_result),
+			    source);
+  g_signal_connect_swapped (view, "objects-modified",
+			    G_CALLBACK (calendar_client_handle_query_result),
+			    source);
+  g_signal_connect_swapped (view, "objects-removed",
+			    G_CALLBACK (calendar_client_handle_objects_removed),
+			    source);
+  g_signal_connect_swapped (view, "view-done",
+			    G_CALLBACK (calendar_client_handle_query_completed),
+			    source);
+
+  e_cal_view_start (view);
+}
+
+static void
+calendar_client_update_appointments (CalendarClient *client)
+{
+  GSList *l;
+  char   *query;
+  char   *month_begin;
+  char   *month_end;
+
+  if (client->priv->month == -1 ||
+      client->priv->year  == -1)
+    return;
+
+  month_begin = make_isodate_for_day_begin (1,
+					    client->priv->month,
+					    client->priv->year);
+
+  month_end = make_isodate_for_day_begin (1,
+					  client->priv->month + 1,
+					  client->priv->year);
+
+  query = g_strdup_printf ("occur-in-time-range? (make-time \"%s\") "
+			                        "(make-time \"%s\")",
+			   month_begin, month_end);
+
+  for (l = client->priv->appointment_sources; l; l = l->next)
+    {
+      CalendarClientSource *cs = l->data;
+                  
+      if (e_cal_get_load_state (cs->source) != E_CAL_LOAD_LOADED)  
+        continue;
+
+      calendar_client_start_query (client, cs, query);
+    }
+
+  g_free (month_begin);
+  g_free (month_end);
+  g_free (query);
+}
+
+/* FIXME:
+ * perhaps we should use evo's "hide_completed_tasks" pref?
+ */
+static void
+calendar_client_update_tasks (CalendarClient *client)
+{
+  GSList *l;
+  char   *query;
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+  /* FIXME: this doesn't work for tasks without a start or
+   *        due date
+   *        Look at filter_task() to see the behaviour we
+   *        want.
+   */
+  
+  char   *day_begin;
+  char   *day_end;
+
+  if (client->priv->day   == -1 ||
+      client->priv->month == -1 ||
+      client->priv->year  == -1)
+    return;
+
+  day_begin = make_isodate_for_day_begin (client->priv->day,
+					  client->priv->month,
+					  client->priv->year);
+
+  day_end = make_isodate_for_day_begin (client->priv->day + 1,
+					client->priv->month,
+					client->priv->year);
+  if (!day_begin || !day_end)
+    {
+      g_warning ("Cannot run query with invalid date: %dd %dy %dm\n",
+		 client->priv->day,
+		 client->priv->month,
+		 client->priv->year);
+      g_free (day_begin);
+      g_free (day_end);
+      return;
+    }
+  
+  query = g_strdup_printf ("(and (occur-in-time-range? (make-time \"%s\") "
+                                                      "(make-time \"%s\")) "
+                             "(or (not is-completed?) "
+                               "(and (is-completed?) "
+                                    "(not (completed-before? (make-time \"%s\"))))))",
+			   day_begin, day_end, day_begin);
+#else
+  query = g_strdup ("#t");
+#endif /* FIX_BROKEN_TASKS_QUERY */
+
+  for (l = client->priv->task_sources; l; l = l->next)
+    {
+      CalendarClientSource *cs = l->data;
+
+      if (e_cal_get_load_state (cs->source) != E_CAL_LOAD_LOADED)  
+        continue;
+
+      calendar_client_start_query (client, cs, query);
+    }
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+  g_free (day_begin);
+  g_free (day_end);
+#endif
+  g_free (query);
+}
+
+static void
+calendar_client_source_finalize (CalendarClientSource *source)
+{
+  source->client = NULL;
+
+  if (source->source) {
+    g_signal_handlers_disconnect_by_func (source->source,
+                                          cal_opened_cb, source);
+    g_object_unref (source->source);
+  }
+  source->source = NULL;
+
+  calendar_client_query_finalize (&source->completed_query);
+  calendar_client_query_finalize (&source->in_progress_query);
+  
+  source->query_completed   = FALSE;
+  source->query_in_progress = FALSE;
+}
+
+static int
+compare_calendar_sources (CalendarClientSource *s1,
+			  CalendarClientSource *s2)
+{
+  return (s1->source == s2->source) ? 0 : 1;
+}
+
+static GSList *
+calendar_client_update_sources_list (CalendarClient *client,
+				     GSList         *sources,
+				     GSList         *esources,
+				     guint           changed_signal_id)
+{
+  GSList *retval, *l;
+
+  retval = NULL;
+
+  for (l = esources; l; l = l->next)
+    {
+      CalendarClientSource  dummy_source;
+      CalendarClientSource *new_source;
+      GSList               *s;
+      ECal                 *esource = l->data;
+
+      dummy_source.source = esource;
+
+      dprintf ("update_sources_list: adding client %s: ",
+	       e_source_peek_uid (e_cal_get_source (esource)));
+
+      if ((s = g_slist_find_custom (sources,
+				    &dummy_source,
+				    (GCompareFunc) compare_calendar_sources)))
+	{
+	  dprintf ("already on list\n");
+	  new_source = s->data;
+	  sources = g_slist_delete_link (sources, s);
+	}
+      else
+	{
+	  dprintf ("added\n");
+	  new_source                    = g_new0 (CalendarClientSource, 1);
+	  new_source->client            = client;
+	  new_source->source            = g_object_ref (esource);
+	  new_source->changed_signal_id = changed_signal_id;
+	}
+
+      retval = g_slist_prepend (retval, new_source);
+    }
+
+  for (l = sources; l; l = l->next)
+    {
+      CalendarClientSource *source = l->data;
+
+      dprintf ("Removing client %s from list\n",
+	       e_source_peek_uid (e_cal_get_source (source->source)));
+
+      calendar_client_source_finalize (source);
+      g_free (source);
+    }
+  g_slist_free (sources);
+
+  return retval;
+}
+
+static void
+calendar_client_appointment_sources_changed (CalendarClient  *client)
+{
+  GSList *esources;
+
+  dprintf ("appointment_sources_changed: updating ...\n");
+
+  esources = calendar_sources_get_appointment_sources (client->priv->calendar_sources);
+
+  client->priv->appointment_sources = 
+    calendar_client_update_sources_list (client,
+					 client->priv->appointment_sources,
+					 esources,
+					 signals [APPOINTMENTS_CHANGED]);
+
+  load_calendars (client, CALENDAR_EVENT_APPOINTMENT);
+  calendar_client_update_appointments (client);
+}
+
+static void
+calendar_client_task_sources_changed (CalendarClient  *client)
+{
+  GSList *esources;
+
+  dprintf ("task_sources_changed: updating ...\n");
+
+  esources = calendar_sources_get_task_sources (client->priv->calendar_sources);
+
+  client->priv->task_sources = 
+    calendar_client_update_sources_list (client,
+					 client->priv->task_sources,
+					 esources,
+					 signals [TASKS_CHANGED]);
+
+  load_calendars (client, CALENDAR_EVENT_TASK);
+  calendar_client_update_tasks (client);
+}
+
+void
+calendar_client_get_date (CalendarClient *client,
+                          guint          *year,
+                          guint          *month,
+                          guint          *day)
+{
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+
+  if (year)
+    *year = client->priv->year;
+
+  if (month)
+    *month = client->priv->month;
+
+  if (day)
+    *day = client->priv->day;
+}
+
+void
+calendar_client_select_month (CalendarClient *client,
+			      guint           month,
+			      guint           year)
+{
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+  g_return_if_fail (month <= 11);
+
+  if (client->priv->year != year || client->priv->month != month)
+    {
+      client->priv->month = month;
+      client->priv->year  = year;
+
+      calendar_client_update_appointments (client);
+      calendar_client_update_tasks (client);
+
+      g_object_freeze_notify (G_OBJECT (client));
+      g_object_notify (G_OBJECT (client), "month");
+      g_object_notify (G_OBJECT (client), "year");
+      g_object_thaw_notify (G_OBJECT (client));
+    }
+}
+
+void
+calendar_client_select_day (CalendarClient *client,
+			    guint           day)
+{
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+  g_return_if_fail (day <= 31);
+
+  if (client->priv->day != day)
+    {
+      client->priv->day = day;
+
+      /* don't need to update appointments unless
+       * the selected month changes
+       */
+#ifdef FIX_BROKEN_TASKS_QUERY
+      calendar_client_update_tasks (client);
+#endif
+
+      g_object_notify (G_OBJECT (client), "day");
+    }
+}
+
+typedef struct
+{
+  CalendarClient *client;
+  GSList         *events;
+  time_t          start_time;
+  time_t          end_time;
+} FilterData;
+
+typedef void (* CalendarEventFilterFunc) (const char    *uid,
+					  CalendarEvent *event,
+					  FilterData    *filter_data);
+
+static void
+filter_appointment (const char    *uid,
+		    CalendarEvent *event,
+		    FilterData    *filter_data)
+{
+  GSList *occurrences, *l;
+
+  if (event->type != CALENDAR_EVENT_APPOINTMENT)
+    return;
+
+  occurrences = CALENDAR_APPOINTMENT (event)->occurrences;
+  CALENDAR_APPOINTMENT (event)->occurrences = NULL;
+
+  for (l = occurrences; l; l = l->next)
+    {
+      CalendarOccurrence *occurrence = l->data;
+      time_t start_time = occurrence->start_time;
+      time_t end_time   = occurrence->end_time;
+
+      if ((start_time >= filter_data->start_time &&
+           start_time < filter_data->end_time) ||
+          (start_time <= filter_data->start_time &&
+           (end_time - 1) > filter_data->start_time))
+	{
+	  CalendarEvent *new_event;
+
+	  new_event = calendar_event_copy (event);
+	      
+	  CALENDAR_APPOINTMENT (new_event)->start_time = occurrence->start_time;
+	  CALENDAR_APPOINTMENT (new_event)->end_time   = occurrence->end_time;
+	      
+	  filter_data->events = g_slist_prepend (filter_data->events, new_event);
+	}
+    }
+
+  CALENDAR_APPOINTMENT (event)->occurrences = occurrences;
+}
+
+static void
+filter_task (const char    *uid,
+	     CalendarEvent *event,
+	     FilterData    *filter_data)
+{
+#ifdef FIX_BROKEN_TASKS_QUERY
+  CalendarTask *task;
+#endif
+
+  if (event->type != CALENDAR_EVENT_TASK)
+    return;
+
+#ifdef FIX_BROKEN_TASKS_QUERY
+  task = CALENDAR_TASK (event);
+
+  if (task->start_time && task->start_time > filter_data->start_time)
+    return;
+
+  if (task->completed_time && 
+      (task->completed_time < filter_data->start_time ||
+       task->completed_time > filter_data->end_time))
+    return;
+#endif /* FIX_BROKEN_TASKS_QUERY */
+
+  filter_data->events = g_slist_prepend (filter_data->events,
+					 calendar_event_copy (event));
+}
+
+static GSList *
+calendar_client_filter_events (CalendarClient          *client,
+			       GSList                  *sources,
+			       CalendarEventFilterFunc  filter_func,
+			       time_t                   start_time,
+			       time_t                   end_time)
+{
+  FilterData  filter_data;
+  GSList     *l;
+  GSList     *retval;
+
+  if (!sources)
+    return NULL;
+
+  filter_data.client     = client;
+  filter_data.events     = NULL;
+  filter_data.start_time = start_time;
+  filter_data.end_time   = end_time;
+
+  retval = NULL;
+  for (l = sources; l; l = l->next)
+    {
+      CalendarClientSource *source = l->data;
+
+      if (source->query_completed)
+	{
+	  filter_data.events = NULL;
+	  g_hash_table_foreach (source->completed_query.events,
+				(GHFunc) filter_func,
+				&filter_data);
+
+	  filter_data.events = g_slist_reverse (filter_data.events);
+
+	  retval = g_slist_concat (retval, filter_data.events);
+	}
+    }
+
+  return retval;
+}
+
+GSList *
+calendar_client_get_events (CalendarClient    *client,
+			    CalendarEventType  event_mask)
+{
+  GSList *appointments;
+  GSList *tasks;
+  time_t  day_begin;
+  time_t  day_end;
+
+  g_return_val_if_fail (CALENDAR_IS_CLIENT (client), NULL);
+  g_return_val_if_fail (client->priv->day   != -1 &&
+			client->priv->month != -1 &&
+			client->priv->year  != -1, NULL);
+
+  day_begin = make_time_for_day_begin (client->priv->day,
+				       client->priv->month,
+				       client->priv->year);
+  day_end   = make_time_for_day_begin (client->priv->day + 1,
+				       client->priv->month,
+				       client->priv->year);
+
+  appointments = NULL;
+  if (event_mask & CALENDAR_EVENT_APPOINTMENT)
+    {
+      appointments = calendar_client_filter_events (client,
+						    client->priv->appointment_sources,
+						    filter_appointment,
+						    day_begin,
+						    day_end);
+    }
+
+  tasks = NULL;
+  if (event_mask & CALENDAR_EVENT_TASK)
+    {
+      tasks = calendar_client_filter_events (client,
+					     client->priv->task_sources,
+					     filter_task,
+					     day_begin,
+					     day_end);
+    }
+
+  return g_slist_concat (appointments, tasks);
+}
+
+static inline int
+day_from_time_t (time_t t)
+{
+  struct tm *tm = localtime (&t);
+
+  g_assert (tm == NULL || (tm->tm_mday >=1 && tm->tm_mday <= 31));
+
+  return tm ? tm->tm_mday : 0;
+}
+
+void
+calendar_client_foreach_appointment_day (CalendarClient  *client,
+					 CalendarDayIter  iter_func,
+					 gpointer         user_data)
+{
+  GSList   *appointments, *l;
+  gboolean  marked_days [32] = { FALSE, };
+  time_t    month_begin;
+  time_t    month_end;
+  int       i;
+
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+  g_return_if_fail (iter_func != NULL);
+  g_return_if_fail (client->priv->month != -1 &&
+		    client->priv->year  != -1);
+
+  month_begin = make_time_for_day_begin (1,
+					 client->priv->month,
+					 client->priv->year);
+  month_end   = make_time_for_day_begin (1,
+					 client->priv->month + 1,
+					 client->priv->year);
+  
+  appointments = calendar_client_filter_events (client,
+						client->priv->appointment_sources,
+						filter_appointment,
+						month_begin,
+						month_end);
+  for (l = appointments; l; l = l->next)
+    {
+      CalendarAppointment *appointment = l->data;
+
+      if (appointment->start_time)
+        {
+          time_t day_time = appointment->start_time;
+
+          if (day_time >= month_begin)
+            marked_days [day_from_time_t (day_time)] = TRUE;
+      
+          if (appointment->end_time)
+            {
+              int day_offset;
+              int duration = appointment->end_time - appointment->start_time;
+	      /* mark the days for the appointment, no need to add an extra one when duration is a multiple of 86400 */
+              for (day_offset = 1; day_offset <= duration / 86400 && duration != day_offset * 86400; day_offset++)
+                {
+                  time_t day_tm = appointment->start_time + day_offset * 86400;
+
+                  if (day_tm > month_end)
+                    break;
+                  if (day_tm >= month_begin)
+                    marked_days [day_from_time_t (day_tm)] = TRUE;
+                }
+            }
+        }
+      calendar_event_free (CALENDAR_EVENT (appointment));
+    }
+
+  g_slist_free (appointments);
+
+  for (i = 1; i < 32; i++)
+    {
+      if (marked_days [i])
+	iter_func (client, i, user_data);
+    }
+}
+
+void
+calendar_client_set_task_completed (CalendarClient *client,
+				    char           *task_uid,
+				    gboolean        task_completed,
+				    guint           percent_complete)
+{
+  GSList              *l;
+  ECal                *esource;
+  icalcomponent       *ical;
+  icalproperty        *prop;
+  icalproperty_status  status;
+
+  g_return_if_fail (CALENDAR_IS_CLIENT (client));
+  g_return_if_fail (task_uid != NULL);
+  g_return_if_fail (task_completed == FALSE || percent_complete == 100);
+
+  ical = NULL;
+  esource = NULL;
+  for (l = client->priv->task_sources; l; l = l->next)
+    {
+      CalendarClientSource *source = l->data;
+
+      esource = source->source;
+      e_cal_get_object (esource, task_uid, NULL, &ical, NULL);
+      if (ical)
+	break;
+    }
+
+  if (!ical)
+    {
+      g_warning ("Cannot locate task with uid = '%s'\n", task_uid);
+      return;
+    }
+
+  g_assert (esource != NULL);
+
+  /* Completed time */
+  prop = icalcomponent_get_first_property (ical,
+					   ICAL_COMPLETED_PROPERTY);
+  if (task_completed)
+    {
+      struct icaltimetype  completed_time;
+
+      completed_time = icaltime_current_time_with_zone (client->priv->zone);
+      if (!prop)
+	{
+	  icalcomponent_add_property (ical,
+				      icalproperty_new_completed (completed_time));
+	}
+      else
+	{
+	  icalproperty_set_completed (prop, completed_time);
+	}
+    }
+  else if (prop)
+    {
+      icalcomponent_remove_property (ical, prop);
+    }
+
+  /* Percent complete */
+  prop = icalcomponent_get_first_property (ical,
+					   ICAL_PERCENTCOMPLETE_PROPERTY);
+  if (!prop)
+    {
+      icalcomponent_add_property (ical,
+				  icalproperty_new_percentcomplete (percent_complete));
+    }
+  else
+    {
+      icalproperty_set_percentcomplete (prop, percent_complete);
+    }
+
+  /* Status */
+  status = task_completed ? ICAL_STATUS_COMPLETED : ICAL_STATUS_NEEDSACTION;
+  prop = icalcomponent_get_first_property (ical, ICAL_STATUS_PROPERTY);
+  if (prop)
+    {
+      icalproperty_set_status (prop, status);
+    }
+  else
+    {
+      icalcomponent_add_property (ical,
+				  icalproperty_new_status (status));
+    }
+
+  e_cal_modify_object (esource, ical, CALOBJ_MOD_ALL, NULL);
+}
diff --git a/src/calendar-client/calendar-client.h b/src/calendar-client/calendar-client.h
new file mode 100644
index 0000000..3ae3b2f
--- /dev/null
+++ b/src/calendar-client/calendar-client.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark@skynet.ie>
+ *     William Jon McCann  <mccann@jhu.edu>
+ *     Martin Grimme  <martin@pycage.de>
+ *     Christian Kellner  <gicmo@xatom.net>
+ */
+
+#ifndef __CALENDAR_CLIENT_H__
+#define __CALENDAR_CLIENT_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef enum
+{
+  CALENDAR_EVENT_APPOINTMENT = 1 << 0,
+  CALENDAR_EVENT_TASK        = 1 << 1,
+  CALENDAR_EVENT_ALL         = (1 << 2) - 1
+} CalendarEventType;
+
+#define CALENDAR_TYPE_CLIENT        (calendar_client_get_type ())
+#define CALENDAR_CLIENT(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_CLIENT, CalendarClient))
+#define CALENDAR_CLIENT_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_CLIENT, CalendarClientClass))
+#define CALENDAR_IS_CLIENT(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_CLIENT))
+#define CALENDAR_IS_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_CLIENT))
+#define CALENDAR_CLIENT_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_CLIENT, CalendarClientClass))
+
+typedef struct _CalendarClient        CalendarClient;
+typedef struct _CalendarClientClass   CalendarClientClass;
+typedef struct _CalendarClientPrivate CalendarClientPrivate;
+
+struct _CalendarClient
+{
+  GObject                parent;
+  CalendarClientPrivate *priv;
+};
+
+struct _CalendarClientClass
+{
+  GObjectClass    parent_class;
+
+  void         (* appointments_changed) (CalendarClient *client);
+  void         (* tasks_changed)        (CalendarClient *client);
+};
+
+
+typedef struct
+{
+  time_t start_time;
+  time_t end_time;
+} CalendarOccurrence;
+
+typedef struct
+{
+  char   *uid;
+  char   *rid;
+  char   *uri;
+  char   *summary;
+  char   *description;
+  char   *color_string;
+  time_t  start_time;
+  time_t  end_time;
+  guint   is_all_day : 1;
+
+  /* Only used internally */
+  GSList *occurrences;
+} CalendarAppointment;
+
+typedef struct
+{
+  char  *uid;
+  char  *summary;
+  char  *description;
+  char  *color_string;
+  char  *url;
+  time_t start_time;
+  time_t due_time;
+  guint  percent_complete;
+  time_t completed_time;
+  int    priority;
+} CalendarTask;
+
+typedef struct
+{
+  union
+  {
+    CalendarAppointment appointment;
+    CalendarTask        task;
+  } event;
+  CalendarEventType type;
+} CalendarEvent;
+
+#define CALENDAR_EVENT(e)       ((CalendarEvent *)(e))
+#define CALENDAR_APPOINTMENT(e) ((CalendarAppointment *)(e))
+#define CALENDAR_TASK(e)        ((CalendarTask *)(e))
+
+typedef void (* CalendarDayIter) (CalendarClient *client,
+				  guint           day,
+				  gpointer        user_data);
+
+
+GType           calendar_client_get_type                (void) G_GNUC_CONST;
+CalendarClient *calendar_client_new                     (void);
+
+void            calendar_client_get_date                (CalendarClient      *client,
+							 guint               *year,
+                                                         guint               *month,
+							 guint               *day);
+void            calendar_client_select_month            (CalendarClient      *client,
+							 guint                month,
+							 guint                year);
+void            calendar_client_select_day              (CalendarClient      *client,
+							 guint                day);
+
+GSList         *calendar_client_get_events              (CalendarClient      *client,
+							 CalendarEventType    event_mask);
+void            calendar_client_foreach_appointment_day (CalendarClient      *client,
+							 CalendarDayIter      iter_func,
+							 gpointer             user_data);
+
+void            calendar_client_set_task_completed       (CalendarClient     *client,
+							  char               *task_uid,
+							  gboolean            task_completed,
+							  guint               percent_complete);
+
+void calendar_event_free (CalendarEvent *event);
+
+G_END_DECLS
+
+#endif /* __CALENDAR_CLIENT_H__ */
diff --git a/src/calendar-client/calendar-debug.h b/src/calendar-client/calendar-debug.h
new file mode 100644
index 0000000..61ad156
--- /dev/null
+++ b/src/calendar-client/calendar-debug.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark@skynet.ie>
+ */
+
+#ifndef __CALENDAR_DEBUG_H__
+#define __CALENDAR_DEBUG_H__
+
+#include <glib.h>
+
+G_BEGIN_DECLS
+
+#ifdef CALENDAR_ENABLE_DEBUG
+
+#include <stdio.h>
+
+#ifdef G_HAVE_ISO_VARARGS
+#  define dprintf(...) fprintf (stderr, __VA_ARGS__);
+#elif defined(G_HAVE_GNUC_VARARGS)
+#  define dprintf(args...) fprintf (stderr, args);
+#endif
+
+#else /* if !defined (CALENDAR_DEBUG) */
+
+#ifdef G_HAVE_ISO_VARARGS
+#  define dprintf(...)
+#elif defined(G_HAVE_GNUC_VARARGS)
+#  define dprintf(args...)
+#endif
+
+#endif /* CALENDAR_ENABLE_DEBUG */
+
+G_END_DECLS
+
+#endif /* __CALENDAR_DEBUG_H__ */
diff --git a/src/calendar-client/calendar-sources.c b/src/calendar-client/calendar-sources.c
new file mode 100644
index 0000000..fa1fcac
--- /dev/null
+++ b/src/calendar-client/calendar-sources.c
@@ -0,0 +1,658 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark@skynet.ie>
+ *     William Jon McCann  <mccann@jhu.edu>
+ *     Martin Grimme  <martin@pycage.de>
+ *     Christian Kellner  <gicmo@xatom.net>
+ */
+
+#include <config.h>
+
+#include "calendar-sources.h"
+
+#include <libintl.h>
+#include <string.h>
+#include <gconf/gconf-client.h>
+#define HANDLE_LIBICAL_MEMORY
+#include <libecal/e-cal.h>
+#include <libedataserver/e-source-list.h>
+#include <libedataserverui/e-passwords.h>
+
+#undef CALENDAR_ENABLE_DEBUG
+#include "calendar-debug.h"
+
+#ifndef _
+#define _(x) gettext(x)
+#endif
+
+#ifndef N_
+#define N_(x) x
+#endif
+
+#define CALENDAR_SOURCES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesPrivate))
+
+#define CALENDAR_SOURCES_EVO_DIR                          "/apps/evolution"
+#define CALENDAR_SOURCES_APPOINTMENT_SOURCES_KEY          CALENDAR_SOURCES_EVO_DIR "/calendar/sources"
+#define CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR CALENDAR_SOURCES_EVO_DIR "/calendar/display"
+#define CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_KEY CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR "/selected_calendars"
+#define CALENDAR_SOURCES_TASK_SOURCES_KEY                 CALENDAR_SOURCES_EVO_DIR "/tasks/sources"
+#define CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR        CALENDAR_SOURCES_EVO_DIR "/calendar/tasks"
+#define CALENDAR_SOURCES_SELECTED_TASK_SOURCES_KEY        CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR "/selected_tasks"
+
+typedef struct _CalendarSourceData CalendarSourceData;
+
+struct _CalendarSourceData
+{
+  ECalSourceType   source_type;
+  CalendarSources *sources;
+  guint            changed_signal;
+
+  GSList          *clients;
+  GSList          *selected_sources;
+  ESourceList     *esource_list;
+
+  guint            selected_sources_listener;
+  char            *selected_sources_dir;
+
+  guint            timeout_id;
+
+  guint            loaded : 1;
+};
+
+struct _CalendarSourcesPrivate
+{
+  CalendarSourceData  appointment_sources;
+  CalendarSourceData  task_sources;
+
+  GConfClient        *gconf_client;
+};
+
+static void calendar_sources_class_init (CalendarSourcesClass *klass);
+static void calendar_sources_init       (CalendarSources      *sources);
+static void calendar_sources_finalize   (GObject             *object);
+
+static void backend_died_cb (ECal *client, CalendarSourceData *source_data);
+static void calendar_sources_esource_list_changed (ESourceList        *source_list,
+                                                   CalendarSourceData *source_data);
+
+enum
+{
+  APPOINTMENT_SOURCES_CHANGED,
+  TASK_SOURCES_CHANGED,
+  LAST_SIGNAL
+};
+static guint signals [LAST_SIGNAL] = { 0, };
+
+static GObjectClass    *parent_class = NULL;
+static CalendarSources *calendar_sources_singleton = NULL;
+
+GType
+calendar_sources_get_type (void)
+{
+  static GType sources_type = 0;
+  
+  if (!sources_type)
+    {
+      static const GTypeInfo sources_info =
+      {
+	sizeof (CalendarSourcesClass),
+	NULL,		/* base_init */
+	NULL,		/* base_finalize */
+	(GClassInitFunc) calendar_sources_class_init,
+	NULL,           /* class_finalize */
+	NULL,		/* class_data */
+	sizeof (CalendarSources),
+	0,		/* n_preallocs */
+	(GInstanceInitFunc) calendar_sources_init,
+      };
+      
+      sources_type = g_type_register_static (G_TYPE_OBJECT,
+					     "CalendarSources",
+					     &sources_info, 0);
+    }
+  
+  return sources_type;
+}
+
+static void
+calendar_sources_class_init (CalendarSourcesClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->finalize = calendar_sources_finalize;
+
+  g_type_class_add_private (klass, sizeof (CalendarSourcesPrivate));
+
+  signals [APPOINTMENT_SOURCES_CHANGED] =
+    g_signal_new ("appointment-sources-changed",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (CalendarSourcesClass,
+				   appointment_sources_changed),
+		  NULL,
+		  NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE,
+		  0);
+
+  signals [TASK_SOURCES_CHANGED] =
+    g_signal_new ("task-sources-changed",
+		  G_TYPE_FROM_CLASS (gobject_class),
+		  G_SIGNAL_RUN_LAST,
+		  G_STRUCT_OFFSET (CalendarSourcesClass,
+				   task_sources_changed),
+		  NULL,
+		  NULL,
+		  g_cclosure_marshal_VOID__VOID,
+		  G_TYPE_NONE,
+		  0);
+}
+
+static void
+calendar_sources_init (CalendarSources *sources)
+{
+  sources->priv = CALENDAR_SOURCES_GET_PRIVATE (sources);
+
+  sources->priv->appointment_sources.source_type    = E_CAL_SOURCE_TYPE_EVENT;
+  sources->priv->appointment_sources.sources        = sources;
+  sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED];
+  sources->priv->appointment_sources.timeout_id     = 0;
+
+  sources->priv->task_sources.source_type    = E_CAL_SOURCE_TYPE_TODO;
+  sources->priv->task_sources.sources        = sources;
+  sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED];
+  sources->priv->task_sources.timeout_id     = 0;
+
+  sources->priv->gconf_client = gconf_client_get_default ();
+}
+
+static void
+calendar_sources_finalize_source_data (CalendarSources    *sources,
+				       CalendarSourceData *source_data)
+{
+  if (source_data->loaded)
+    {
+      GSList *l;
+
+      if (source_data->selected_sources_dir)
+	{
+	  gconf_client_remove_dir (sources->priv->gconf_client,
+				   source_data->selected_sources_dir,
+				   NULL);
+
+	  g_free (source_data->selected_sources_dir);
+	  source_data->selected_sources_dir = NULL;
+	}
+
+      if (source_data->selected_sources_listener)
+	{
+	  gconf_client_notify_remove (sources->priv->gconf_client,
+				      source_data->selected_sources_listener);
+	  source_data->selected_sources_listener = 0;
+	}
+
+      for (l = source_data->clients; l; l = l->next)
+        {
+          g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
+                                                G_CALLBACK (backend_died_cb),
+                                                source_data);
+          g_object_unref (l->data);
+        }
+      g_slist_free (source_data->clients);
+      source_data->clients = NULL;
+
+      if (source_data->esource_list)
+        {
+          g_signal_handlers_disconnect_by_func (source_data->esource_list,
+                                                G_CALLBACK (calendar_sources_esource_list_changed),
+                                                source_data);
+          g_object_unref (source_data->esource_list);
+	}
+      source_data->esource_list = NULL;
+
+      for (l = source_data->selected_sources; l; l = l->next)
+	g_free (l->data);
+      g_slist_free (source_data->selected_sources);
+      source_data->selected_sources = NULL;
+
+      if (source_data->timeout_id != 0)
+        {
+          g_source_remove (source_data->timeout_id);
+          source_data->timeout_id = 0;
+        }
+
+      source_data->loaded = FALSE;
+    }
+}
+
+static void
+calendar_sources_finalize (GObject *object)
+{
+  CalendarSources *sources = CALENDAR_SOURCES (object);
+
+  calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources);
+  calendar_sources_finalize_source_data (sources, &sources->priv->task_sources);
+
+  if (sources->priv->gconf_client)
+    g_object_unref (sources->priv->gconf_client);
+  sources->priv->gconf_client = NULL;
+
+  if (G_OBJECT_CLASS (parent_class)->finalize)
+    G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+CalendarSources *
+calendar_sources_get (void)
+{
+  gpointer singleton_location = &calendar_sources_singleton;
+
+  if (calendar_sources_singleton)
+    return g_object_ref (calendar_sources_singleton);
+
+  calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
+  g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
+			     singleton_location);
+
+  return calendar_sources_singleton;
+}
+
+static gboolean
+is_source_selected (ESource *esource,
+		    GSList  *selected_sources)
+{
+  const char *uid;
+  GSList     *l;
+
+  uid = e_source_peek_uid (esource);
+
+  for (l = selected_sources; l; l = l->next)
+    {
+      const char *source = l->data;
+
+      if (!strcmp (source, uid))
+	return TRUE;
+    }
+
+  return FALSE;
+}
+
+static char *
+auth_func_cb (ECal       *ecal,
+	      const char *prompt,
+	      const char *key,
+	      gpointer    user_data)
+{
+	ESource *source;
+	const gchar *auth_domain;
+	const gchar *component_name;
+
+	source = e_cal_get_source (ecal);
+	auth_domain = e_source_get_property (source, "auth-domain");
+	component_name = auth_domain ? auth_domain : "Calendar";
+
+	return e_passwords_get_password (component_name, key);
+}
+
+/* The clients are just created here but not loaded */
+static ECal *
+get_ecal_from_source (ESource        *esource,
+		      ECalSourceType  source_type,
+		      GSList         *existing_clients)
+{
+  ECal *retval;
+
+  if (existing_clients)
+    {
+      GSList *l;
+
+      for (l = existing_clients; l; l = l->next)
+	{
+	  ECal *client = E_CAL (l->data);
+
+	  if (e_source_equal (esource, e_cal_get_source (client)))
+	    {
+	      dprintf ("        load_esource: found existing source ... returning that\n");
+
+	      return g_object_ref (client);
+	    }
+	}
+    }
+
+  retval = e_cal_new (esource, source_type);
+  if (!retval)
+    {
+      g_warning ("Could not load source '%s' from '%s'\n",
+		 e_source_peek_name (esource),
+		 e_source_peek_relative_uri (esource));
+      return NULL;
+    }
+
+  e_cal_set_auth_func (retval, auth_func_cb, NULL);
+
+  return retval;
+}
+
+/* - Order doesn't matter
+ * - Can just compare object pointers since we
+ *   re-use client connections
+ */
+static gboolean
+compare_ecal_lists (GSList *a,
+		    GSList *b)
+{
+  GSList *l;
+
+  if (g_slist_length (a) != g_slist_length (b))
+    return FALSE;
+
+  for (l = a; l; l = l->next)
+    {
+      if (!g_slist_find (b, l->data))
+	return FALSE;
+    }
+
+  return TRUE;
+}
+
+static inline void
+debug_dump_selected_sources (GSList *selected_sources)
+{
+#ifdef CALENDAR_ENABLE_DEBUG
+  GSList *l;
+
+  dprintf ("Selected sources:\n");
+  for (l = selected_sources; l; l = l->next)
+    {
+      char *source = l->data;
+
+      dprintf ("  %s\n", source);
+    }
+  dprintf ("\n");
+#endif
+}
+
+static inline void
+debug_dump_ecal_list (GSList *ecal_list)
+{
+#ifdef CALENDAR_ENABLE_DEBUG
+  GSList *l;
+
+  dprintf ("Loaded clients:\n");
+  for (l = ecal_list; l; l = l->next)
+    {
+      ECal    *client = l->data;
+      ESource *source = e_cal_get_source (client);
+
+      dprintf ("  %s %s %s\n",
+	       e_source_peek_uid (source),
+	       e_source_peek_name (source),
+	       e_cal_get_uri (client));
+    }
+#endif
+}
+
+static void
+calendar_sources_load_esource_list (CalendarSourceData *source_data);
+
+static gboolean
+backend_restart (gpointer data)
+{
+  CalendarSourceData *source_data = data;
+
+  calendar_sources_load_esource_list (source_data);
+
+  source_data->timeout_id = 0;
+    
+  return FALSE;
+}
+
+static void
+backend_died_cb (ECal *client, CalendarSourceData *source_data)
+{
+  const char *uristr;
+
+  source_data->clients = g_slist_remove (source_data->clients, client);
+  if (g_slist_length (source_data->clients) < 1) 
+    {
+      g_slist_free (source_data->clients);
+      source_data->clients = NULL;
+    }
+  uristr = e_cal_get_uri (client);
+  g_warning ("The calendar backend for %s has crashed.", uristr);
+
+  if (source_data->timeout_id != 0)
+    {
+      g_source_remove (source_data->timeout_id);
+      source_data->timeout_id = 0;
+    }
+
+  source_data->timeout_id = g_timeout_add_seconds (2, backend_restart,
+		  				   source_data);
+}
+
+static void
+calendar_sources_load_esource_list (CalendarSourceData *source_data)
+{
+  GSList  *clients = NULL;
+  GSList  *groups, *l;
+  gboolean emit_signal = FALSE;
+
+  g_return_if_fail (source_data->esource_list != NULL);
+
+  debug_dump_selected_sources (source_data->selected_sources);
+
+  dprintf ("Source groups:\n");
+  groups = e_source_list_peek_groups (source_data->esource_list);
+  for (l = groups; l; l = l->next)
+    {
+      GSList *esources, *s;
+
+      dprintf ("  %s\n", e_source_group_peek_uid (l->data));
+      dprintf ("    sources:\n");
+
+      esources = e_source_group_peek_sources (l->data);
+      for (s = esources; s; s = s->next)
+	{
+	  ESource *esource = E_SOURCE (s->data);
+	  ECal    *client;
+
+	  dprintf ("      type = '%s' uid = '%s', name = '%s', relative uri = '%s': \n",
+                   source_data->source_type == E_CAL_SOURCE_TYPE_EVENT ? "appointment" : "task",
+		   e_source_peek_uid (esource),
+		   e_source_peek_name (esource),
+		   e_source_peek_relative_uri (esource));
+
+	  if (is_source_selected (esource, source_data->selected_sources) &&
+	      (client = get_ecal_from_source (esource, source_data->source_type, source_data->clients)))
+	    {
+	      clients = g_slist_prepend (clients, client);
+	    }
+	}
+    }
+  dprintf ("\n");
+
+  if (source_data->loaded && 
+      !compare_ecal_lists (source_data->clients, clients))
+    emit_signal = TRUE;
+
+  for (l = source_data->clients; l; l = l->next)
+    {
+      g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
+                                            G_CALLBACK (backend_died_cb),
+                                            source_data);
+
+      g_object_unref (l->data);
+    }
+  g_slist_free (source_data->clients);
+  source_data->clients = g_slist_reverse (clients);
+
+  /* connect to backend_died after we disconnected the previous signal
+   * handlers. If we do it before, we'll lose some handlers (for clients that
+   * were already there before) */
+  for (l = source_data->clients; l; l = l->next)
+    {
+      g_signal_connect (G_OBJECT (l->data), "backend_died",
+                        G_CALLBACK (backend_died_cb), source_data);
+    }
+
+  if (emit_signal) 
+    {
+      dprintf ("Emitting %s-sources-changed signal\n",
+	       source_data->source_type == E_CAL_SOURCE_TYPE_EVENT ? "appointment" : "task");
+      g_signal_emit (source_data->sources, source_data->changed_signal, 0);
+    }
+
+  debug_dump_ecal_list (source_data->clients);
+}
+
+static void
+calendar_sources_esource_list_changed (ESourceList        *source_list,
+				       CalendarSourceData *source_data)
+				       
+{
+  dprintf ("ESourceList changed, reloading\n");
+
+  calendar_sources_load_esource_list (source_data);
+}
+
+static void
+calendar_sources_selected_sources_notify (GConfClient        *client,
+					  guint               cnx_id,
+					  GConfEntry         *entry,
+					  CalendarSourceData *source_data)
+{
+  GSList *l;
+
+  if (!entry->value ||
+      entry->value->type != GCONF_VALUE_LIST ||
+      gconf_value_get_list_type (entry->value) != GCONF_VALUE_STRING)
+    return;
+
+  dprintf ("Selected sources key (%s) changed, reloading\n", entry->key);
+
+  for (l = source_data->selected_sources; l; l = l->next)
+    g_free (l->data);
+  source_data->selected_sources = NULL;
+
+  for (l = gconf_value_get_list (entry->value); l; l = l->next)
+    {
+      const char *source = gconf_value_get_string (l->data);
+
+      source_data->selected_sources = 
+	g_slist_prepend (source_data->selected_sources,
+			 g_strdup (source));
+    }
+  source_data->selected_sources =
+    g_slist_reverse (source_data->selected_sources);
+
+  calendar_sources_load_esource_list (source_data);
+}
+
+static void
+calendar_sources_load_sources (CalendarSources    *sources,
+			       CalendarSourceData *source_data,
+			       const char         *sources_key,
+			       const char         *selected_sources_key,
+			       const char         *selected_sources_dir)
+{
+  GConfClient *gconf_client;
+  GError      *error;
+
+  dprintf ("---------------------------\n");
+  dprintf ("Loading sources:\n");
+  dprintf ("  sources_key: %s\n", sources_key);
+  dprintf ("  selected_sources_key: %s\n", selected_sources_key);
+  dprintf ("  selected_sources_dir: %s\n", selected_sources_dir);
+
+  gconf_client = sources->priv->gconf_client;
+
+  error = NULL;
+  source_data->selected_sources = gconf_client_get_list (gconf_client,
+							 selected_sources_key,
+							 GCONF_VALUE_STRING,
+							 &error);
+  if (error)
+    {
+      g_warning ("Failed to get selected sources from '%s': %s\n",
+		 selected_sources_key,
+		 error->message);
+      g_error_free (error);
+      return;
+    }
+
+  gconf_client_add_dir (gconf_client,
+			selected_sources_dir,
+			GCONF_CLIENT_PRELOAD_NONE,
+			NULL);
+  source_data->selected_sources_dir = g_strdup (selected_sources_dir);
+
+  source_data->selected_sources_listener =
+    gconf_client_notify_add (gconf_client,
+			     selected_sources_dir,
+			     (GConfClientNotifyFunc) calendar_sources_selected_sources_notify,
+			     source_data, NULL, NULL);
+
+  source_data->esource_list = e_source_list_new_for_gconf (gconf_client, sources_key);
+  g_signal_connect (source_data->esource_list, "changed",
+		    G_CALLBACK (calendar_sources_esource_list_changed),
+		    source_data);
+
+  calendar_sources_load_esource_list (source_data);
+
+  source_data->loaded = TRUE;
+
+  dprintf ("---------------------------\n");
+}
+
+GSList *
+calendar_sources_get_appointment_sources (CalendarSources *sources)
+{
+  g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
+
+  if (!sources->priv->appointment_sources.loaded)
+    {
+      calendar_sources_load_sources (sources,
+				     &sources->priv->appointment_sources,
+				     CALENDAR_SOURCES_APPOINTMENT_SOURCES_KEY,
+				     CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_KEY,
+				     CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR);
+    }
+  
+  return sources->priv->appointment_sources.clients;
+}
+
+GSList *
+calendar_sources_get_task_sources (CalendarSources *sources)
+{
+  g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
+
+  if (!sources->priv->task_sources.loaded)
+    {
+      calendar_sources_load_sources (sources,
+				     &sources->priv->task_sources,
+				     CALENDAR_SOURCES_TASK_SOURCES_KEY,
+				     CALENDAR_SOURCES_SELECTED_TASK_SOURCES_KEY,
+				     CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR);
+    }
+
+  return sources->priv->task_sources.clients;
+}
diff --git a/src/calendar-client/calendar-sources.h b/src/calendar-client/calendar-sources.h
new file mode 100644
index 0000000..52741ab
--- /dev/null
+++ b/src/calendar-client/calendar-sources.h
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2004 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Authors:
+ *     Mark McLoughlin  <mark@skynet.ie>
+ *     William Jon McCann  <mccann@jhu.edu>
+ *     Martin Grimme  <martin@pycage.de>
+ *     Christian Kellner  <gicmo@xatom.net>
+ */
+
+#ifndef __CALENDAR_SOURCES_H__
+#define __CALENDAR_SOURCES_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define CALENDAR_TYPE_SOURCES        (calendar_sources_get_type ())
+#define CALENDAR_SOURCES(o)          (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_SOURCES, CalendarSources))
+#define CALENDAR_SOURCES_CLASS(k)    (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_SOURCES, CalendarSourcesClass))
+#define CALENDAR_IS_SOURCES(o)       (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_SOURCES))
+#define CALENDAR_IS_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_SOURCES))
+#define CALENDAR_SOURCES_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesClass))
+
+typedef struct _CalendarSources        CalendarSources;
+typedef struct _CalendarSourcesClass   CalendarSourcesClass;
+typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate;
+
+struct _CalendarSources
+{
+  GObject                 parent;
+  CalendarSourcesPrivate *priv;
+};
+
+struct _CalendarSourcesClass
+{
+  GObjectClass    parent_class;
+
+  void         (* appointment_sources_changed) (CalendarSources *sources);
+  void         (* task_sources_changed)        (CalendarSources *sources);
+};
+
+
+GType            calendar_sources_get_type                (void) G_GNUC_CONST;
+CalendarSources *calendar_sources_get                     (void);
+GSList          *calendar_sources_get_appointment_sources (CalendarSources *sources);
+GSList          *calendar_sources_get_task_sources        (CalendarSources *sources);
+
+G_END_DECLS
+
+#endif /* __CALENDAR_SOURCES_H__ */
diff --git a/src/shell-evolution-event-source.c b/src/shell-evolution-event-source.c
new file mode 100644
index 0000000..3c080e2
--- /dev/null
+++ b/src/shell-evolution-event-source.c
@@ -0,0 +1,245 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+
+#include "config.h"
+
+#include "calendar-client/calendar-client.h"
+#include "shell-evolution-event-source.h"
+
+
+struct _ShellEvolutionEventSourceClass
+{
+  GObjectClass parent_class;
+};
+
+struct _ShellEvolutionEventSource {
+  GObject parent;
+  CalendarClient *client;
+  /* The month that we are currently requesting events from */
+  gint req_year;
+  gint req_mon; /* starts at 1, not zero */
+};
+
+/* Signals */
+enum
+{
+  CHANGED_SIGNAL,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+G_DEFINE_TYPE (ShellEvolutionEventSource, shell_evolution_event_source, G_TYPE_OBJECT);
+
+static void
+on_tasks_changed (CalendarClient *client,
+                  gpointer        user_data)
+{
+  ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (user_data);
+  /* g_print ("on tasks changed\n"); */
+  g_signal_emit (source, signals[CHANGED_SIGNAL], 0);
+}
+
+static void
+on_appointments_changed (CalendarClient *client,
+                         gpointer        user_data)
+{
+  ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (user_data);
+  /* g_print ("on appointments changed\n"); */
+  g_signal_emit (source, signals[CHANGED_SIGNAL], 0);
+}
+
+static void
+shell_evolution_event_source_init (ShellEvolutionEventSource *source)
+{
+  source->client = calendar_client_new ();
+  g_signal_connect (source->client,
+                    "tasks-changed",
+                    G_CALLBACK (on_tasks_changed),
+                    source);
+  g_signal_connect (source->client,
+                    "appointments-changed",
+                    G_CALLBACK (on_appointments_changed),
+                    source);
+}
+
+static void
+shell_evolution_event_source_finalize (GObject *object)
+{
+  ShellEvolutionEventSource *source = SHELL_EVOLUTION_EVENT_SOURCE (object);
+  g_object_unref (source->client);
+  G_OBJECT_CLASS (shell_evolution_event_source_parent_class)->finalize (object);
+}
+
+static void
+shell_evolution_event_source_class_init (ShellEvolutionEventSourceClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+  gobject_class->finalize = shell_evolution_event_source_finalize;
+
+  signals[CHANGED_SIGNAL] =
+    g_signal_new ("changed",
+                  G_TYPE_FROM_CLASS (klass),
+                  G_SIGNAL_RUN_LAST,
+                  0,
+                  NULL, NULL,
+                  g_cclosure_marshal_VOID__VOID,
+                  G_TYPE_NONE, 0);
+
+}
+
+ShellEvolutionEventSource *
+shell_evolution_event_source_new (void)
+{
+  return SHELL_EVOLUTION_EVENT_SOURCE (g_object_new (SHELL_TYPE_EVOLUTION_EVENT_SOURCE, NULL));
+}
+
+void
+shell_evolution_event_source_request_range (ShellEvolutionEventSource *source,
+                                            gint64                     msec_begin,
+                                            gint64                     msec_end)
+{
+  GDateTime *middle;
+
+  /* The CalendarClient type is a convenience wrapper on top of
+   * Evolution Data Server. It is based on the assumption that only
+   * a single month is shown at a time.
+   *
+   * To avoid reimplemting all the work already done in CalendarClient
+   * we make the same assumption. This means that we only show events
+   * in the month that is in the middle of @msec_begin and
+   * @msec_end. Since the Shell displays a month at a time (plus the
+   * days before and after) it works out just fine.
+   */
+
+  middle = g_date_time_new_from_unix_utc ((msec_begin + msec_end) / 2 / 1000);
+  g_date_time_get_ymd (middle, &source->req_year, &source->req_mon, NULL);
+  g_date_time_unref (middle);
+  calendar_client_select_month (source->client, source->req_mon - 1, source->req_year);
+}
+
+static gint
+event_cmp (gconstpointer a,
+           gconstpointer b)
+{
+  const ShellEvolutionEvent *ea;
+  const ShellEvolutionEvent *eb;
+
+  ea = a;
+  eb = b;
+  if (ea->msec_begin < eb->msec_begin)
+    return -1;
+  else if (ea->msec_begin > eb->msec_begin)
+    return 1;
+  else
+    return 0;
+}
+
+/**
+ * shell_evolution_event_source_get_events:
+ * @source: A #ShellEvolutionEventSource.
+ * @msec_begin: Start date (milli-seconds since Epoch).
+ * @msec_end: End date (milli-seconds since Epoch).
+ *
+ * Gets all events that occur between @msec_begin and @msec_end.
+ *
+ * Returns: (element-type ShellEvolutionEvent) (transfer full): List of events.
+ */
+GList *
+shell_evolution_event_source_get_events  (ShellEvolutionEventSource *source,
+                                          gint64                     msec_begin,
+                                          gint64                     msec_end)
+{
+  GList *result;
+  GDateTime *cur_date;
+  GDateTime *begin_date;
+  GDateTime *end_date;
+
+  g_return_val_if_fail (msec_begin <= msec_end, NULL);
+
+  result = NULL;
+
+  begin_date = g_date_time_new_from_unix_utc (msec_begin / 1000);
+  end_date = g_date_time_new_from_unix_utc (msec_end / 1000);
+  cur_date = g_date_time_ref (begin_date);
+  do
+    {
+      gint year, mon, day;
+      GDateTime *next_date;
+
+      g_date_time_get_ymd (cur_date, &year, &mon, &day);
+      /* g_print ("y=%04d m=%02d d=%02d: ", year, mon, day); */
+
+      /* Silently drop events not in range (see comment in
+       * shell_evolution_event_source_request_range() above)
+       */
+      if (!(year == source->req_year && mon == source->req_mon))
+        {
+          /* g_print ("skipping day\n"); */
+        }
+      else
+        {
+          GSList *events;
+          GSList *l;
+          calendar_client_select_day (source->client, day);
+          events = calendar_client_get_events (source->client, CALENDAR_EVENT_APPOINTMENT);
+          /* g_print ("num_events: %d\n", g_slist_length (events)); */
+          for (l = events; l; l = l->next)
+            {
+              CalendarAppointment *appointment = l->data;
+              ShellEvolutionEvent *event;
+              event = shell_evolution_event_new (appointment->summary,
+                                                 appointment->is_all_day,
+                                                 appointment->start_time * G_GINT64_CONSTANT (1000));
+              result = g_list_prepend (result, event);
+            }
+          g_slist_foreach (events, (GFunc) calendar_event_free, NULL);
+          g_slist_free (events);
+        }
+
+      next_date = g_date_time_add_days (cur_date, 1);
+      g_date_time_unref (cur_date);
+      cur_date = next_date;
+    }
+  while (g_date_time_difference (end_date, cur_date) > 0);
+  g_date_time_unref (begin_date);
+  g_date_time_unref (end_date);
+
+  result = g_list_sort (result, event_cmp);
+
+  return result;
+}
+
+G_DEFINE_BOXED_TYPE (ShellEvolutionEvent,
+                     shell_evolution_event,
+                     shell_evolution_event_copy,
+                     shell_evolution_event_free);
+
+void
+shell_evolution_event_free (ShellEvolutionEvent *event)
+{
+  g_free (event->summary);
+  g_free (event);
+}
+
+ShellEvolutionEvent *
+shell_evolution_event_copy (ShellEvolutionEvent *event)
+{
+  ShellEvolutionEvent *copy;
+  copy = g_memdup (event, sizeof (ShellEvolutionEvent));
+  copy->summary = g_strdup (event->summary);
+  return copy;
+}
+
+ShellEvolutionEvent *
+shell_evolution_event_new (const gchar *summary,
+                           gboolean     all_day,
+                           gint64       msec_begin)
+{
+  ShellEvolutionEvent *event;
+  event = g_new0 (ShellEvolutionEvent, 1);
+  event->summary = g_strdup (summary);
+  event->all_day = all_day;
+  event->msec_begin = msec_begin;
+  return event;
+}
diff --git a/src/shell-evolution-event-source.h b/src/shell-evolution-event-source.h
new file mode 100644
index 0000000..4866b45
--- /dev/null
+++ b/src/shell-evolution-event-source.h
@@ -0,0 +1,45 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
+#ifndef __SHELL_EVOLUTION_EVENT_SOURCE_H__
+#define __SHELL_EVOLUTION_EVENT_SOURCE_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _ShellEvolutionEvent            ShellEvolutionEvent;
+
+struct _ShellEvolutionEvent
+{
+  gchar    *summary;
+  gboolean  all_day;
+  gint64    msec_begin;
+};
+
+GType                shell_evolution_event_get_type (void) G_GNUC_CONST;
+ShellEvolutionEvent *shell_evolution_event_new      (const gchar *summary,
+                                                     gboolean     all_day,
+                                                     gint64       msec_begin);
+ShellEvolutionEvent *shell_evolution_event_copy     (ShellEvolutionEvent *event);
+void                 shell_evolution_event_free     (ShellEvolutionEvent *event);
+
+typedef struct _ShellEvolutionEventSource      ShellEvolutionEventSource;
+typedef struct _ShellEvolutionEventSourceClass ShellEvolutionEventSourceClass;
+
+#define SHELL_TYPE_EVOLUTION_EVENT_SOURCE              (shell_evolution_event_source_get_type ())
+#define SHELL_EVOLUTION_EVENT_SOURCE(object)           (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSource))
+#define SHELL_EVOLUTION_EVENT_SOURCE_CLASS(klass)      (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSourceClass))
+#define SHELL_IS_EVOLUTION_EVENT_SOURCE(object)        (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_EVOLUTION_EVENT_SOURCE))
+#define SHELL_IS_EVOLUTION_EVENT_SOURCE_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_EVOLUTION_EVENT_SOURCE))
+#define SHELL_EVOLUTION_EVENT_SOURCE_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSourceClass))
+
+GType                      shell_evolution_event_source_get_type      (void) G_GNUC_CONST;
+ShellEvolutionEventSource *shell_evolution_event_source_new           (void);
+void                       shell_evolution_event_source_request_range (ShellEvolutionEventSource *source,
+                                                                       gint64                     msec_begin,
+                                                                       gint64                     msec_end);
+GList                     *shell_evolution_event_source_get_events    (ShellEvolutionEventSource *source,
+                                                                       gint64                     msec_begin,
+                                                                       gint64                     msec_end);
+G_END_DECLS
+
+#endif /* __SHELL_EVOLUTION_EVENT_SOURCE_H__ */
