diff --git a/data/theme/gnome-shell-sass/widgets/_calendar.scss b/data/theme/gnome-shell-sass/widgets/_calendar.scss index e99b8a82e37f8d75c671fd4cd3d857acfe0d144b..70d95193a987884ecfd1be572876141b102d0e98 100644 --- a/data/theme/gnome-shell-sass/widgets/_calendar.scss +++ b/data/theme/gnome-shell-sass/widgets/_calendar.scss @@ -177,6 +177,32 @@ } } +/* Events */ +.events-button { + @include notification_bubble; + padding: $base_padding * 2; + + .events-box { + spacing: $base_spacing; + } + + .events-list { + spacing: 2 * $base_spacing; + } + + .events-title { + color: desaturate(darken($fg_color,40%), 10%); + font-weight: bold; + margin-bottom: $base_margin; + } + + .event-time { + color: darken($fg_color,20%); + font-feature-settings: "tnum"; + @include fontsize($base_font_size - 1); + } +} + /* World clocks */ .world-clocks-button { @include notification_bubble; diff --git a/js/ui/calendar.js b/js/ui/calendar.js index 35308c3597f03eab1a93b4e1dcacd092436030a1..6a5f024f71161d32f61743a9717fd4dc26b378a1 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -14,7 +14,6 @@ const { loadInterfaceXML } = imports.misc.fileUtils; var MSECS_IN_DAY = 24 * 60 * 60 * 1000; var SHOW_WEEKDATE_KEY = 'show-weekdate'; -var ELLIPSIS_CHAR = '\u2026'; var MESSAGE_ICON_SIZE = -1; // pick up from CSS @@ -32,10 +31,6 @@ function sameDay(dateA, dateB) { return sameMonth(dateA, dateB) && (dateA.getDate() == dateB.getDate()); } -function isToday(date) { - return sameDay(new Date(), date); -} - function _isWorkDay(date) { /* Translators: Enter 0-6 (Sunday-Saturday) for non-work days. Examples: "0" (Sunday) "6" (Saturday) "06" (Sunday and Saturday). */ let days = C_('calendar-no-work', "06"); @@ -723,67 +718,6 @@ var Calendar = GObject.registerClass({ } }); -var EventMessage = GObject.registerClass( -class EventMessage extends MessageList.Message { - _init(event, date) { - super._init('', ''); - - this._date = date; - - this.update(event); - - this._icon = new St.Icon({ icon_name: 'x-office-calendar-symbolic' }); - this.setIcon(this._icon); - } - - vfunc_style_changed() { - let iconVisible = this.get_parent().has_style_pseudo_class('first-child'); - this._icon.opacity = iconVisible ? 255 : 0; - super.vfunc_style_changed(); - } - - update(event) { - this._event = event; - - this.setTitle(this._formatEventTime()); - this.setBody(event.summary); - } - - _formatEventTime() { - let periodBegin = _getBeginningOfDay(this._date); - let periodEnd = _getEndOfDay(this._date); - let allDay = this._event.allDay || (this._event.date <= periodBegin && - this._event.end >= periodEnd); - let title; - if (allDay) { - /* Translators: Shown in calendar event list for all day events - * Keep it short, best if you can use less then 10 characters - */ - title = C_("event list time", "All Day"); - } else { - let date = this._event.date >= periodBegin - ? this._event.date - : this._event.end; - title = Util.formatTime(date, { timeOnly: true }); - } - - let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL; - if (this._event.date < periodBegin && !this._event.allDay) { - if (rtl) - title = '%s%s'.format(title, ELLIPSIS_CHAR); - else - title = '%s%s'.format(ELLIPSIS_CHAR, title); - } - if (this._event.end > periodEnd && !this._event.allDay) { - if (rtl) - title = '%s%s'.format(ELLIPSIS_CHAR, title); - else - title = '%s%s'.format(title, ELLIPSIS_CHAR); - } - return title; - } -}); - var NotificationMessage = GObject.registerClass( class NotificationMessage extends MessageList.Message { _init(notification) { @@ -849,149 +783,6 @@ class NotificationMessage extends MessageList.Message { } }); -var EventsSection = GObject.registerClass( -class EventsSection extends MessageList.MessageListSection { - _init() { - super._init(); - - this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' }); - this._desktopSettings.connect('changed', this._reloadEvents.bind(this)); - this._eventSource = new EmptyEventSource(); - - this._messageById = new Map(); - - this._title = new St.Button({ style_class: 'events-section-title', - label: '', - can_focus: true }); - this._title.child.x_align = Clutter.ActorAlign.START; - this.insert_child_below(this._title, null); - - this._title.connect('clicked', this._onTitleClicked.bind(this)); - this._title.connect('key-focus-in', this._onKeyFocusIn.bind(this)); - - this._appSys = Shell.AppSystem.get_default(); - this._appSys.connect('installed-changed', - this._appInstalledChanged.bind(this)); - this._appInstalledChanged(); - } - - setEventSource(eventSource) { - if (!(eventSource instanceof EventSourceBase)) - throw new Error('Event source is not valid type'); - - this._eventSource = eventSource; - this._eventSource.connect('changed', this._reloadEvents.bind(this)); - } - - get allowed() { - return Main.sessionMode.showCalendarEvents; - } - - _updateTitle() { - this._title.visible = !isToday(this._date); - - if (!this._title.visible) - return; - - let dayFormat; - let now = new Date(); - if (sameYear(this._date, now)) { - /* Translators: Shown on calendar heading when selected day occurs on current year */ - dayFormat = Shell.util_translate_time_string(NC_("calendar heading", "%A, %B %-d")); - } else { - /* Translators: Shown on calendar heading when selected day occurs on different year */ - dayFormat = Shell.util_translate_time_string(NC_("calendar heading", "%A, %B %-d, %Y")); - } - this._title.label = this._date.toLocaleFormat(dayFormat); - } - - _reloadEvents() { - if (this._eventSource.isLoading || this._reloading) - return; - - this._reloading = true; - - let periodBegin = _getBeginningOfDay(this._date); - let periodEnd = _getEndOfDay(this._date); - let events = this._eventSource.getEvents(periodBegin, periodEnd); - - let ids = events.map(e => e.id); - this._messageById.forEach((message, id) => { - if (ids.includes(id)) - return; - this._messageById.delete(id); - this.removeMessage(message); - }); - - for (let i = 0; i < events.length; i++) { - let event = events[i]; - - let message = this._messageById.get(event.id); - if (!message) { - message = new EventMessage(event, this._date); - this._messageById.set(event.id, message); - this.addMessage(message, false); - } else { - message.update(event); - this.moveMessage(message, i, false); - } - } - - this._reloading = false; - this._sync(); - } - - _appInstalledChanged() { - this._calendarApp = undefined; - this._title.reactive = this._getCalendarApp() != null; - } - - _getCalendarApp() { - if (this._calendarApp !== undefined) - return this._calendarApp; - - let apps = Gio.AppInfo.get_recommended_for_type('text/calendar'); - if (apps && (apps.length > 0)) { - let app = Gio.AppInfo.get_default_for_type('text/calendar', false); - let defaultInRecommended = apps.some(a => a.equal(app)); - this._calendarApp = defaultInRecommended ? app : apps[0]; - } else { - this._calendarApp = null; - } - return this._calendarApp; - } - - _onTitleClicked() { - Main.overview.hide(); - Main.panel.closeCalendar(); - - let appInfo = this._getCalendarApp(); - if (appInfo.get_id() === 'org.gnome.Evolution.desktop') { - let app = this._appSys.lookup_app('evolution-calendar.desktop'); - if (app) - appInfo = app.app_info; - } - appInfo.launch([], global.create_app_launch_context(0, -1)); - } - - setDate(date) { - super.setDate(date); - this._updateTitle(); - this._reloadEvents(); - } - - _shouldShow() { - return !this.empty || !isToday(this._date); - } - - _sync() { - if (this._reloading) - return; - - super._sync(); - } -}); - var TimeLabel = GObject.registerClass( class NotificationTimeLabel extends St.Label { _init(datetime) { @@ -1088,10 +879,6 @@ class NotificationSection extends MessageList.MessageListSection { }); super.vfunc_map(); } - - _shouldShow() { - return !this.empty && isToday(this._date); - } }); var Placeholder = GObject.registerClass( @@ -1100,41 +887,13 @@ class Placeholder extends St.BoxLayout { super._init({ style_class: 'message-list-placeholder', vertical: true }); this._date = new Date(); - let todayFile = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/no-notifications.svg'); - let otherFile = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/no-events.svg'); - this._todayIcon = new Gio.FileIcon({ file: todayFile }); - this._otherIcon = new Gio.FileIcon({ file: otherFile }); - - this._icon = new St.Icon(); + const file = Gio.File.new_for_uri( + 'resource:///org/gnome/shell/theme/no-notifications.svg'); + this._icon = new St.Icon({ gicon: new Gio.FileIcon({ file }) }); this.add_actor(this._icon); - this._label = new St.Label(); + this._label = new St.Label({ text: _('No Notifications') }); this.add_actor(this._label); - - this._sync(); - } - - setDate(date) { - if (sameDay(this._date, date)) - return; - this._date = date; - this._sync(); - } - - _sync() { - let today = isToday(this._date); - if (today && this._icon.gicon == this._todayIcon) - return; - if (!today && this._icon.gicon == this._otherIcon) - return; - - if (today) { - this._icon.gicon = this._todayIcon; - this._label.text = _("No Notifications"); - } else { - this._icon.gicon = this._otherIcon; - this._label.text = _("No Events"); - } } }); @@ -1235,9 +994,6 @@ class CalendarMessageList extends St.Widget { this._notificationSection = new NotificationSection(); this._addSection(this._notificationSection); - this._eventsSection = new EventsSection(); - this._addSection(this._eventsSection); - Main.sessionMode.connect('updated', this._sync.bind(this)); } @@ -1273,13 +1029,4 @@ class CalendarMessageList extends St.Widget { let canClear = sections.some(s => s.canClear && s.visible); this._clearButton.reactive = canClear; } - - setEventSource(eventSource) { - this._eventsSection.setEventSource(eventSource); - } - - setDate(date) { - this._sectionList.get_children().forEach(s => s.setDate(date)); - this._placeholder.setDate(date); - } }); diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js index 034050d19f88914443d8026403b857d77e1c2e2f..ce0a1f6173d253ecee133def3fd8c41cfb7e6f3f 100644 --- a/js/ui/dateMenu.js +++ b/js/ui/dateMenu.js @@ -13,7 +13,11 @@ const System = imports.system; const { loadInterfaceXML } = imports.misc.fileUtils; +const NC_ = (context, str) => '%s\u0004%s'.format(context, str); +const T_ = Shell.util_translate_time_string; + const MAX_FORECASTS = 5; +const ELLIPSIS_CHAR = '\u2026'; const ClocksIntegrationIface = loadInterfaceXML('org.gnome.Shell.ClocksIntegration'); const ClocksProxy = Gio.DBusProxy.makeProxyWrapper(ClocksIntegrationIface); @@ -84,6 +88,188 @@ class TodayButton extends St.Button { } }); +var EventsSection = GObject.registerClass( +class EventsSection extends St.Button { + _init() { + super._init({ + style_class: 'events-button', + can_focus: true, + x_expand: true, + child: new St.BoxLayout({ + style_class: 'events-box', + vertical: true, + x_expand: true, + }), + }); + + this._startDate = null; + this._endDate = null; + + this._eventSource = null; + this._calendarApp = null; + + this._title = new St.Label({ + style_class: 'events-title', + }); + this.child.add_child(this._title); + + this._eventsList = new St.BoxLayout({ + style_class: 'events-list', + vertical: true, + x_expand: true, + }); + this.child.add_child(this._eventsList); + + this._appSys = Shell.AppSystem.get_default(); + this._appSys.connect('installed-changed', + this._appInstalledChanged.bind(this)); + this._appInstalledChanged(); + } + + setDate(date) { + const day = [date.getFullYear(), date.getMonth(), date.getDate()]; + this._startDate = new Date(...day); + this._endDate = new Date(...day, 23, 59, 59, 999); + + this._updateTitle(); + this._reloadEvents(); + } + + setEventSource(eventSource) { + if (!(eventSource instanceof Calendar.EventSourceBase)) + throw new Error('Event source is not valid type'); + + this._eventSource = eventSource; + this._eventSource.connect('changed', this._reloadEvents.bind(this)); + this._eventSource.connect('notify::has-calendars', + this._sync.bind(this)); + this._sync(); + } + + _updateTitle() { + /* Translators: Shown on calendar heading when selected day occurs on current year */ + const sameYearFormat = T_(NC_('calendar heading', '%B %-d')); + + /* Translators: Shown on calendar heading when selected day occurs on different year */ + const otherYearFormat = T_(NC_('calendar heading', '%B %-d %Y')); + + const timeSpanDay = GLib.TIME_SPAN_DAY / 1000; + const now = new Date(); + + if (this._startDate <= now && now <= this._endDate) + this._title.text = _('Today'); + else if (this._endDate < now && now - this._endDate < timeSpanDay) + this._title.text = _('Yesterday'); + else if (this._startDate > now && this._startDate - now < timeSpanDay) + this._title.text = _('Tomorrow'); + else if (this._startDate.getFullYear() === now.getFullYear()) + this._title.text = this._startDate.toLocaleFormat(sameYearFormat); + else + this._title.text = this._startDate.toLocaleFormat(otherYearFormat); + } + + _formatEventTime(event) { + const allDay = event.allDay || + (event.date <= this._startDate && event.end >= this._endDate); + + let title; + if (allDay) { + /* Translators: Shown in calendar event list for all day events + * Keep it short, best if you can use less then 10 characters + */ + title = C_('event list time', 'All Day'); + } else { + let date = event.date >= this._startDate ? event.date : event.end; + title = Util.formatTime(date, { timeOnly: true }); + } + + const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL; + if (event.date < this._startDate && !event.allDay) { + if (rtl) + title = '%s%s'.format(title, ELLIPSIS_CHAR); + else + title = '%s%s'.format(ELLIPSIS_CHAR, title); + } + if (event.end > this._endDate && !event.allDay) { + if (rtl) + title = '%s%s'.format(ELLIPSIS_CHAR, title); + else + title = '%s%s'.format(title, ELLIPSIS_CHAR); + } + return title; + } + + _reloadEvents() { + if (this._eventSource.isLoading || this._reloading) + return; + + this._reloading = true; + + [...this._eventsList].forEach(c => c.destroy()); + + const events = + this._eventSource.getEvents(this._startDate, this._endDate); + + for (let event of events) { + const box = new St.BoxLayout({ + style_class: 'event-box', + vertical: true, + }); + box.add(new St.Label({ + text: event.summary, + style_class: 'event-summary', + })); + box.add(new St.Label({ + text: this._formatEventTime(event), + style_class: 'event-time', + })); + this._eventsList.add_child(box); + } + + if (this._eventsList.get_n_children() === 0) { + const placeholder = new St.Label({ + text: _('No Events'), + style_class: 'event-placeholder', + }); + this._eventsList.add_child(placeholder); + } + + this._reloading = false; + this._sync(); + } + + vfunc_clicked() { + Main.overview.hide(); + Main.panel.closeCalendar(); + + let appInfo = this._calendarApp; + if (appInfo.get_id() === 'org.gnome.Evolution.desktop') { + const app = this._appSys.lookup_app('evolution-calendar.desktop'); + if (app) + appInfo = app.app_info; + } + appInfo.launch([], global.create_app_launch_context(0, -1)); + } + + _appInstalledChanged() { + const apps = Gio.AppInfo.get_recommended_for_type('text/calendar'); + if (apps && (apps.length > 0)) { + const app = Gio.AppInfo.get_default_for_type('text/calendar', false); + const defaultInRecommended = apps.some(a => a.equal(app)); + this._calendarApp = defaultInRecommended ? app : apps[0]; + } else { + this._calendarApp = null; + } + + return this._sync(); + } + + _sync() { + this.visible = this._eventSource && this._eventSource.hasCalendars; + this.reactive = this._calendarApp !== null; + } +}); + var WorldClocksSection = GObject.registerClass( class WorldClocksSection extends St.Button { _init() { @@ -632,7 +818,7 @@ class DateMenuButton extends PanelMenu.Button { this._calendar.connect('selected-date-changed', (_calendar, datetime) => { let date = _gDateTimeToDate(datetime); layout.frozen = !_isToday(date); - this._messageList.setDate(date); + this._eventsItem.setDate(date); }); this.menu.connect('open-state-changed', (menu, isOpen) => { @@ -641,7 +827,7 @@ class DateMenuButton extends PanelMenu.Button { let now = new Date(); this._calendar.setDate(now); this._date.setDate(now); - this._messageList.setDate(now); + this._eventsItem.setDate(now); } }); @@ -672,6 +858,9 @@ class DateMenuButton extends PanelMenu.Button { style_class: 'datemenu-displays-box' }); this._displaysSection.add_actor(displaysBox); + this._eventsItem = new EventsSection(); + displaysBox.add_child(this._eventsItem); + this._clocksItem = new WorldClocksSection(); displaysBox.add_child(this._clocksItem); @@ -697,7 +886,7 @@ class DateMenuButton extends PanelMenu.Button { this._eventSource.destroy(); this._calendar.setEventSource(eventSource); - this._messageList.setEventSource(eventSource); + this._eventsItem.setEventSource(eventSource); this._eventSource = eventSource; } diff --git a/js/ui/messageList.js b/js/ui/messageList.js index fb94767178c3e72069560f9686423968a0f8cbd7..a79f87bb4e3041b21e96a3d24c61b780bac728a1 100644 --- a/js/ui/messageList.js +++ b/js/ui/messageList.js @@ -4,7 +4,6 @@ const { Atk, Clutter, Gio, GLib, const Main = imports.ui.main; const MessageTray = imports.ui.messageTray; -const Calendar = imports.ui.calendar; const Util = imports.misc.util; var MESSAGE_ANIMATION_TIME = 100; @@ -572,7 +571,6 @@ var MessageListSection = GObject.registerClass({ Main.sessionMode.disconnect(id); }); - this._date = new Date(); this._empty = true; this._canClear = false; this._sync(); @@ -598,13 +596,6 @@ var MessageListSection = GObject.registerClass({ return true; } - setDate(date) { - if (Calendar.sameDay(date, this._date)) - return; - this._date = date; - this._sync(); - } - addMessage(message, animate) { this.addMessageAtIndex(message, -1, animate); } diff --git a/js/ui/mpris.js b/js/ui/mpris.js index 23fca91b18e95648d79c47003bf0ac5be118aeb4..3650c577a3f4407a977a86986cf3cc22a9bfe4f2 100644 --- a/js/ui/mpris.js +++ b/js/ui/mpris.js @@ -2,7 +2,6 @@ const { Gio, GObject, Shell, St } = imports.gi; const Signals = imports.signals; -const Calendar = imports.ui.calendar; const Main = imports.ui.main; const MessageList = imports.ui.messageList; @@ -252,10 +251,6 @@ class MediaSection extends MessageList.MessageListSection { this._onProxyReady.bind(this)); } - _shouldShow() { - return !this.empty && Calendar.isToday(this._date); - } - get allowed() { return !Main.sessionMode.isGreeter; }