Compare commits

...

50 Commits

Author SHA1 Message Date
David Zeuthen
2be92900b6 Merge branch 'master' into datetime 2011-01-28 14:47:34 -05:00
David Zeuthen
e82cbf3cc8 Simplify code for 'This week' / 'Next week' logic
Also make the calendar work when there are many all-day events shown. See

 http://people.freedesktop.org/~david/many-all-day-events.png

for details.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-28 14:30:52 -05:00
David Zeuthen
0b05280e03 Merge branch 'master' into datetime 2011-01-28 13:32:19 -05:00
David Zeuthen
cd468021e2 Merge branch 'master' into datetime 2011-01-28 13:26:05 -05:00
David Zeuthen
57ebfb596b Add Evolution Data Server to required packages
Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-28 13:21:27 -05:00
David Zeuthen
8a1313be71 Update for comments from code review
See https://bugzilla.gnome.org/show_bug.cgi?id=632109#c41 for the review.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-28 12:05:17 -05:00
David Zeuthen
803e74101c Merge branch 'master' into datetime 2011-01-28 11:06:58 -05:00
David Zeuthen
858e1b7f5b Hard-code width of calendar popup to 600px and fix ellipsis for events
We might want the width to be expressed in em or in % of the available
monitor width. Or perhaps a combination of all three. I don't know.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-26 14:34:13 -05:00
David Zeuthen
a922db0398 Align day name to "This week"
Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-26 14:26:05 -05:00
David Zeuthen
bbd2b89df3 Collapse two-pixel borders inside the table
It's a hack, but it works

 http://people.freedesktop.org/~david/calendar-table-collapse-hack.png

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-26 14:10:34 -05:00
David Zeuthen
505b9047d7 Adjust alignment in event list
As per this comment

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

See

 http://people.freedesktop.org/~david/calendar-today-align-fixes.png

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-26 13:39:04 -05:00
David Zeuthen
dd8716e5d9 Don't show "[All Day] Nothing Scheduled" for "This week" or "Next week"
Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-26 13:22:46 -05:00
David Zeuthen
18f2f6f938 Forgot to add file
Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-25 15:53:07 -05:00
David Zeuthen
41b0e0832e Get events from Evolution
This commit copies existing and deployed (thus, working!) GPLv2 code
from gnome-panel into src/calendar-client. Please keep in sync.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-25 15:42:10 -05:00
David Zeuthen
374a88366b Set up glue for native Evolution Data Server event source
Right now it's just native code returning a fake event.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-25 12:17:32 -05:00
David Zeuthen
a71c82863e Merge branch 'master' into datetime 2011-01-25 10:10:22 -05:00
David Zeuthen
a1440bdec7 Calendar updates
- Reshuffle items to make it look more like the mockup
 - Increase vertical spacing between event section ("Today") headings
   and the event items (see mockup)
 - Don't show the year in month-switcher and event list unless it's
   a different year
 - Include the day in the date heading
 - Sort events in event list according to start time
 - Respect 12h/24h setting in event list
 - Support "All Day" events in CalendarTask abstraction
 - Show "Nothing Scheduled" if there are no events
 - Add a "Open Calendar" button
 - Refactor some of the code

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-25 10:07:54 -05:00
David Zeuthen
b10458e57a Use stippled line instead of gradient in calendar
See

 http://people.freedesktop.org/~david/shell-calendar-with-dash.png

We still have to move around some stuff to make it look like the mock-up, see

 https://live.gnome.org/GnomeShell/Design/Whiteboards/DateNTime

