Calendar: Implement new mockup

https://bugzilla.gnome.org/show_bug.cgi?id=632109

Signed-off-by: David Zeuthen <davidz@redhat.com>
This commit is contained in:
David Zeuthen 2011-01-28 16:35:46 -05:00
parent 4c64920f45
commit 885b6ffaef
21 changed files with 4492 additions and 215 deletions

View File

@ -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`

View File

@ -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 \

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg2"
version="1.1"
inkscape:version="0.48+devel r9942 custom"
sodipodi:docname="New document 4">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="8.984481"
inkscape:cy="5.6224906"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
borderlayer="true"
inkscape:showpageshadow="false"
inkscape:window-width="930"
inkscape:window-height="681"
inkscape:window-x="1892"
inkscape:window-y="272"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid17403"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1036.3622)">
<path
sodipodi:type="star"
style="fill:#5f5f5f;fill-opacity:1;stroke:#5f5f5f;stroke-width:0.43015847;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
id="path18028"
sodipodi:sides="3"
sodipodi:cx="84.5"
sodipodi:cy="337.5"
sodipodi:r1="5"
sodipodi:r2="2.5"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 88.830127,340 80.169873,340 84.5,332.5 z"
transform="matrix(0,1.3621708,0.99186247,0,-325.48222,929.32667)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg2"
version="1.1"
inkscape:version="0.48+devel r9942 custom"
sodipodi:docname="arrow-left.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1"
inkscape:cx="7.7366092"
inkscape:cy="6.4536271"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
borderlayer="true"
inkscape:showpageshadow="false"
inkscape:window-width="930"
inkscape:window-height="681"
inkscape:window-x="1892"
inkscape:window-y="272"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid17403"
empspacing="5"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1036.3622)">
<path
sodipodi:type="star"
style="fill:#5f5f5f;fill-opacity:1;stroke:#5f5f5f;stroke-width:0.43015847;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;display:inline"
id="path18028"
sodipodi:sides="3"
sodipodi:cx="84.5"
sodipodi:cy="337.5"
sodipodi:r1="5"
sodipodi:r2="2.5"
sodipodi:arg1="0.52359878"
sodipodi:arg2="1.5707963"
inkscape:flatsided="true"
inkscape:rounded="0"
inkscape:randomized="0"
d="M 88.830127,340 80.169873,340 84.5,332.5 z"
transform="matrix(0,1.3621708,-0.99186247,0,342.48324,929.32667)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

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

View File

@ -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 \

View File

@ -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] = ['&lt;', '&gt;'];
if (St.Widget.get_default_direction () == St.TextDirection.RTL) {
[backlabel, forwardlabel] = [forwardlabel, backlabel];
}
let back = new St.Button({ label: backlabel, style_class: 'calendar-change-month' });
let back = new St.Button({ style_class: 'calendar-change-month-back' });
this._topBox.add(back);
back.connect('clicked', Lang.bind(this, this._prevMonth));
back.connect('clicked', Lang.bind(this, this._onPrevMonthButtonClicked));
this._dateLabel = new St.Label();
this._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);
}
}
};

212
js/ui/dateMenu.js Normal file
View File

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

View File

@ -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,

View File

@ -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,

View File

@ -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

View File

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

View File

@ -0,0 +1 @@
Please keep in sync with gnome-panel.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,149 @@
/*
* Copyright (C) 2004 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Authors:
* Mark McLoughlin <mark@skynet.ie>
* William Jon McCann <mccann@jhu.edu>
* Martin Grimme <martin@pycage.de>
* Christian Kellner <gicmo@xatom.net>
*/
#ifndef __CALENDAR_CLIENT_H__
#define __CALENDAR_CLIENT_H__
#include <glib-object.h>
G_BEGIN_DECLS
typedef enum
{
CALENDAR_EVENT_APPOINTMENT = 1 << 0,
CALENDAR_EVENT_TASK = 1 << 1,
CALENDAR_EVENT_ALL = (1 << 2) - 1
} CalendarEventType;
#define CALENDAR_TYPE_CLIENT (calendar_client_get_type ())
#define CALENDAR_CLIENT(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_CLIENT, CalendarClient))
#define CALENDAR_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_CLIENT, CalendarClientClass))
#define CALENDAR_IS_CLIENT(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_CLIENT))
#define CALENDAR_IS_CLIENT_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_CLIENT))
#define CALENDAR_CLIENT_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_CLIENT, CalendarClientClass))
typedef struct _CalendarClient CalendarClient;
typedef struct _CalendarClientClass CalendarClientClass;
typedef struct _CalendarClientPrivate CalendarClientPrivate;
struct _CalendarClient
{
GObject parent;
CalendarClientPrivate *priv;
};
struct _CalendarClientClass
{
GObjectClass parent_class;
void (* appointments_changed) (CalendarClient *client);
void (* tasks_changed) (CalendarClient *client);
};
typedef struct
{
time_t start_time;
time_t end_time;
} CalendarOccurrence;
typedef struct
{
char *uid;
char *rid;
char *uri;
char *summary;
char *description;
char *color_string;
time_t start_time;
time_t end_time;
guint is_all_day : 1;
/* Only used internally */
GSList *occurrences;
} CalendarAppointment;
typedef struct
{
char *uid;
char *summary;
char *description;
char *color_string;
char *url;
time_t start_time;
time_t due_time;
guint percent_complete;
time_t completed_time;
int priority;
} CalendarTask;
typedef struct
{
union
{
CalendarAppointment appointment;
CalendarTask task;
} event;
CalendarEventType type;
} CalendarEvent;
#define CALENDAR_EVENT(e) ((CalendarEvent *)(e))
#define CALENDAR_APPOINTMENT(e) ((CalendarAppointment *)(e))
#define CALENDAR_TASK(e) ((CalendarTask *)(e))
typedef void (* CalendarDayIter) (CalendarClient *client,
guint day,
gpointer user_data);
GType calendar_client_get_type (void) G_GNUC_CONST;
CalendarClient *calendar_client_new (void);
void calendar_client_get_date (CalendarClient *client,
guint *year,
guint *month,
guint *day);
void calendar_client_select_month (CalendarClient *client,
guint month,
guint year);
void calendar_client_select_day (CalendarClient *client,
guint day);
GSList *calendar_client_get_events (CalendarClient *client,
CalendarEventType event_mask);
void calendar_client_foreach_appointment_day (CalendarClient *client,
CalendarDayIter iter_func,
gpointer user_data);
void calendar_client_set_task_completed (CalendarClient *client,
char *task_uid,
gboolean task_completed,
guint percent_complete);
void calendar_event_free (CalendarEvent *event);
G_END_DECLS
#endif /* __CALENDAR_CLIENT_H__ */

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2004 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Authors:
* Mark McLoughlin <mark@skynet.ie>
*/
#ifndef __CALENDAR_DEBUG_H__
#define __CALENDAR_DEBUG_H__
#include <glib.h>
G_BEGIN_DECLS
#ifdef CALENDAR_ENABLE_DEBUG
#include <stdio.h>
#ifdef G_HAVE_ISO_VARARGS
# define dprintf(...) fprintf (stderr, __VA_ARGS__);
#elif defined(G_HAVE_GNUC_VARARGS)
# define dprintf(args...) fprintf (stderr, args);
#endif
#else /* if !defined (CALENDAR_DEBUG) */
#ifdef G_HAVE_ISO_VARARGS
# define dprintf(...)
#elif defined(G_HAVE_GNUC_VARARGS)
# define dprintf(args...)
#endif
#endif /* CALENDAR_ENABLE_DEBUG */
G_END_DECLS
#endif /* __CALENDAR_DEBUG_H__ */

View File

@ -0,0 +1,658 @@
/*
* Copyright (C) 2004 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Authors:
* Mark McLoughlin <mark@skynet.ie>
* William Jon McCann <mccann@jhu.edu>
* Martin Grimme <martin@pycage.de>
* Christian Kellner <gicmo@xatom.net>
*/
#include <config.h>
#include "calendar-sources.h"
#include <libintl.h>
#include <string.h>
#include <gconf/gconf-client.h>
#define HANDLE_LIBICAL_MEMORY
#include <libecal/e-cal.h>
#include <libedataserver/e-source-list.h>
#include <libedataserverui/e-passwords.h>
#undef CALENDAR_ENABLE_DEBUG
#include "calendar-debug.h"
#ifndef _
#define _(x) gettext(x)
#endif
#ifndef N_
#define N_(x) x
#endif
#define CALENDAR_SOURCES_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesPrivate))
#define CALENDAR_SOURCES_EVO_DIR "/apps/evolution"
#define CALENDAR_SOURCES_APPOINTMENT_SOURCES_KEY CALENDAR_SOURCES_EVO_DIR "/calendar/sources"
#define CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR CALENDAR_SOURCES_EVO_DIR "/calendar/display"
#define CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_KEY CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR "/selected_calendars"
#define CALENDAR_SOURCES_TASK_SOURCES_KEY CALENDAR_SOURCES_EVO_DIR "/tasks/sources"
#define CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR CALENDAR_SOURCES_EVO_DIR "/calendar/tasks"
#define CALENDAR_SOURCES_SELECTED_TASK_SOURCES_KEY CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR "/selected_tasks"
typedef struct _CalendarSourceData CalendarSourceData;
struct _CalendarSourceData
{
ECalSourceType source_type;
CalendarSources *sources;
guint changed_signal;
GSList *clients;
GSList *selected_sources;
ESourceList *esource_list;
guint selected_sources_listener;
char *selected_sources_dir;
guint timeout_id;
guint loaded : 1;
};
struct _CalendarSourcesPrivate
{
CalendarSourceData appointment_sources;
CalendarSourceData task_sources;
GConfClient *gconf_client;
};
static void calendar_sources_class_init (CalendarSourcesClass *klass);
static void calendar_sources_init (CalendarSources *sources);
static void calendar_sources_finalize (GObject *object);
static void backend_died_cb (ECal *client, CalendarSourceData *source_data);
static void calendar_sources_esource_list_changed (ESourceList *source_list,
CalendarSourceData *source_data);
enum
{
APPOINTMENT_SOURCES_CHANGED,
TASK_SOURCES_CHANGED,
LAST_SIGNAL
};
static guint signals [LAST_SIGNAL] = { 0, };
static GObjectClass *parent_class = NULL;
static CalendarSources *calendar_sources_singleton = NULL;
GType
calendar_sources_get_type (void)
{
static GType sources_type = 0;
if (!sources_type)
{
static const GTypeInfo sources_info =
{
sizeof (CalendarSourcesClass),
NULL, /* base_init */
NULL, /* base_finalize */
(GClassInitFunc) calendar_sources_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (CalendarSources),
0, /* n_preallocs */
(GInstanceInitFunc) calendar_sources_init,
};
sources_type = g_type_register_static (G_TYPE_OBJECT,
"CalendarSources",
&sources_info, 0);
}
return sources_type;
}
static void
calendar_sources_class_init (CalendarSourcesClass *klass)
{
GObjectClass *gobject_class = (GObjectClass *) klass;
parent_class = g_type_class_peek_parent (klass);
gobject_class->finalize = calendar_sources_finalize;
g_type_class_add_private (klass, sizeof (CalendarSourcesPrivate));
signals [APPOINTMENT_SOURCES_CHANGED] =
g_signal_new ("appointment-sources-changed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (CalendarSourcesClass,
appointment_sources_changed),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
signals [TASK_SOURCES_CHANGED] =
g_signal_new ("task-sources-changed",
G_TYPE_FROM_CLASS (gobject_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (CalendarSourcesClass,
task_sources_changed),
NULL,
NULL,
g_cclosure_marshal_VOID__VOID,
G_TYPE_NONE,
0);
}
static void
calendar_sources_init (CalendarSources *sources)
{
sources->priv = CALENDAR_SOURCES_GET_PRIVATE (sources);
sources->priv->appointment_sources.source_type = E_CAL_SOURCE_TYPE_EVENT;
sources->priv->appointment_sources.sources = sources;
sources->priv->appointment_sources.changed_signal = signals [APPOINTMENT_SOURCES_CHANGED];
sources->priv->appointment_sources.timeout_id = 0;
sources->priv->task_sources.source_type = E_CAL_SOURCE_TYPE_TODO;
sources->priv->task_sources.sources = sources;
sources->priv->task_sources.changed_signal = signals [TASK_SOURCES_CHANGED];
sources->priv->task_sources.timeout_id = 0;
sources->priv->gconf_client = gconf_client_get_default ();
}
static void
calendar_sources_finalize_source_data (CalendarSources *sources,
CalendarSourceData *source_data)
{
if (source_data->loaded)
{
GSList *l;
if (source_data->selected_sources_dir)
{
gconf_client_remove_dir (sources->priv->gconf_client,
source_data->selected_sources_dir,
NULL);
g_free (source_data->selected_sources_dir);
source_data->selected_sources_dir = NULL;
}
if (source_data->selected_sources_listener)
{
gconf_client_notify_remove (sources->priv->gconf_client,
source_data->selected_sources_listener);
source_data->selected_sources_listener = 0;
}
for (l = source_data->clients; l; l = l->next)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
G_CALLBACK (backend_died_cb),
source_data);
g_object_unref (l->data);
}
g_slist_free (source_data->clients);
source_data->clients = NULL;
if (source_data->esource_list)
{
g_signal_handlers_disconnect_by_func (source_data->esource_list,
G_CALLBACK (calendar_sources_esource_list_changed),
source_data);
g_object_unref (source_data->esource_list);
}
source_data->esource_list = NULL;
for (l = source_data->selected_sources; l; l = l->next)
g_free (l->data);
g_slist_free (source_data->selected_sources);
source_data->selected_sources = NULL;
if (source_data->timeout_id != 0)
{
g_source_remove (source_data->timeout_id);
source_data->timeout_id = 0;
}
source_data->loaded = FALSE;
}
}
static void
calendar_sources_finalize (GObject *object)
{
CalendarSources *sources = CALENDAR_SOURCES (object);
calendar_sources_finalize_source_data (sources, &sources->priv->appointment_sources);
calendar_sources_finalize_source_data (sources, &sources->priv->task_sources);
if (sources->priv->gconf_client)
g_object_unref (sources->priv->gconf_client);
sources->priv->gconf_client = NULL;
if (G_OBJECT_CLASS (parent_class)->finalize)
G_OBJECT_CLASS (parent_class)->finalize (object);
}
CalendarSources *
calendar_sources_get (void)
{
gpointer singleton_location = &calendar_sources_singleton;
if (calendar_sources_singleton)
return g_object_ref (calendar_sources_singleton);
calendar_sources_singleton = g_object_new (CALENDAR_TYPE_SOURCES, NULL);
g_object_add_weak_pointer (G_OBJECT (calendar_sources_singleton),
singleton_location);
return calendar_sources_singleton;
}
static gboolean
is_source_selected (ESource *esource,
GSList *selected_sources)
{
const char *uid;
GSList *l;
uid = e_source_peek_uid (esource);
for (l = selected_sources; l; l = l->next)
{
const char *source = l->data;
if (!strcmp (source, uid))
return TRUE;
}
return FALSE;
}
static char *
auth_func_cb (ECal *ecal,
const char *prompt,
const char *key,
gpointer user_data)
{
ESource *source;
const gchar *auth_domain;
const gchar *component_name;
source = e_cal_get_source (ecal);
auth_domain = e_source_get_property (source, "auth-domain");
component_name = auth_domain ? auth_domain : "Calendar";
return e_passwords_get_password (component_name, key);
}
/* The clients are just created here but not loaded */
static ECal *
get_ecal_from_source (ESource *esource,
ECalSourceType source_type,
GSList *existing_clients)
{
ECal *retval;
if (existing_clients)
{
GSList *l;
for (l = existing_clients; l; l = l->next)
{
ECal *client = E_CAL (l->data);
if (e_source_equal (esource, e_cal_get_source (client)))
{
dprintf (" load_esource: found existing source ... returning that\n");
return g_object_ref (client);
}
}
}
retval = e_cal_new (esource, source_type);
if (!retval)
{
g_warning ("Could not load source '%s' from '%s'\n",
e_source_peek_name (esource),
e_source_peek_relative_uri (esource));
return NULL;
}
e_cal_set_auth_func (retval, auth_func_cb, NULL);
return retval;
}
/* - Order doesn't matter
* - Can just compare object pointers since we
* re-use client connections
*/
static gboolean
compare_ecal_lists (GSList *a,
GSList *b)
{
GSList *l;
if (g_slist_length (a) != g_slist_length (b))
return FALSE;
for (l = a; l; l = l->next)
{
if (!g_slist_find (b, l->data))
return FALSE;
}
return TRUE;
}
static inline void
debug_dump_selected_sources (GSList *selected_sources)
{
#ifdef CALENDAR_ENABLE_DEBUG
GSList *l;
dprintf ("Selected sources:\n");
for (l = selected_sources; l; l = l->next)
{
char *source = l->data;
dprintf (" %s\n", source);
}
dprintf ("\n");
#endif
}
static inline void
debug_dump_ecal_list (GSList *ecal_list)
{
#ifdef CALENDAR_ENABLE_DEBUG
GSList *l;
dprintf ("Loaded clients:\n");
for (l = ecal_list; l; l = l->next)
{
ECal *client = l->data;
ESource *source = e_cal_get_source (client);
dprintf (" %s %s %s\n",
e_source_peek_uid (source),
e_source_peek_name (source),
e_cal_get_uri (client));
}
#endif
}
static void
calendar_sources_load_esource_list (CalendarSourceData *source_data);
static gboolean
backend_restart (gpointer data)
{
CalendarSourceData *source_data = data;
calendar_sources_load_esource_list (source_data);
source_data->timeout_id = 0;
return FALSE;
}
static void
backend_died_cb (ECal *client, CalendarSourceData *source_data)
{
const char *uristr;
source_data->clients = g_slist_remove (source_data->clients, client);
if (g_slist_length (source_data->clients) < 1)
{
g_slist_free (source_data->clients);
source_data->clients = NULL;
}
uristr = e_cal_get_uri (client);
g_warning ("The calendar backend for %s has crashed.", uristr);
if (source_data->timeout_id != 0)
{
g_source_remove (source_data->timeout_id);
source_data->timeout_id = 0;
}
source_data->timeout_id = g_timeout_add_seconds (2, backend_restart,
source_data);
}
static void
calendar_sources_load_esource_list (CalendarSourceData *source_data)
{
GSList *clients = NULL;
GSList *groups, *l;
gboolean emit_signal = FALSE;
g_return_if_fail (source_data->esource_list != NULL);
debug_dump_selected_sources (source_data->selected_sources);
dprintf ("Source groups:\n");
groups = e_source_list_peek_groups (source_data->esource_list);
for (l = groups; l; l = l->next)
{
GSList *esources, *s;
dprintf (" %s\n", e_source_group_peek_uid (l->data));
dprintf (" sources:\n");
esources = e_source_group_peek_sources (l->data);
for (s = esources; s; s = s->next)
{
ESource *esource = E_SOURCE (s->data);
ECal *client;
dprintf (" type = '%s' uid = '%s', name = '%s', relative uri = '%s': \n",
source_data->source_type == E_CAL_SOURCE_TYPE_EVENT ? "appointment" : "task",
e_source_peek_uid (esource),
e_source_peek_name (esource),
e_source_peek_relative_uri (esource));
if (is_source_selected (esource, source_data->selected_sources) &&
(client = get_ecal_from_source (esource, source_data->source_type, source_data->clients)))
{
clients = g_slist_prepend (clients, client);
}
}
}
dprintf ("\n");
if (source_data->loaded &&
!compare_ecal_lists (source_data->clients, clients))
emit_signal = TRUE;
for (l = source_data->clients; l; l = l->next)
{
g_signal_handlers_disconnect_by_func (G_OBJECT (l->data),
G_CALLBACK (backend_died_cb),
source_data);
g_object_unref (l->data);
}
g_slist_free (source_data->clients);
source_data->clients = g_slist_reverse (clients);
/* connect to backend_died after we disconnected the previous signal
* handlers. If we do it before, we'll lose some handlers (for clients that
* were already there before) */
for (l = source_data->clients; l; l = l->next)
{
g_signal_connect (G_OBJECT (l->data), "backend_died",
G_CALLBACK (backend_died_cb), source_data);
}
if (emit_signal)
{
dprintf ("Emitting %s-sources-changed signal\n",
source_data->source_type == E_CAL_SOURCE_TYPE_EVENT ? "appointment" : "task");
g_signal_emit (source_data->sources, source_data->changed_signal, 0);
}
debug_dump_ecal_list (source_data->clients);
}
static void
calendar_sources_esource_list_changed (ESourceList *source_list,
CalendarSourceData *source_data)
{
dprintf ("ESourceList changed, reloading\n");
calendar_sources_load_esource_list (source_data);
}
static void
calendar_sources_selected_sources_notify (GConfClient *client,
guint cnx_id,
GConfEntry *entry,
CalendarSourceData *source_data)
{
GSList *l;
if (!entry->value ||
entry->value->type != GCONF_VALUE_LIST ||
gconf_value_get_list_type (entry->value) != GCONF_VALUE_STRING)
return;
dprintf ("Selected sources key (%s) changed, reloading\n", entry->key);
for (l = source_data->selected_sources; l; l = l->next)
g_free (l->data);
source_data->selected_sources = NULL;
for (l = gconf_value_get_list (entry->value); l; l = l->next)
{
const char *source = gconf_value_get_string (l->data);
source_data->selected_sources =
g_slist_prepend (source_data->selected_sources,
g_strdup (source));
}
source_data->selected_sources =
g_slist_reverse (source_data->selected_sources);
calendar_sources_load_esource_list (source_data);
}
static void
calendar_sources_load_sources (CalendarSources *sources,
CalendarSourceData *source_data,
const char *sources_key,
const char *selected_sources_key,
const char *selected_sources_dir)
{
GConfClient *gconf_client;
GError *error;
dprintf ("---------------------------\n");
dprintf ("Loading sources:\n");
dprintf (" sources_key: %s\n", sources_key);
dprintf (" selected_sources_key: %s\n", selected_sources_key);
dprintf (" selected_sources_dir: %s\n", selected_sources_dir);
gconf_client = sources->priv->gconf_client;
error = NULL;
source_data->selected_sources = gconf_client_get_list (gconf_client,
selected_sources_key,
GCONF_VALUE_STRING,
&error);
if (error)
{
g_warning ("Failed to get selected sources from '%s': %s\n",
selected_sources_key,
error->message);
g_error_free (error);
return;
}
gconf_client_add_dir (gconf_client,
selected_sources_dir,
GCONF_CLIENT_PRELOAD_NONE,
NULL);
source_data->selected_sources_dir = g_strdup (selected_sources_dir);
source_data->selected_sources_listener =
gconf_client_notify_add (gconf_client,
selected_sources_dir,
(GConfClientNotifyFunc) calendar_sources_selected_sources_notify,
source_data, NULL, NULL);
source_data->esource_list = e_source_list_new_for_gconf (gconf_client, sources_key);
g_signal_connect (source_data->esource_list, "changed",
G_CALLBACK (calendar_sources_esource_list_changed),
source_data);
calendar_sources_load_esource_list (source_data);
source_data->loaded = TRUE;
dprintf ("---------------------------\n");
}
GSList *
calendar_sources_get_appointment_sources (CalendarSources *sources)
{
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
if (!sources->priv->appointment_sources.loaded)
{
calendar_sources_load_sources (sources,
&sources->priv->appointment_sources,
CALENDAR_SOURCES_APPOINTMENT_SOURCES_KEY,
CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_KEY,
CALENDAR_SOURCES_SELECTED_APPOINTMENT_SOURCES_DIR);
}
return sources->priv->appointment_sources.clients;
}
GSList *
calendar_sources_get_task_sources (CalendarSources *sources)
{
g_return_val_if_fail (CALENDAR_IS_SOURCES (sources), NULL);
if (!sources->priv->task_sources.loaded)
{
calendar_sources_load_sources (sources,
&sources->priv->task_sources,
CALENDAR_SOURCES_TASK_SOURCES_KEY,
CALENDAR_SOURCES_SELECTED_TASK_SOURCES_KEY,
CALENDAR_SOURCES_SELECTED_TASK_SOURCES_DIR);
}
return sources->priv->task_sources.clients;
}

View File

@ -0,0 +1,66 @@
/*
* Copyright (C) 2004 Free Software Foundation, Inc.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*
* Authors:
* Mark McLoughlin <mark@skynet.ie>
* William Jon McCann <mccann@jhu.edu>
* Martin Grimme <martin@pycage.de>
* Christian Kellner <gicmo@xatom.net>
*/
#ifndef __CALENDAR_SOURCES_H__
#define __CALENDAR_SOURCES_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define CALENDAR_TYPE_SOURCES (calendar_sources_get_type ())
#define CALENDAR_SOURCES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), CALENDAR_TYPE_SOURCES, CalendarSources))
#define CALENDAR_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), CALENDAR_TYPE_SOURCES, CalendarSourcesClass))
#define CALENDAR_IS_SOURCES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), CALENDAR_TYPE_SOURCES))
#define CALENDAR_IS_SOURCES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), CALENDAR_TYPE_SOURCES))
#define CALENDAR_SOURCES_GET_CLASS(o)(G_TYPE_INSTANCE_GET_CLASS ((o), CALENDAR_TYPE_SOURCES, CalendarSourcesClass))
typedef struct _CalendarSources CalendarSources;
typedef struct _CalendarSourcesClass CalendarSourcesClass;
typedef struct _CalendarSourcesPrivate CalendarSourcesPrivate;
struct _CalendarSources
{
GObject parent;
CalendarSourcesPrivate *priv;
};
struct _CalendarSourcesClass
{
GObjectClass parent_class;
void (* appointment_sources_changed) (CalendarSources *sources);
void (* task_sources_changed) (CalendarSources *sources);
};
GType calendar_sources_get_type (void) G_GNUC_CONST;
CalendarSources *calendar_sources_get (void);
GSList *calendar_sources_get_appointment_sources (CalendarSources *sources);
GSList *calendar_sources_get_task_sources (CalendarSources *sources);
G_END_DECLS
#endif /* __CALENDAR_SOURCES_H__ */

View File

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

View File

@ -0,0 +1,45 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
#ifndef __SHELL_EVOLUTION_EVENT_SOURCE_H__
#define __SHELL_EVOLUTION_EVENT_SOURCE_H__
#include <glib-object.h>
G_BEGIN_DECLS
typedef struct _ShellEvolutionEvent ShellEvolutionEvent;
struct _ShellEvolutionEvent
{
gchar *summary;
gboolean all_day;
gint64 msec_begin;
};
GType shell_evolution_event_get_type (void) G_GNUC_CONST;
ShellEvolutionEvent *shell_evolution_event_new (const gchar *summary,
gboolean all_day,
gint64 msec_begin);
ShellEvolutionEvent *shell_evolution_event_copy (ShellEvolutionEvent *event);
void shell_evolution_event_free (ShellEvolutionEvent *event);
typedef struct _ShellEvolutionEventSource ShellEvolutionEventSource;
typedef struct _ShellEvolutionEventSourceClass ShellEvolutionEventSourceClass;
#define SHELL_TYPE_EVOLUTION_EVENT_SOURCE (shell_evolution_event_source_get_type ())
#define SHELL_EVOLUTION_EVENT_SOURCE(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSource))
#define SHELL_EVOLUTION_EVENT_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSourceClass))
#define SHELL_IS_EVOLUTION_EVENT_SOURCE(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), SHELL_TYPE_EVOLUTION_EVENT_SOURCE))
#define SHELL_IS_EVOLUTION_EVENT_SOURCE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), SHELL_TYPE_EVOLUTION_EVENT_SOURCE))
#define SHELL_EVOLUTION_EVENT_SOURCE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), SHELL_TYPE_EVOLUTION_EVENT_SOURCE, ShellEvolutionEventSourceClass))
GType shell_evolution_event_source_get_type (void) G_GNUC_CONST;
ShellEvolutionEventSource *shell_evolution_event_source_new (void);
void shell_evolution_event_source_request_range (ShellEvolutionEventSource *source,
gint64 msec_begin,
gint64 msec_end);
GList *shell_evolution_event_source_get_events (ShellEvolutionEventSource *source,
gint64 msec_begin,
gint64 msec_end);
G_END_DECLS
#endif /* __SHELL_EVOLUTION_EVENT_SOURCE_H__ */

View File

@ -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"