From a1440bdec721227994dbf89f9bea996e74e40ae0 Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Tue, 25 Jan 2011 09:55:41 -0500 Subject: [PATCH] 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 --- data/theme/gnome-shell.css | 28 ++---- js/ui/calendar.js | 169 +++++++++++++++++++++++++------------ js/ui/dateMenu.js | 49 ++++++++--- 3 files changed, 162 insertions(+), 84 deletions(-) diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css index 46962622e..5572a20b1 100644 --- a/data/theme/gnome-shell.css +++ b/data/theme/gnome-shell.css @@ -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; diff --git a/js/ui/calendar.js b/js/ui/calendar.js index aea20defe..2e89479ca 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -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); + 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._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); + 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._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); + 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._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,7 +466,12 @@ Calendar.prototype = { }, _update: function() { - this._dateLabel.text = this.selectedDate.toLocaleFormat(this._headerFormat); + 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 let children = this.actor.get_children(); @@ -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, true); } - let d = 6 - dayEnd.getDay() - 1; - dayBegin.setDate(dayBegin.getDate() + 1); - dayEnd.setDate(dayEnd.getDate() + 1 + d); - this._addPeriod(_("This week"), dayBegin, dayEnd, true); }, // Sets the event list to show events from a specific date diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js index 8616735d4..35f655877 100644 --- a/js/ui/dateMenu.js +++ b/js/ui/dateMenu.js @@ -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'); + }, };