Thanks to Colin for binding Cairo's, set_dash() method.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-24 16:01:08 -05:00
David Zeuthen
50c5591ec2 Merge branch 'master' into datetime 2011-01-24 15:27:13 -05:00
David Zeuthen
bce2cf9164 Switch to empty event source
We are leaving in the fake event source so it's easy to debug the
UI. This is helpful because the fake event source has a transient
event that is being added/removed every five seconds.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-20 13:17:57 -05:00
David Zeuthen
2f52b87451 Merge branch 'master' into datetime 2011-01-20 13:11:13 -05:00
David Zeuthen
ba5a28023e Update comment about work days
Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-20 13:08:58 -05:00
David Zeuthen
0a87e28d1a Rework interaction between Calendar, EventList and EventSource abstractions
As proposed by Florian in the review.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-19 13:55:04 -05:00
David Zeuthen
d7ad949ecb Remove separator comment lines
Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-19 13:33:19 -05:00
David Zeuthen
fd49fe6915 Use camelCase for naming
Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-19 13:31:29 -05:00
David Zeuthen
c3c34890ce Merge branch 'master' into datetime 2011-01-19 12:46:00 -05:00
David Zeuthen
af68881a94 Partial update for code review
This addresses some of the comments in

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

Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-19 12:44:40 -05:00
David Zeuthen
0d9095d2d4 Catch up with master 2011-01-19 11:09:57 -05:00
Florian Müllner
8bc396a7e0 Adjust for merging commit 24f1e87
Signed-off-by: David Zeuthen <davidz@redhat.com>
2011-01-19 11:08:36 -05:00
David Zeuthen
71851a58e5 Minor updates
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-12-20 15:29:14 -05:00
David Zeuthen
70f50d61e0 Merge branch 'master' into datetime 2010-12-20 15:07:50 -05:00
David Zeuthen
f3ad82442d Remove remaining references to EDS/ical
We can add this back using the Calendar.EventSource abstraction once
we are ready for it.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-12-20 14:49:20 -05:00
David Zeuthen
6c80a35f8f Minor changes to event source code
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-12-20 14:36:29 -05:00
David Zeuthen
a2932250a7 Refactor calendar code
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-12-20 13:51:44 -05:00
David Zeuthen
1d5cce679a Neuter debug output 2010-12-20 11:58:56 -05:00
David Zeuthen
137ea12109 Fix merge conflicts 2010-12-20 11:55:36 -05:00
David Zeuthen
0b866a620f Rework some of the visual layout
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-12-15 17:32:35 -05:00
David Zeuthen
397386c761 Use SVGs from Jimmac for arrows instead of '<' and '>' glyphs
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-12-15 15:37:28 -05:00
David Zeuthen
f51984b20c Merge branch 'master' into datetime 2010-12-15 13:12:54 -05:00
David Zeuthen
8e9bc6019c Rework how fake events work
Also draw days with events with bold in the calendar view.

Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-11-30 14:31:37 -05:00
David Zeuthen
51481bed82 Fix merge conflicts
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-11-30 11:11:10 -05:00
David Zeuthen
8dd05f9e7b Use fake events
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-11-30 10:50:20 -05:00
David Zeuthen
b7fe72e74a Merge branch 'master' into datetime 2010-11-19 07:22:28 -05:00
David Zeuthen
7aa3aba0c2 Merge branch 'master' into datetime 2010-11-16 09:16:38 -05:00
David Zeuthen
eb66993447 Merge branch 'master' into datetime 2010-10-28 13:34:27 -04:00
David Zeuthen
7d5b4511cd Update datetime branch for latest changes
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-10-28 13:33:12 -04:00
David Zeuthen
3d1063dacb Merge branch 'master' into datetime
Conflicts:
	configure.ac
2010-10-26 11:48:34 -04:00
Maxim Ermilov
b4c038c036 calendar: implement Events List
Signed-off-by: David Zeuthen <davidz@redhat.com>
2010-10-20 14:30:35 -04:00
David Zeuthen
1ca1a2712d Merge branch 'master' into datetime 2010-10-20 13:55:41 -04:00
David Zeuthen
85ec4d86f3 Start implementing the Date and Time mockups 2010-10-13 17:53:26 -04:00
21 changed files with 4490 additions and 214 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

