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:
parent
b10458e57a
commit
a1440bdec7
@ -677,7 +677,7 @@ StTooltip StLabel {
|
||||
.calendar-vertical-separator {
|
||||
-stipple-width: 1px;
|
||||
-stipple-color: #505050;
|
||||
width: 1em;
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
#calendarPopup {
|
||||
@ -691,15 +691,8 @@ StTooltip StLabel {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#calendarHBox {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
#calendarVBox1 {
|
||||
spacing: 20px;
|
||||
}
|
||||
|
||||
.calendar {
|
||||
padding: .4em 1.75em;
|
||||
spacing-rows: 0px;
|
||||
spacing-columns: 0px;
|
||||
}
|
||||
@ -749,22 +742,24 @@ StTooltip StLabel {
|
||||
}
|
||||
|
||||
.datemenu-date-label {
|
||||
padding: .4em 1.75em;
|
||||
font-size: 16px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.calendar-day-base {
|
||||
font-size: 10px;
|
||||
padding: 5px 3px;
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.calendar-day-base:hover {
|
||||
background: #999999;
|
||||
background: #777777;
|
||||
}
|
||||
|
||||
.calendar-day-base:active {
|
||||
background: #666666;
|
||||
background: #555555;
|
||||
}
|
||||
|
||||
.calendar-day-heading {
|
||||
@ -820,6 +815,7 @@ StTooltip StLabel {
|
||||
.events-day-header {
|
||||
font-size: 14px;
|
||||
color: rgba(153, 153, 153, 1.0);
|
||||
padding: 0.3em;
|
||||
}
|
||||
|
||||
.events-day-dayname {
|
||||
@ -852,14 +848,6 @@ StTooltip StLabel {
|
||||
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 {
|
||||
padding-bottom: 12px;
|
||||
padding-left: 12px;
|
||||
|
@ -15,12 +15,19 @@ 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)
|
||||
@ -29,6 +36,43 @@ function _isWorkDay(date) {
|
||||
return date.getDay() != 0 && date.getDay() != 6;
|
||||
}
|
||||
|
||||
function _getBeginningOfDay(date) {
|
||||
let ret = new Date(date.getTime());
|
||||
ret.setHours(0);
|
||||
ret.setMinutes(0);
|
||||
ret.setMilliseconds(0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function _getEndOfDay(date) {
|
||||
let ret = new Date(date.getTime());
|
||||
ret.setHours(23);
|
||||
ret.setMinutes(59);
|
||||
ret.setMilliseconds(999);
|
||||
return ret;
|
||||
}
|
||||
|
||||
function _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) {
|
||||
// Based on the algorithms found here:
|
||||
// http://en.wikipedia.org/wiki/Talk:ISO_week_date
|
||||
@ -106,14 +150,15 @@ function _getEventDayAbbreviation(dayNumber) {
|
||||
|
||||
// Abstraction for an appointment/task in a calendar
|
||||
|
||||
function CalendarTask(date, summary) {
|
||||
this._init(date, summary);
|
||||
function CalendarTask(date, summary, allDay) {
|
||||
this._init(date, summary, allDay);
|
||||
}
|
||||
|
||||
CalendarTask.prototype = {
|
||||
_init: function(date, summary) {
|
||||
_init: function(date, summary, allDay) {
|
||||
this.date = date;
|
||||
this.summary = summary;
|
||||
this.allDay = allDay;
|
||||
}
|
||||
};
|
||||
|
||||
@ -153,52 +198,56 @@ FakeEventSource.prototype = {
|
||||
|
||||
// Generate fake events
|
||||
//
|
||||
let now = new Date();
|
||||
let midnightToday = _getBeginningOfDay(new Date());
|
||||
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
|
||||
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);
|
||||
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
|
||||
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);
|
||||
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)
|
||||
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.setMinutes(45);
|
||||
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) :-)
|
||||
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'
|
||||
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
|
||||
// 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.setMinutes(30);
|
||||
transientEventDate.setSeconds(0);
|
||||
Mainloop.timeout_add(5000, Lang.bind(this, this._updateTransientEvent));
|
||||
this._includeTransientEvent = false;
|
||||
this._transientEvent = new CalendarTask(transientEventDate, 'A Transient Event');
|
||||
this._transientEvent = new CalendarTask(transientEventDate, 'A Transient Event', false);
|
||||
this._transientEventCounter = 1;
|
||||
},
|
||||
|
||||
@ -223,16 +272,15 @@ FakeEventSource.prototype = {
|
||||
}
|
||||
if (this._includeTransientEvent && this._transientEvent.date >= begin && this._transientEvent.date <= end)
|
||||
result.push(this._transientEvent);
|
||||
result.sort(function(task1, task2) {
|
||||
return task1.date.getTime() - task2.date.getTime();
|
||||
});
|
||||
return result;
|
||||
},
|
||||
|
||||
hasTasks: function(day) {
|
||||
let dayBegin = new Date(day.getTime());
|
||||
let dayEnd = new Date(day.getTime());
|
||||
dayBegin.setHours(0);
|
||||
dayBegin.setMinutes(1);
|
||||
dayEnd.setHours(23);
|
||||
dayEnd.setMinutes(59);
|
||||
let dayBegin = _getBeginningOfDay(day);
|
||||
let dayEnd = _getEndOfDay(day);
|
||||
|
||||
let tasks = this.getTasks(dayBegin, dayEnd);
|
||||
|
||||
@ -280,6 +328,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';
|
||||
@ -417,6 +466,11 @@ Calendar.prototype = {
|
||||
},
|
||||
|
||||
_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);
|
||||
|
||||
// Remove everything but the topBox and the weekday labels
|
||||
@ -432,8 +486,6 @@ Calendar.prototype = {
|
||||
let daysToWeekStart = (7 + iter.getDay() - this._weekStart) % 7;
|
||||
iter.setTime(iter.getTime() - daysToWeekStart * MSECS_IN_DAY);
|
||||
|
||||
let now = new Date();
|
||||
|
||||
let row = 2;
|
||||
while (true) {
|
||||
let button = new St.Button({ label: iter.getDate().toString() });
|
||||
@ -500,6 +552,8 @@ EventsList.prototype = {
|
||||
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();
|
||||
},
|
||||
|
||||
@ -516,10 +570,12 @@ EventsList.prototype = {
|
||||
text: desc} ));
|
||||
},
|
||||
|
||||
_addPeriod: function(header, begin, end, includeDayName) {
|
||||
_addPeriod: function(header, begin, end, includeDayName, showNothingScheduled) {
|
||||
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;
|
||||
|
||||
let vbox = new St.BoxLayout( {vertical: true} );
|
||||
@ -538,50 +594,59 @@ EventsList.prototype = {
|
||||
for (let n = 0; n < tasks.length; n++) {
|
||||
let task = tasks[n];
|
||||
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;
|
||||
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) {
|
||||
this.actor.destroy_children();
|
||||
|
||||
let dayBegin = new Date(day.getTime());
|
||||
let dayEnd = new Date(day.getTime());
|
||||
dayBegin.setHours(0);
|
||||
dayBegin.setMinutes(1);
|
||||
dayEnd.setHours(23);
|
||||
dayEnd.setMinutes(59);
|
||||
this._addPeriod(day.toLocaleFormat('%A, %B %d, %Y'), dayBegin, dayEnd, false);
|
||||
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 dayBegin = new Date();
|
||||
let dayEnd = new Date();
|
||||
dayBegin.setHours(0);
|
||||
dayBegin.setMinutes(1);
|
||||
dayEnd.setHours(23);
|
||||
dayEnd.setMinutes(59);
|
||||
this._addPeriod(_("Today"), dayBegin, dayEnd, false);
|
||||
let now = new Date();
|
||||
let dayBegin = _getBeginningOfDay(now);
|
||||
let dayEnd = _getEndOfDay(now);
|
||||
this._addPeriod(_("Today"), dayBegin, dayEnd, false, true);
|
||||
|
||||
dayBegin.setDate(dayBegin.getDate() + 1);
|
||||
dayEnd.setDate(dayEnd.getDate() + 1);
|
||||
this._addPeriod(_("Tomorrow"), dayBegin, dayEnd, false);
|
||||
this._addPeriod(_("Tomorrow"), dayBegin, dayEnd, false, true);
|
||||
|
||||
if (dayEnd.getDay() == 6 || dayEnd.getDay() == 0) {
|
||||
dayBegin.setDate(dayEnd.getDate() + 1);
|
||||
dayEnd.setDate(dayBegin.getDate() + 6 - dayBegin.getDay());
|
||||
|
||||
this._addPeriod(_("Next week"), dayBegin, dayEnd, true);
|
||||
this._addPeriod(_("Next week"), dayBegin, dayEnd, true, true);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let d = 6 - dayEnd.getDay() - 1;
|
||||
dayBegin.setDate(dayBegin.getDate() + 1);
|
||||
dayEnd.setDate(dayEnd.getDate() + 1 + d);
|
||||
this._addPeriod(_("This week"), dayBegin, dayEnd, true);
|
||||
this._addPeriod(_("This week"), dayBegin, dayEnd, true, true);
|
||||
}
|
||||
},
|
||||
|
||||
// Sets the event list to show events from a specific date
|
||||
|
@ -55,8 +55,8 @@ DateMenuButton.prototype = {
|
||||
let hbox;
|
||||
let vbox;
|
||||
|
||||
this._eventSource = new Calendar.EmptyEventSource();
|
||||
//this._eventSource = new Calendar.FakeEventSource();
|
||||
//this._eventSource = new Calendar.EmptyEventSource();
|
||||
this._eventSource = new Calendar.FakeEventSource();
|
||||
// TODO: write e.g. EvolutionEventSource
|
||||
|
||||
PanelMenu.Button.prototype._init.call(this, St.Align.START);
|
||||
@ -64,12 +64,12 @@ DateMenuButton.prototype = {
|
||||
this._clock = new St.Label();
|
||||
this.actor.set_child(this._clock);
|
||||
|
||||
hbox = new St.BoxLayout({name: 'calendarHBox'});
|
||||
hbox = new St.BoxLayout();
|
||||
this.menu.addActor(hbox);
|
||||
|
||||
// Fill up the first column
|
||||
|
||||
vbox = new St.BoxLayout({vertical: true, name: 'calendarVBox1'});
|
||||
vbox = new St.BoxLayout({vertical: true});
|
||||
hbox.add(vbox);
|
||||
|
||||
// Date
|
||||
@ -87,18 +87,33 @@ DateMenuButton.prototype = {
|
||||
}));
|
||||
vbox.add(this._calendar.actor);
|
||||
|
||||
//item = new St.Button({style_class: 'popup-menu-item', label: 'foobar'});
|
||||
//vbox.add(item);
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
item.actor.remove_actor(item._drawingArea);
|
||||
vbox.add(item._drawingArea);
|
||||
item = new PopupMenu.PopupMenuItem(_("Date and Time Settings"));
|
||||
item.connect('activate', Lang.bind(this, this._onPreferencesActivate));
|
||||
vbox.add(item.actor);
|
||||
|
||||
// Add vertical separator
|
||||
|
||||
item = new St.DrawingArea({ style_class: 'calendar-vertical-separator',
|
||||
pseudo_class: 'highlighted' });
|
||||
item.set_width(25); // TODO: don't hard-code the width
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
// Add separator
|
||||
item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
this.menu.addMenuItem(item);
|
||||
//item = new PopupMenu.PopupSeparatorMenuItem();
|
||||
//this.menu.addMenuItem(item);
|
||||
|
||||
// Add button to get to the Date and Time settings
|
||||
this.menu.addAction(_("Date and Time Settings"),
|
||||
Lang.bind(this, this._onPreferencesActivate));
|
||||
//this.menu.addAction(_("Date and Time Settings"),
|
||||
// Lang.bind(this, this._onPreferencesActivate));
|
||||
|
||||
// Track changes to clock settings
|
||||
this._desktopSettings = new Gio.Settings({ schema: 'org.gnome.desktop.interface' });
|
||||
@ -185,8 +200,10 @@ DateMenuButton.prototype = {
|
||||
|
||||
this._clock.set_text(displayDate.toLocaleFormat(clockFormat));
|
||||
|
||||
/* Translators: This is the date format to use */
|
||||
dateFormat = _("%B %e, %Y");
|
||||
/* 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));
|
||||
@ -194,6 +211,14 @@ DateMenuButton.prototype = {
|
||||
},
|
||||
|
||||
_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');
|
||||
},
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user