[gnome-shell] dateMenu: Add "Events" section



commit fdd9def9222f5dec61812a8159f5af263ba99b08
Author: Florian Müllner <fmuellner gnome org>
Date:   Thu May 14 22:24:36 2020 +0200

    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

 data/theme/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;
     }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]