@ -265,10 +265,8 @@ function _relayout() {
// To avoid updating the position and size of the workspaces
// in the overview, we just hide the overview. The positions
// will be updated when it is next shown. We do the same for
// the calendar popdown.
// will be updated when it is next shown.
overview.hide();
panel.hideCalendar();
}
// metacity-clutter currently uses the same prefs as plain metacity,

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;
@ -496,121 +497,6 @@ AppMenuButton.prototype = {
Signals.addSignalMethods(AppMenuButton.prototype);
function ClockButton() {
this._init();
}
ClockButton.prototype = {
_init: function() {
this.actor = new St.Bin({ style_class: 'panel-button',
reactive: true,
can_focus: true,
x_fill: true,
y_fill: false,
track_hover: true });
this.actor._delegate = this;
this.actor.connect('button-press-event',
Lang.bind(this, this._toggleCalendar));
this._clock = new St.Label();
this.actor.set_child(this._clock);
this._calendarPopup = null;
this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
this._clockSettings = new Gio.Settings({ schema: 'org.gnome.shell.clock' });
this._desktopSettings.connect('changed', Lang.bind(this, this._updateClock));
this._clockSettings.connect('changed', Lang.bind(this, this._updateClock));
// Start the clock
this._updateClock();
},
closeCalendar: function() {
if (!this._calendarPopup || !this._calendarPopup.isOpen)
return;
this._calendarPopup.hide();
this.actor.remove_style_pseudo_class('pressed');
},
openCalendar: function() {
this._calendarPopup.show();
this.actor.add_style_pseudo_class('pressed');
},
_toggleCalendar: function() {
if (this._calendarPopup == null) {
this._calendarPopup = new CalendarPopup();
this._calendarPopup.actor.hide();
}
if (!this._calendarPopup.isOpen)
this.openCalendar();
else
this.closeCalendar();
},
_updateClock: function() {
let format = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);
let showDate = this._clockSettings.get_boolean(CLOCK_SHOW_DATE_KEY);
let showSeconds = this._clockSettings.get_boolean(CLOCK_SHOW_SECONDS_KEY);
let clockFormat;
switch (format) {
case '24h':
if (showDate)
/* Translators: This is the time format with date used
in 24-hour mode. */
clockFormat = showSeconds ? _("%a %b %e, %R:%S")
: _("%a %b %e, %R");
else
/* Translators: This is the time format without date used
in 24-hour mode. */
clockFormat = showSeconds ? _("%a %R:%S")
: _("%a %R");
break;
case '12h':
default:
if (showDate)
/* Translators: This is a time format with date used
for AM/PM. */
clockFormat = showSeconds ? _("%a %b %e, %l:%M:%S %p")
: _("%a %b %e, %l:%M %p");
else
/* Translators: This is a time format without date used
for AM/PM. */
clockFormat = showSeconds ? _("%a %l:%M:%S %p")
: _("%a %l:%M %p");
break;
}
let displayDate = new Date();
let msecRemaining;
if (showSeconds) {
msecRemaining = 1000 - displayDate.getMilliseconds();
if (msecRemaining < 50) {
displayDate.setSeconds(displayDate.getSeconds() + 1);
msecRemaining += 1000;
}
} else {
msecRemaining = 60000 - (1000 * displayDate.getSeconds() +
displayDate.getMilliseconds());
if (msecRemaining < 500) {
displayDate.setMinutes(displayDate.getMinutes() + 1);
msecRemaining += 60000;
}
}
this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
Mainloop.timeout_add(msecRemaining, Lang.bind(this, this._updateClock));
return false;
}
};
function Panel() {
this._init();
}
@ -807,9 +693,9 @@ Panel.prototype = {
this._menus.addMenu(appMenuButton.menu);
/* center */
this._clockButton = new ClockButton();
this._centerBox.add(this._clockButton.actor, { y_fill: true });
this._dateMenu = new DateMenu.DateMenuButton();
this._centerBox.add(this._dateMenu.actor, { y_fill: true });
this._menus.addMenu(this._dateMenu.menu);
/* right */
@ -898,10 +784,6 @@ Panel.prototype = {
this._rightBox.add(this._statusmenu.actor);
},
hideCalendar: function() {
this._clockButton.closeCalendar();
},
startupAnimation: function() {
this.actor.y = -this.actor.height;
Tweener.addTween(this.actor,

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

@ -83,7 +83,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 +121,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 +147,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 +168,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"