From d36a1808528748e393ee740d339da8754b1ad8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Mon, 17 Dec 2018 19:08:38 +0100 Subject: [PATCH 1/5] calendar: Remove events section from message list While treating notifications as a type of present event was a neat concept, there are some issues with it that we never managed to address (not least the inability to "open" an event). So remove the current events section from the message list; we'll bring back events in a different form later. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1282 --- js/ui/calendar.js | 212 ---------------------------------------------- js/ui/dateMenu.js | 1 - 2 files changed, 213 deletions(-) diff --git a/js/ui/calendar.js b/js/ui/calendar.js index 35308c3597..e048cd086c 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 @@ -723,67 +722,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 +787,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) { @@ -1235,9 +1030,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)); } @@ -1274,10 +1066,6 @@ class CalendarMessageList extends St.Widget { 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 034050d19f..681968dc11 100644 --- a/js/ui/dateMenu.js +++ b/js/ui/dateMenu.js @@ -697,7 +697,6 @@ class DateMenuButton extends PanelMenu.Button { this._eventSource.destroy(); this._calendar.setEventSource(eventSource); - this._messageList.setEventSource(eventSource); this._eventSource = eventSource; } -- GitLab From ff55cf017e5f495d6ab7912342b55e2d13ee2737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 18 Dec 2018 11:16:24 +0100 Subject: [PATCH 2/5] calendar: Don't restrict section visibility by date The idea behind hiding the notifications and media section on days other than today was that they represent present activity together with today's events, in contrast to past and future events from other days. After events were moved out of the message list, that behavior is no longer useful: We just guarantee that the left-hand side of the calendar will always be empty when browsing the calendar. Adjust to that by removing the limitation by date. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1282 --- js/ui/calendar.js | 4 ---- js/ui/mpris.js | 5 ----- 2 files changed, 9 deletions(-) diff --git a/js/ui/calendar.js b/js/ui/calendar.js index e048cd086c..7eb4519166 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -883,10 +883,6 @@ class NotificationSection extends MessageList.MessageListSection { }); super.vfunc_map(); } - - _shouldShow() { - return !this.empty && isToday(this._date); - } }); var Placeholder = GObject.registerClass( diff --git a/js/ui/mpris.js b/js/ui/mpris.js index 23fca91b18..3650c577a3 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; } -- GitLab From 8451df977c76af4e9cc232946a8f02ef14bd0071 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 18 Dec 2018 11:20:42 +0100 Subject: [PATCH 3/5] calendar: Simplify placeholder Without the events section in the message list, the placeholder can only reflect whether or not there are notifications. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1282 --- js/ui/calendar.js | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/js/ui/calendar.js b/js/ui/calendar.js index 7eb4519166..ef3a282404 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -31,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"); @@ -891,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"); - } } }); @@ -1064,6 +1032,5 @@ class CalendarMessageList extends St.Widget { setDate(date) { this._sectionList.get_children().forEach(s => s.setDate(date)); - this._placeholder.setDate(date); } }); -- GitLab From 771050f4d789b93fa08729e4f452f8f834397182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Tue, 18 Dec 2018 11:26:30 +0100 Subject: [PATCH 4/5] messageList: Remove setDate() method Since the events section has been removed and visibility no longer depends on the date, it's not used by anything anymore. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1282 --- js/ui/calendar.js | 4 ---- js/ui/dateMenu.js | 2 -- js/ui/messageList.js | 9 --------- 3 files changed, 15 deletions(-) diff --git a/js/ui/calendar.js b/js/ui/calendar.js index ef3a282404..6a5f024f71 100644 --- a/js/ui/calendar.js +++ b/js/ui/calendar.js @@ -1029,8 +1029,4 @@ class CalendarMessageList extends St.Widget { let canClear = sections.some(s => s.canClear && s.visible); this._clearButton.reactive = canClear; } - - setDate(date) { - this._sectionList.get_children().forEach(s => s.setDate(date)); - } }); diff --git a/js/ui/dateMenu.js b/js/ui/dateMenu.js index 681968dc11..a98b5eec59 100644 --- a/js/ui/dateMenu.js +++ b/js/ui/dateMenu.js @@ -632,7 +632,6 @@ 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.menu.connect('open-state-changed', (menu, isOpen) => { @@ -641,7 +640,6 @@ class DateMenuButton extends PanelMenu.Button { let now = new Date(); this._calendar.setDate(now); this._date.setDate(now); - this._messageList.setDate(now); } }); diff --git a/js/ui/messageList.js b/js/ui/messageList.js index fb94767178..a79f87bb4e 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); } -- GitLab From fdd9def9222f5dec61812a8159f5af263ba99b08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Thu, 14 May 2020 22:24:36 +0200 Subject: [PATCH 5/5] dateMenu: Add "Events" section Events have a clear and obvious connection to the calendar, and similar to the Clocks and Weather sections there's a strong link to a particular application. Adding them as another section to the right-hand side of the calendar therefore presents a viable alternative to the old events section. https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/1282 --- .../gnome-shell-sass/widgets/_calendar.scss | 26 +++ js/ui/dateMenu.js | 192 ++++++++++++++++++ 2 files changed, 218 insertions(+) diff --git a/data/theme/gnome-shell-sass/widgets/_calendar.scss b/data/theme/gnome-shell-sass/widgets/_calendar.scss index e99b8a82e3..70d95193a9 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/dateMenu.js b/js/ui/dateMenu.js index a98b5eec59..ce0a1f6173 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,6 +818,7 @@ class DateMenuButton extends PanelMenu.Button { this._calendar.connect('selected-date-changed', (_calendar, datetime) => { let date = _gDateTimeToDate(datetime); layout.frozen = !_isToday(date); + this._eventsItem.setDate(date); }); this.menu.connect('open-state-changed', (menu, isOpen) => { @@ -640,6 +827,7 @@ class DateMenuButton extends PanelMenu.Button { let now = new Date(); this._calendar.setDate(now); this._date.setDate(now); + this._eventsItem.setDate(now); } }); @@ -670,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); @@ -695,6 +886,7 @@ class DateMenuButton extends PanelMenu.Button { this._eventSource.destroy(); this._calendar.setEventSource(eventSource); + this._eventsItem.setEventSource(eventSource); this._eventSource = eventSource; } -- GitLab