diff --git a/configure.ac b/configure.ac
index d19bce0a6..2cab70d09 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 62eed7c88..2de2ff976 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/theme/calendar-arrow-left.svg b/data/theme/calendar-arrow-left.svg
new file mode 100644
index 000000000..d5d97b3c3
--- /dev/null
+++ b/data/theme/calendar-arrow-left.svg
@@ -0,0 +1,82 @@
+
+
+
+
diff --git a/data/theme/calendar-arrow-right.svg b/data/theme/calendar-arrow-right.svg
new file mode 100644
index 000000000..545da7ec5
--- /dev/null
+++ b/data/theme/calendar-arrow-right.svg
@@ -0,0 +1,82 @@
+
+
+
+
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index d6616aa14..73d2b5c9b 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,155 @@ StTooltip StLabel {
}
.calendar {
- spacing-rows: 5px;
- spacing-columns: 3px;
+ padding: .4em 1.75em;
+ spacing-rows: 0px;
+ spacing-columns: 0px;
}
-.calendar-change-month {
+.calendar-month-label {
+ color: #666666;
+ font-size: 10px;
padding: 2px;
}
-.calendar-change-month:hover {
- background: #314a6c;
- border-radius: 5px;
+.calendar-change-month-back {
+ width: 20px;
+ height: 20px;
+ background-image: url("calendar-arrow-left.svg");
+ border-radius: 4px;
+}
+.calendar-change-month-back:hover {
+ background-color: #999999;
+}
+.calendar-change-month-back:active {
+ background-color: #aaaaaa;
}
-.calendar-change-month:active {
- background: #213050;
- border-radius: 5px;
+.calendar-change-month-forward {
+ width: 20px;
+ height: 20px;
+ background-image: url("calendar-arrow-right.svg");
+ border-radius: 4px;
+}
+.calendar-change-month-forward:hover {
+ background-color: #999999;
+}
+.calendar-change-month-forward:active {
+ background-color: #aaaaaa;
}
+.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-color: rgba(128, 128, 128, .1);
}
.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 {
}
.url-highlighter {
@@ -895,10 +1024,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 3a7a2da06..21683af19 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 8f58f69f7..236c3ec67 100644
--- a/js/ui/calendar.js
+++ b/js/ui/calendar.js
@@ -4,19 +4,78 @@ 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.setSeconds(0);
+ ret.setMilliseconds(0);
+ return ret;
+}
+
+function _getEndOfDay(date) {
+ let ret = new Date(date.getTime());
+ ret.setHours(23);
+ ret.setMinutes(59);
+ ret.setSeconds(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 +102,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 +377,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 +392,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 +407,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 +424,36 @@ Calendar.prototype = {
{ row: 0, col: 0, col_span: offsetCols + 7 });
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChange));
- let [backlabel, forwardlabel] = ['<', '>'];
- 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._topBox.add(this._dateLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });
+ this._monthLabel = new St.Label({style_class: 'calendar-month-label'});
+ this._topBox.add(this._monthLabel, { 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({ style_class: 'calendar-day-base calendar-day-heading',
+ text: customDayAbbrev });
+ 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 +477,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 +515,12 @@ Calendar.prototype = {
},
_update: function() {
- this._dateLabel.text = this.date.toLocaleFormat(this._headerFormat);
+ let now = new Date();
+
+ if (_sameYear(this._selectedDate, now))
+ this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear);
+ else
+ this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat);
// Remove everything but the topBox and the weekday labels
let children = this.actor.get_children();
@@ -222,45 +528,201 @@ 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 newlySelectedDate = new Date(iterStr);
+ this.setDate(newlySelectedDate);
+ }));
+
+ let hasEvents = this._eventSource.hasEvents(iter);
+ let 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);
+
+ let tomorrowBegin = new Date(dayBegin.getTime() + 86400 * 1000);
+ let tomorrowEnd = new Date(dayEnd.getTime() + 86400 * 1000);
+ this._addPeriod(_("Tomorrow"), tomorrowBegin, tomorrowEnd, false, true);
+
+ if (dayEnd.getDay() <= 4) {
+ /* if now is Sunday through Thursday show "This week" and include events up until
+ * and including Saturday
+ */
+ let thisWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
+ let thisWeekEnd = new Date(dayEnd.getTime() + (6 - dayEnd.getDay()) * 86400 * 1000);
+ this._addPeriod(_("This week"), thisWeekBegin, thisWeekEnd, true, false);
+ } else {
+ /* otherwise it's a Friday or Saturday... show "Next week" and include events up
+ * until and including *next* Saturday
+ */
+ let nextWeekBegin = new Date(dayBegin.getTime() + 2 * 86400 * 1000);
+ let nextWeekEnd = new Date(dayEnd.getTime() + (13 - dayEnd.getDay()) * 86400 * 1000);
+ this._addPeriod(_("Next week"), nextWeekBegin, nextWeekEnd, 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 000000000..09f8dfe17
--- /dev/null
+++ b/js/ui/dateMenu.js
@@ -0,0 +1,212 @@
+/* -*- 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 stippleColor = new Clutter.Color();
+ let stippleWidth = themeNode.get_length('-stipple-width');
+ let x = Math.floor(width/2) + 0.5;
+ themeNode.lookup_color('-stipple-color', false, stippleColor);
+ 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();
+
+ 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 PopupMenu.PopupSeparatorMenuItem();
+ item.setColumnWidths(1);
+ vbox.add(item.actor, {y_align: St.Align.END, expand: true, y_fill: false});
+ 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
+
+ // 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 572709c0e..1f650fed9 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -284,10 +284,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 1b8863fb8..9b7c3b36a 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;
@@ -492,121 +493,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();
}
@@ -803,9 +689,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 */
@@ -884,10 +770,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/src/Makefile-calendar-client.am b/src/Makefile-calendar-client.am
new file mode 100644
index 000000000..cac046f57
--- /dev/null
+++ b/src/Makefile-calendar-client.am
@@ -0,0 +1,22 @@
+
+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)
+
+EXTRA_DIST += calendar-client/README
diff --git a/src/Makefile.am b/src/Makefile.am
index 5bfa8c764..a7a38d8cc 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 000000000..ad9b5e3d0
--- /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 000000000..bbdb47327
--- /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
+ * William Jon McCann
+ * Martin Grimme
+ * Christian Kellner
+ */
+
+#include
+
+#include "calendar-client.h"
+
+#include
+#include
+#define HANDLE_LIBICAL_MEMORY
+#include
+#include
+#include
+
+#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 000000000..3ae3b2fc8
--- /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
+ * William Jon McCann
+ * Martin Grimme
+ * Christian Kellner
+ */
+
+#ifndef __CALENDAR_CLIENT_H__
+#define __CALENDAR_CLIENT_H__
+
+#include
+
+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 000000000..61ad15652
--- /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
+ */
+
+#ifndef __CALENDAR_DEBUG_H__
+#define __CALENDAR_DEBUG_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#ifdef CALENDAR_ENABLE_DEBUG
+
+#include
+
+#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 000000000..fa1fcaccd
--- /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
+ * William Jon McCann
+ * Martin Grimme
+ * Christian Kellner
+ */
+
+#include
+
+#include "calendar-sources.h"
+
+#include
+#include
+#include
+#define HANDLE_LIBICAL_MEMORY
+#include
+#include
+#include
+
+#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 000000000..52741abd7
--- /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
+ * William Jon McCann
+ * Martin Grimme
+ * Christian Kellner
+ */
+
+#ifndef __CALENDAR_SOURCES_H__
+#define __CALENDAR_SOURCES_H__
+
+#include
+
+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 000000000..dd2aea91f
--- /dev/null
+++ b/src/shell-evolution-event-source.c
@@ -0,0 +1,255 @@
+/* -*- 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\n", 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;
+ gint64 start_time;
+
+ if (appointment->is_all_day)
+ {
+ start_time = g_date_time_to_unix (cur_date) * G_GINT64_CONSTANT (1000);
+ }
+ else
+ {
+ start_time = appointment->start_time * G_GINT64_CONSTANT (1000);
+ }
+ event = shell_evolution_event_new (appointment->summary,
+ appointment->is_all_day,
+ start_time);
+ 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 000000000..4866b4575
--- /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
+
+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__ */
diff --git a/tools/build/gnome-shell-build-setup.sh b/tools/build/gnome-shell-build-setup.sh
index 78e94d018..6f950529f 100755
--- a/tools/build/gnome-shell-build-setup.sh
+++ b/tools/build/gnome-shell-build-setup.sh
@@ -62,7 +62,8 @@ fi
# libxklavier, libxml2, ORBit2, pam, python, readline,
# spidermonkey ({mozilla,firefox,xulrunner}-js), startup-notification,
# xdamage, icon-naming-utils, upower, libtool-ltdl, libvorbis,
-# libgcrypt, libtasn1, libgnome-keyring, libgtop, cups
+# libgcrypt, libtasn1, libgnome-keyring, libgtop, cups,
+# evolution-data-server
#
# Non-devel packages needed by gnome-shell and its deps:
# glxinfo, gstreamer-plugins-base, gstreamer-plugins-good,
@@ -83,7 +84,7 @@ if test "x$system" = xUbuntu -o "x$system" = xDebian -o "x$system" = xLinuxMint
xulrunner-dev xserver-xephyr gnome-terminal libcroco3-dev
libgstreamer0.10-dev gstreamer0.10-plugins-base gstreamer0.10-plugins-good
libltdl-dev libvorbis-dev libxklavier-dev libgnome-keyring-dev
- libupower-glib-dev libcups2-dev
+ libupower-glib-dev libcups2-dev evolution-data-server-dev
"
if apt-cache show autopoint > /dev/null 2> /dev/null; then
@@ -121,7 +122,7 @@ if test "x$system" = xFedora ; then
startup-notification-devel xorg-x11-server-Xephyr gnome-terminal zenity
icon-naming-utils upower-devel libtool-ltdl-devel libvorbis-devel
libxklavier-devel libgcrypt-devel libtasn1-devel libtasn1-tools
- libgnome-keyring-devel libgtop2-devel cups-devel
+ libgnome-keyring-devel libgtop2-devel cups-devel evolution-data-server-devel
"
if expr $version \>= 14 > /dev/null ; then
@@ -147,7 +148,7 @@ if test "x$system" = xSUSE -o "x$system" = "xSUSE LINUX" ; then
libgtop-devel libpulse-devel libtiff-devel cups-devel libffi-devel \
orbit2-devel libwnck-devel xorg-x11-proto-devel readline-devel \
mozilla-xulrunner191-devel libcroco-devel \
- xorg-x11-devel xorg-x11 xorg-x11-server-extra \
+ xorg-x11-devel xorg-x11 xorg-x11-server-extra evolution-data-server-devel \
; do
if ! rpm -q $pkg > /dev/null 2>&1; then
reqd="$pkg $reqd"
@@ -168,7 +169,7 @@ if test "x$system" = xMandrivaLinux ; then
intltool ffi5-devel libwnck-1-devel GL-devel ORBit2-devel \
readline-devel libxulrunner-devel \
libxdamage-devel mesa-demos x11-server-xephyr zenity \
- libcroco0.6-devel \
+ libcroco0.6-devel libevolution-data-server3-devel \
; do
if ! rpm -q --whatprovides $pkg > /dev/null 2>&1; then
reqd="$pkg $reqd"