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>
This commit is contained in:
David Zeuthen 2011-01-25 09:55:41 -05:00
parent b10458e57a
commit a1440bdec7
3 changed files with 162 additions and 84 deletions

View File

@ -677,7 +677,7 @@ StTooltip StLabel {
.calendar-vertical-separator { .calendar-vertical-separator {
-stipple-width: 1px; -stipple-width: 1px;
-stipple-color: #505050; -stipple-color: #505050;
width: 1em; width: 1.5em;
} }
#calendarPopup { #calendarPopup {
@ -691,15 +691,8 @@ StTooltip StLabel {
padding: 10px; padding: 10px;
} }
#calendarHBox {
padding: 10px 20px;
}
#calendarVBox1 {
spacing: 20px;
}
.calendar { .calendar {
padding: .4em 1.75em;
spacing-rows: 0px; spacing-rows: 0px;
spacing-columns: 0px; spacing-columns: 0px;
} }
@ -749,22 +742,24 @@ StTooltip StLabel {
} }
.datemenu-date-label { .datemenu-date-label {
padding: .4em 1.75em;
font-size: 16px; font-size: 16px;
color: #ffffff; color: #ffffff;
} }
.calendar-day-base { .calendar-day-base {
font-size: 10px; font-size: 10px;
padding: 5px 3px;
text-align: center; text-align: center;
width: 24px;
height: 24px;
} }
.calendar-day-base:hover { .calendar-day-base:hover {
background: #999999; background: #777777;
} }
.calendar-day-base:active { .calendar-day-base:active {
background: #666666; background: #555555;
} }
.calendar-day-heading { .calendar-day-heading {
@ -820,6 +815,7 @@ StTooltip StLabel {
.events-day-header { .events-day-header {
font-size: 14px; font-size: 14px;
color: rgba(153, 153, 153, 1.0); color: rgba(153, 153, 153, 1.0);
padding: 0.3em;
} }
.events-day-dayname { .events-day-dayname {
@ -852,14 +848,6 @@ StTooltip StLabel {
width: 200px; width: 200px;
} }
.events-no-events {
font-weight: bold;
padding-left: 40px;
padding-right: 40px;
font-size: 14px;
color: rgba(153, 153, 153, 1.0);
}
.open-calendar { .open-calendar {
padding-bottom: 12px; padding-bottom: 12px;
padding-left: 12px; padding-left: 12px;

View File

@ -15,12 +15,19 @@ const MSECS_IN_DAY = 24 * 60 * 60 * 1000;
const WEEKDATE_HEADER_WIDTH_DIGITS = 3; const WEEKDATE_HEADER_WIDTH_DIGITS = 3;
const SHOW_WEEKDATE_KEY = 'show-weekdate'; const SHOW_WEEKDATE_KEY = 'show-weekdate';
// in org.gnome.desktop.interface
const CLOCK_FORMAT_KEY = 'clock-format';
function _sameDay(dateA, dateB) { function _sameDay(dateA, dateB) {
return (dateA.getDate() == dateB.getDate() && return (dateA.getDate() == dateB.getDate() &&
dateA.getMonth() == dateB.getMonth() && dateA.getMonth() == dateB.getMonth() &&
dateA.getYear() == dateB.getYear()); dateA.getYear() == dateB.getYear());
} }
function _sameYear(dateA, dateB) {
return (dateA.getYear() == dateB.getYear());
}
/* TODO: maybe needs config - right now we assume that Saturday and /* 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 * Sunday are non-work days (not true in e.g. Israel, it's Sunday and
* Monday there) * Monday there)
@ -29,6 +36,43 @@ function _isWorkDay(date) {
return date.getDay() != 0 && date.getDay() != 6; return date.getDay() != 0 && date.getDay() != 6;
} }
function _getBeginningOfDay(date) {
let ret = new Date(date.getTime());
ret.setHours(0);
ret.setMinutes(0);
ret.setMilliseconds(0);
return ret;
}
function _getEndOfDay(date) {
let ret = new Date(date.getTime());
ret.setHours(23);
ret.setMinutes(59);
ret.setMilliseconds(999);
return ret;
}
function _formatTaskTime(task, clockFormat) {
let ret;
if (task.allDay) {
/* Translators: Shown in calendar event list for all day events */
ret = _("All Day");
} else {
switch (clockFormat) {
case '24h':
ret = task.date.toLocaleFormat('%H:%M');
break;
default:
/* explicit fall-through */
case '12h':
ret = task.date.toLocaleFormat('%l:%M %p');
break;
}
}
return ret;
}
function _getCalendarWeekForDate(date) { function _getCalendarWeekForDate(date) {
// Based on the algorithms found here: // Based on the algorithms found here:
// http://en.wikipedia.org/wiki/Talk:ISO_week_date // http://en.wikipedia.org/wiki/Talk:ISO_week_date
@ -106,14 +150,15 @@ function _getEventDayAbbreviation(dayNumber) {
// Abstraction for an appointment/task in a calendar // Abstraction for an appointment/task in a calendar
function CalendarTask(date, summary) { function CalendarTask(date, summary, allDay) {
this._init(date, summary); this._init(date, summary, allDay);
} }
CalendarTask.prototype = { CalendarTask.prototype = {
_init: function(date, summary) { _init: function(date, summary, allDay) {
this.date = date; this.date = date;
this.summary = summary; this.summary = summary;
this.allDay = allDay;
} }
}; };
@ -153,52 +198,56 @@ FakeEventSource.prototype = {
// Generate fake events // Generate fake events
// //
let now = new Date(); let midnightToday = _getBeginningOfDay(new Date());
let summary = ''; let summary = '';
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
// '10-oclock pow-wow' is an event occuring IN THE PAST every four days at 10am // '10-oclock pow-wow' is an event occuring IN THE PAST every four days at 10am
for(let n = 0; n < 10; n++) { for (let n = 0; n < 10; n++) {
let t = new Date(now.getTime() - n * 4 * 86400 * 1000); let t = new Date(midnightToday.getTime() - n * 4 * 86400 * 1000);
t.setHours(10); t.setHours(10);
summary = '10-oclock pow-wow (n=' + n + ')'; summary = '10-oclock pow-wow (n=' + n + ')';
this._fakeTasks.push(new CalendarTask(t, summary)); this._fakeTasks.push(new CalendarTask(t, summary, false));
} }
// '11-oclock thing' is an event occuring every three days at 11am // '11-oclock thing' is an event occuring every three days at 11am
for(let n = 0; n < 10; n++) { for (let n = 0; n < 10; n++) {
let t = new Date(now.getTime() + n * 3 * 86400 * 1000); let t = new Date(midnightToday.getTime() + n * 3 * 86400 * 1000);
t.setHours(11); t.setHours(11);
summary = '11-oclock thing (n=' + n + ')'; summary = '11-oclock thing (n=' + n + ')';
this._fakeTasks.push(new CalendarTask(t, summary)); this._fakeTasks.push(new CalendarTask(t, summary, false));
} }
// 'Weekly Meeting' is an event occuring every seven days at 1:45pm (two days displaced) // 'Weekly Meeting' is an event occuring every seven days at 1:45pm (two days displaced)
for(let n = 0; n < 5; n++) { for (let n = 0; n < 5; n++) {
let t = new Date(now.getTime() + (n * 7 + 2) * 86400 * 1000); let t = new Date(midnightToday.getTime() + (n * 7 + 2) * 86400 * 1000);
t.setHours(13); t.setHours(13);
t.setMinutes(45); t.setMinutes(45);
summary = 'Weekly Meeting (n=' + n + ')'; summary = 'Weekly Meeting (n=' + n + ')';
this._fakeTasks.push(new CalendarTask(t, summary)); this._fakeTasks.push(new CalendarTask(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._fakeTasks.push(new CalendarTask(t, summary, true));
} }
// 'Get Married' is an event that actually reflects reality (Dec 4, 2010) :-) // 'Get Married' is an event that actually reflects reality (Dec 4, 2010) :-)
this._fakeTasks.push(new CalendarTask(new Date(2010, 11, 4, 16, 0), 'Get Married')); this._fakeTasks.push(new CalendarTask(new Date(2010, 11, 4, 16, 0), 'Get Married', false));
// ditto for 'NE Patriots vs NY Jets' // ditto for 'NE Patriots vs NY Jets'
this._fakeTasks.push(new CalendarTask(new Date(2010, 11, 6, 20, 30), 'NE Patriots vs NY Jets')); this._fakeTasks.push(new CalendarTask(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 // An event for tomorrow @6:30pm that is added/removed every five
// seconds (to check that the ::changed signal works) // seconds (to check that the ::changed signal works)
let transientEventDate = new Date(now.getTime() + 86400*1000); let transientEventDate = new Date(midnightToday.getTime() + 86400 * 1000);
transientEventDate.setHours(18); transientEventDate.setHours(18);
transientEventDate.setMinutes(30); transientEventDate.setMinutes(30);
transientEventDate.setSeconds(0); transientEventDate.setSeconds(0);
Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent)); Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
this._includeTransientEvent = false; this._includeTransientEvent = false;
this._transientEvent = new CalendarTask(transientEventDate, 'A Transient Event'); this._transientEvent = new CalendarTask(transientEventDate, 'A Transient Event', false);
this._transientEventCounter = 1; this._transientEventCounter = 1;
}, },
@ -223,16 +272,15 @@ FakeEventSource.prototype = {
} }
if (this._includeTransientEvent && this._transientEvent.date >= begin && this._transientEvent.date <= end) if (this._includeTransientEvent && this._transientEvent.date >= begin && this._transientEvent.date <= end)
result.push(this._transientEvent); result.push(this._transientEvent);
result.sort(function(task1, task2) {
return task1.date.getTime() - task2.date.getTime();
});
return result; return result;
}, },
hasTasks: function(day) { hasTasks: function(day) {
let dayBegin = new Date(day.getTime()); let dayBegin = _getBeginningOfDay(day);
let dayEnd = new Date(day.getTime()); let dayEnd = _getEndOfDay(day);
dayBegin.setHours(0);
dayBegin.setMinutes(1);
dayEnd.setHours(23);
dayEnd.setMinutes(59);
let tasks = this.getTasks(dayBegin, dayEnd); let tasks = this.getTasks(dayBegin, dayEnd);
@ -280,6 +328,7 @@ Calendar.prototype = {
} }
// Find the ordering for month/year in the calendar heading // Find the ordering for month/year in the calendar heading
this._headerFormatWithoutYear = '%B';
switch (Gettext_gtk30.gettext('calendar:MY')) { switch (Gettext_gtk30.gettext('calendar:MY')) {
case 'calendar:MY': case 'calendar:MY':
this._headerFormat = '%B %Y'; this._headerFormat = '%B %Y';
@ -417,6 +466,11 @@ Calendar.prototype = {
}, },
_update: function() { _update: function() {
let now = new Date();
if (_sameYear(this.selectedDate, now))
this._dateLabel.text = this.selectedDate.toLocaleFormat(this._headerFormatWithoutYear);
else
this._dateLabel.text = this.selectedDate.toLocaleFormat(this._headerFormat); this._dateLabel.text = this.selectedDate.toLocaleFormat(this._headerFormat);
// Remove everything but the topBox and the weekday labels // Remove everything but the topBox and the weekday labels
@ -432,8 +486,6 @@ Calendar.prototype = {
let daysToWeekStart = (7 + iter.getDay() - this._weekStart) % 7; let daysToWeekStart = (7 + iter.getDay() - this._weekStart) % 7;
iter.setTime(iter.getTime() - daysToWeekStart * MSECS_IN_DAY); iter.setTime(iter.getTime() - daysToWeekStart * MSECS_IN_DAY);
let now = new Date();
let row = 2; let row = 2;
while (true) { while (true) {
let button = new St.Button({ label: iter.getDate().toString() }); let button = new St.Button({ label: iter.getDate().toString() });
@ -500,6 +552,8 @@ EventsList.prototype = {
this._date = new Date(); this._date = new Date();
this._eventSource = eventSource; this._eventSource = eventSource;
this._eventSource.connect('changed', Lang.bind(this, this._update)); 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(); this._update();
}, },
@ -516,10 +570,12 @@ EventsList.prototype = {
text: desc} )); text: desc} ));
}, },
_addPeriod: function(header, begin, end, includeDayName) { _addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
let tasks = this._eventSource.getTasks(begin, end); let tasks = this._eventSource.getTasks(begin, end);
if (tasks.length == 0) let clockFormat = this._desktopSettings.get_string(CLOCK_FORMAT_KEY);;
if (tasks.length == 0 && !showNothingScheduled)
return; return;
let vbox = new St.BoxLayout( {vertical: true} ); let vbox = new St.BoxLayout( {vertical: true} );
@ -538,50 +594,59 @@ EventsList.prototype = {
for (let n = 0; n < tasks.length; n++) { for (let n = 0; n < tasks.length; n++) {
let task = tasks[n]; let task = tasks[n];
let dayString = _getEventDayAbbreviation(task.date.getDay()); let dayString = _getEventDayAbbreviation(task.date.getDay());
let timeString = task.date.toLocaleFormat('%I:%M %p'); // TODO: locale considerations let timeString = _formatTaskTime(task, clockFormat);
let summaryString = task.summary; let summaryString = task.summary;
this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString); this._addEvent(dayNameBox, timeBox, eventTitleBox, includeDayName, dayString, timeString, summaryString);
} }
if (tasks.length == 0 && showNothingScheduled) {
let now = new Date();
/* Translators: Text to show if there are no events */
let nothingTask = new CalendarTask(now, _("Nothing Scheduled"), true);
let timeString = _formatTaskTime(nothingTask, clockFormat);
this._addEvent(dayNameBox, timeBox, eventTitleBox, false, "", timeString, nothingTask.summary);
}
}, },
_showOtherDay: function(day) { _showOtherDay: function(day) {
this.actor.destroy_children(); this.actor.destroy_children();
let dayBegin = new Date(day.getTime()); let dayBegin = _getBeginningOfDay(day);
let dayEnd = new Date(day.getTime()); let dayEnd = _getEndOfDay(day);
dayBegin.setHours(0);
dayBegin.setMinutes(1); let dayString;
dayEnd.setHours(23); let now = new Date();
dayEnd.setMinutes(59); if (_sameYear(day, now))
this._addPeriod(day.toLocaleFormat('%A, %B %d, %Y'), dayBegin, dayEnd, false); dayString = day.toLocaleFormat('%A, %B %d');
else
dayString = day.toLocaleFormat('%A, %B %d, %Y');
this._addPeriod(dayString, dayBegin, dayEnd, false, true);
}, },
_showToday: function() { _showToday: function() {
this.actor.destroy_children(); this.actor.destroy_children();
let dayBegin = new Date(); let now = new Date();
let dayEnd = new Date(); let dayBegin = _getBeginningOfDay(now);
dayBegin.setHours(0); let dayEnd = _getEndOfDay(now);
dayBegin.setMinutes(1); this._addPeriod(_("Today"), dayBegin, dayEnd, false, true);
dayEnd.setHours(23);
dayEnd.setMinutes(59);
this._addPeriod(_("Today"), dayBegin, dayEnd, false);
dayBegin.setDate(dayBegin.getDate() + 1); dayBegin.setDate(dayBegin.getDate() + 1);
dayEnd.setDate(dayEnd.getDate() + 1); dayEnd.setDate(dayEnd.getDate() + 1);
this._addPeriod(_("Tomorrow"), dayBegin, dayEnd, false); this._addPeriod(_("Tomorrow"), dayBegin, dayEnd, false, true);
if (dayEnd.getDay() == 6 || dayEnd.getDay() == 0) { if (dayEnd.getDay() == 6 || dayEnd.getDay() == 0) {
dayBegin.setDate(dayEnd.getDate() + 1); dayBegin.setDate(dayEnd.getDate() + 1);
dayEnd.setDate(dayBegin.getDate() + 6 - dayBegin.getDay()); dayEnd.setDate(dayBegin.getDate() + 6 - dayBegin.getDay());
this._addPeriod(_("Next week"), dayBegin, dayEnd, true); this._addPeriod(_("Next week"), dayBegin, dayEnd, true, true);
return; return;
} } else {
let d = 6 - dayEnd.getDay() - 1; let d = 6 - dayEnd.getDay() - 1;
dayBegin.setDate(dayBegin.getDate() + 1); dayBegin.setDate(dayBegin.getDate() + 1);
dayEnd.setDate(dayEnd.getDate() + 1 + d); dayEnd.setDate(dayEnd.getDate() + 1 + d);
this._addPeriod(_("This week"), dayBegin, dayEnd, true); this._addPeriod(_("This week"), dayBegin, dayEnd, true, true);
}
}, },
// Sets the event list to show events from a specific date // Sets the event list to show events from a specific date

View File

@ -55,8 +55,8 @@ DateMenuButton.prototype = {
let hbox; let hbox;
let vbox; let vbox;
this._eventSource = new Calendar.EmptyEventSource(); //this._eventSource = new Calendar.EmptyEventSource();
//this._eventSource = new Calendar.FakeEventSource(); this._eventSource = new Calendar.FakeEventSource();
// TODO: write e.g. EvolutionEventSource // TODO: write e.g. EvolutionEventSource
PanelMenu.Button.prototype._init.call(this, St.Align.START); PanelMenu.Button.prototype._init.call(this, St.Align.START);
@ -64,12 +64,12 @@ DateMenuButton.prototype = {
this._clock = new St.Label(); this._clock = new St.Label();
this.actor.set_child(this._clock); this.actor.set_child(this._clock);
hbox = new St.BoxLayout({name: 'calendarHBox'}); hbox = new St.BoxLayout();
this.menu.addActor(hbox); this.menu.addActor(hbox);
// Fill up the first column // Fill up the first column
vbox = new St.BoxLayout({vertical: true, name: 'calendarVBox1'}); vbox = new St.BoxLayout({vertical: true});
hbox.add(vbox); hbox.add(vbox);
// Date // Date
@ -87,18 +87,33 @@ DateMenuButton.prototype = {
})); }));
vbox.add(this._calendar.actor); vbox.add(this._calendar.actor);
//item = new St.Button({style_class: 'popup-menu-item', label: 'foobar'});
//vbox.add(item);
item = new PopupMenu.PopupSeparatorMenuItem();
item.actor.remove_actor(item._drawingArea);
vbox.add(item._drawingArea);
item = new PopupMenu.PopupMenuItem(_("Date and Time Settings"));
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
vbox.add(item.actor);
// Add vertical separator // Add vertical separator
item = new St.DrawingArea({ style_class: 'calendar-vertical-separator', item = new St.DrawingArea({ style_class: 'calendar-vertical-separator',
pseudo_class: 'highlighted' }); pseudo_class: 'highlighted' });
item.set_width(25); // TODO: don't hard-code the width
item.connect('repaint', Lang.bind(this, _onVertSepRepaint)); item.connect('repaint', Lang.bind(this, _onVertSepRepaint));
hbox.add(item); hbox.add(item);
// Fill up the second column // Fill up the second column
// //
vbox = new St.BoxLayout({vertical: true});
hbox.add(vbox);
// Event list // Event list
hbox.add(this._eventList.actor); 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 // Whenever the menu is opened, select today
this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) { this.menu.connect('open-state-changed', Lang.bind(this, function(menu, isOpen) {
@ -113,12 +128,12 @@ DateMenuButton.prototype = {
// Done with hbox for calendar and event list // Done with hbox for calendar and event list
// Add separator // Add separator
item = new PopupMenu.PopupSeparatorMenuItem(); //item = new PopupMenu.PopupSeparatorMenuItem();
this.menu.addMenuItem(item); //this.menu.addMenuItem(item);
// Add button to get to the Date and Time settings // Add button to get to the Date and Time settings
this.menu.addAction(_("Date and Time Settings"), //this.menu.addAction(_("Date and Time Settings"),
Lang.bind(this, this._onPreferencesActivate)); // Lang.bind(this, this._onPreferencesActivate));
// Track changes to clock settings // Track changes to clock settings
this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' }); this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
@ -185,8 +200,10 @@ DateMenuButton.prototype = {
this._clock.set_text(displayDate.toLocaleFormat(clockFormat)); this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
/* Translators: This is the date format to use */ /* Translators: This is the date format to use when the calendar popup is
dateFormat = _("%B %e, %Y"); * 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)); this._date.set_text(displayDate.toLocaleFormat(dateFormat));
Mainloop.timeout_add(msecRemaining, Lang.bind(this, this._updateClockAndDate)); Mainloop.timeout_add(msecRemaining, Lang.bind(this, this._updateClockAndDate));
@ -194,6 +211,14 @@ DateMenuButton.prototype = {
}, },
_onPreferencesActivate: function() { _onPreferencesActivate: function() {
this.menu.close();
Util.spawnDesktop('gnome-datetime-panel'); 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');
},
}; };