[gnome-shell] ScreenShield: decouple detailed notifications from resident notifications



commit 8cb3884fae67a20a2b87f442479cfdb5feb8a9c8
Author: Giovanni Campagna <gcampagna src gnome org>
Date:   Wed Jan 30 19:47:55 2013 +0100

    ScreenShield: decouple detailed notifications from resident notifications
    
    The designs says that only music notifications should be shown in full
    in the screenshield, the others should be either shown as a summary or
    with very light details.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=685926

 data/theme/gnome-shell.css  |    1 +
 js/ui/messageTray.js        |   42 ++++---
 js/ui/notificationDaemon.js |    3 +
 js/ui/screenShield.js       |  266 ++++++++++++++++++++++++++-----------------
 4 files changed, 188 insertions(+), 124 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 41a9557..e1c5fb7 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -2417,6 +2417,7 @@ StScrollBar StButton#vhandle:active {
 
 .screen-shield-notifications-box {
     spacing: 18px;
+    max-width: 34em;
 }
 
 .screen-shield-notification-source {
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index 103248c..6cc5729 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -345,6 +345,7 @@ const Notification = new Lang.Class({
         this.resident = false;
         // 'transient' is a reserved keyword in JS, so we have to use an alternate variable name
         this.isTransient = false;
+        this.isMusic = false;
         this.forFeedback = false;
         this.expanded = false;
         this.focused = false;
@@ -352,8 +353,8 @@ const Notification = new Lang.Class({
         this._destroyed = false;
         this._useActionIcons = false;
         this._customContent = false;
-        this._bannerBodyText = null;
-        this._bannerBodyMarkup = false;
+        this.bannerBodyText = null;
+        this.bannerBodyMarkup = false;
         this._titleFitsInBannerMode = true;
         this._titleDirection = Clutter.TextDirection.DEFAULT;
         this._spacing = 0;
@@ -505,12 +506,12 @@ const Notification = new Lang.Class({
         // is done correctly automatically.
         this._table.set_text_direction(this._titleDirection);
 
-        // Unless the notification has custom content, we save this._bannerBodyText
+        // Unless the notification has custom content, we save this.bannerBodyText
         // to add it to the content of the notification if the notification is
         // expandable due to other elements in its content area or due to the banner
         // not fitting fully in the single-line mode.
-        this._bannerBodyText = this._customContent ? null : banner;
-        this._bannerBodyMarkup = params.bannerMarkup;
+        this.bannerBodyText = this._customContent ? null : banner;
+        this.bannerBodyMarkup = params.bannerMarkup;
 
         banner = banner ? banner.replace(/\n/g, '  ') : '';
 
@@ -518,7 +519,7 @@ const Notification = new Lang.Class({
         this._bannerLabel.queue_relayout();
 
         // Add the bannerBody now if we know for sure we'll need it
-        if (this._bannerBodyText && this._bannerBodyText.indexOf('\n') > -1)
+        if (this.bannerBodyText && this.bannerBodyText.indexOf('\n') > -1)
             this._addBannerBody();
 
         if (params.body)
@@ -584,10 +585,10 @@ const Notification = new Lang.Class({
     },
 
     _addBannerBody: function() {
-        if (this._bannerBodyText) {
-            let text = this._bannerBodyText;
-            this._bannerBodyText = null;
-            this.addBody(text, this._bannerBodyMarkup);
+        if (this.bannerBodyText) {
+            let text = this.bannerBodyText;
+            this.bannerBodyText = null;
+            this.addBody(text, this.bannerBodyMarkup);
         }
     },
 
@@ -879,7 +880,7 @@ const Notification = new Lang.Class({
     },
 
     _canExpandContent: function() {
-        return this._bannerBodyText ||
+        return this.bannerBodyText ||
                (!this._titleFitsInBannerMode && !this._table.has_style_class_name('multi-line-notification'));
     },
 
@@ -1279,7 +1280,16 @@ const Source = new Lang.Class({
 
     hasResidentNotification: function() {
         return this.notifications.some(function(n) { return n.resident; });
-    }
+    },
+
+    getMusicNotification: function() {
+        for (let i = 0; i < this.notifications.length; i++) {
+            if (this.notifications[i].isMusic)
+                return this.notifications[i];
+        }
+
+        return null;
+    },
 });
 Signals.addSignalMethods(Source.prototype);
 
@@ -1777,7 +1787,7 @@ const MessageTray = new Lang.Class({
         // So postpone calling _updateState() a tiny bit.
         Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { this._updateState(); return false; }));
 
-        this.emit('summary-item-added', summaryItem);
+        this.emit('source-added', source);
 
         this._updateNoMessagesLabel();
     },
@@ -1813,10 +1823,8 @@ const MessageTray = new Lang.Class({
             this._updateState();
     },
 
-    getSummaryItems: function() {
-        return this._sources.values().map(function(v) {
-            return v.summaryItem;
-        });
+    getSources: function() {
+        return this._sources.keys();
     },
 
     _onSourceEnableChanged: function(policy, source) {
diff --git a/js/ui/notificationDaemon.js b/js/ui/notificationDaemon.js
index cbe2831..e02bf22 100644
--- a/js/ui/notificationDaemon.js
+++ b/js/ui/notificationDaemon.js
@@ -501,6 +501,9 @@ const NotificationDaemon = new Lang.Class({
                 }));
         }
 
+        // Mark music notifications so they can be shown in the screen shield
+        notification.isMusic = (ndata.hints['category'] == 'x-gnome.music');
+
         let gicon = this._iconForNotificationData(icon, hints);
         let gimage = this._imageForNotificationData(hints);
 
diff --git a/js/ui/screenShield.js b/js/ui/screenShield.js
index 38da9df..0367f60 100644
--- a/js/ui/screenShield.js
+++ b/js/ui/screenShield.js
@@ -13,6 +13,7 @@ const St = imports.gi.St;
 const TweenerEquations = imports.tweener.equations;
 
 const GnomeSession = imports.misc.gnomeSession;
+const Hash = imports.misc.hash;
 const Layout = imports.ui.layout;
 const LoginManager = imports.misc.loginManager;
 const Lightbox = imports.ui.lightbox;
@@ -127,49 +128,48 @@ const NotificationsBox = new Lang.Class({
                                         name: 'screenShieldNotifications',
                                         style_class: 'screen-shield-notifications-box' });
 
-        this._residentNotificationBox = new St.BoxLayout({ vertical: true,
-                                                           style_class: 'screen-shield-notifications-box' });
+        this._musicBin = new St.Bin({ style_class: 'screen-shield-notifications-box',
+                                      visible: false });
+
         let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START });
-        this._persistentNotificationBox = new St.BoxLayout({ vertical: true,
-                                                             style_class: 'screen-shield-notifications-box' });
-        scrollView.add_actor(this._persistentNotificationBox);
+        this._notificationBox = new St.BoxLayout({ vertical: true,
+                                                   style_class: 'screen-shield-notifications-box' });
+        scrollView.add_actor(this._notificationBox);
 
-        this.actor.add(this._residentNotificationBox, { x_fill: true });
+        this.actor.add(this._musicBin);
         this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START });
 
-        this._items = [];
-        Main.messageTray.getSummaryItems().forEach(Lang.bind(this, function(item) {
-            this._summaryItemAdded(Main.messageTray, item, true);
+        this._sources = new Hash.Map();
+        Main.messageTray.getSources().forEach(Lang.bind(this, function(source) {
+            this._sourceAdded(Main.messageTray, source, true);
         }));
         this._updateVisibility();
 
-        this._summaryAddedId = Main.messageTray.connect('summary-item-added', Lang.bind(this, this._summaryItemAdded));
+        this._sourceAddedId = Main.messageTray.connect('source-added', Lang.bind(this, this._sourceAdded));
     },
 
     destroy: function() {
-        if (this._summaryAddedId) {
-            Main.messageTray.disconnect(this._summaryAddedId);
-            this._summaryAddedId = 0;
+        if (this._sourceAddedId) {
+            Main.messageTray.disconnect(this._sourceAddedId);
+            this._sourceAddedId = 0;
         }
 
-        for (let i = 0; i < this._items.length; i++)
-            this._removeItem(this._items[i]);
-        this._items = [];
+        let items = this._sources.items();
+        for (let i = 0; i < items.length; i++) {
+            let [source, obj] = items[i];
+            this._removeSource(source, obj);
+        }
 
         this.actor.destroy();
     },
 
     _updateVisibility: function() {
-        this._residentNotificationBox.visible = this._residentNotificationBox.get_n_children() > 0;
-        this._persistentNotificationBox.visible = this._persistentNotificationBox.get_children().some(function(a) {
+        this._musicBin.visible = this._musicBin.child != null && this._musicBin.child.visible;
+        this._notificationBox.visible = this._notificationBox.get_children().some(function(a) {
             return a.visible;
         });
 
-        this.actor.visible = this._residentNotificationBox.visible || this._persistentNotificationBox.visible;
-    },
-
-    _sourceIsResident: function(source) {
-        return source.hasResidentNotification() && !source.isChat;
+        this.actor.visible = this._musicBin.visible || this._notificationBox.visible;
     },
 
     _makeNotificationCountText: function(count, isChat) {
@@ -179,18 +179,16 @@ const NotificationsBox = new Lang.Class({
             return ngettext("%d new notification", "%d new notifications", count).format(count);
     },
 
-    _makeNotificationSource: function(source) {
-        let box = new St.BoxLayout({ style_class: 'screen-shield-notification-source' });
-
+    _makeNotificationSource: function(source, box) {
         let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
         box.add(sourceActor.actor, { y_fill: true });
 
         let textBox = new St.BoxLayout({ vertical: true });
         box.add(textBox, { y_fill: false, y_align: St.Align.START });
 
-        let label = new St.Label({ text: source.title,
+        let title = new St.Label({ text: source.title,
                                    style_class: 'screen-shield-notification-label' });
-        textBox.add(label);
+        textBox.add(title);
 
         let count = source.unseenCount;
         let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat),
@@ -198,118 +196,172 @@ const NotificationsBox = new Lang.Class({
         textBox.add(countLabel);
 
         box.visible = count != 0;
-        return [box, countLabel];
+        return [title, countLabel];
+    },
+
+    _makeNotificationDetailedSource: function(source, box) {
+        let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
+        box.add(sourceActor.actor, { y_fill: true });
+
+        let textBox = new St.BoxLayout({ vertical: true });
+        box.add(textBox, { y_fill: false, y_align: St.Align.START });
+
+        let title = new St.Label({ text: source.title,
+                                   style_class: 'screen-shield-notification-label' });
+        textBox.add(title);
+
+        let visible = false;
+        for (let i = 0; i < source.notifications.length; i++) {
+            let n = source.notifications[i];
+
+            if (n.acknowledged || n.isMusic)
+                continue;
+
+            let body = '';
+            if (n.bannerBodyText)
+                body = n.bannerBodyMarkup ? n.bannerBodyText :
+                GLib.markup_escape_text(n.bannerBodyMarkup, -1);
+            let label = new St.Label({ style_class: 'screen-shield-notification-count-text' });
+            label.clutter_text.set_markup('<b>' + n.title + '</b> ' + body);
+            textBox.add(label);
+
+            visible = true;
+        }
+
+        box.visible = visible;
+        return [title, null];
+    },
+
+    _showSource: function(source, obj, box) {
+        let musicNotification = source.getMusicNotification();
+
+        if (musicNotification != null &&
+            this._musicBin.child == null) {
+            if (musicNotification.actor.get_parent() != null)
+                musicNotification.actor.get_parent().remove_actor(musicNotification.actor);
+            this._musicBin.child = musicNotification.actor;
+            this._musicBin.child.visible = obj.visible;
+
+            musicNotification.expand(false /* animate */);
+
+            obj.musicNotification = musicNotification;
+        }
+
+        if (obj.detailed) {
+            [obj.titleLabel, obj.countLabel] = this._makeNotificationDetailedSource(source, box);
+        } else {
+            [obj.titleLabel, obj.countLabel] = this._makeNotificationSource(source, box);
+        }
+
+        box.visible = obj.visible &&
+            (source.unseenCount > (musicNotification ? 1 : 0));
     },
 
-    _summaryItemAdded: function(tray, item, dontUpdateVisibility) {
-        // Ignore transient sources, or sources explicitly marked not to show
-        // in the lock screen
-        if (item.source.isTransient || !item.source.showInLockScreen)
+    _sourceAdded: function(tray, source, dontUpdateVisibility) {
+        // Ignore transient sources
+        if (source.isTransient)
             return;
 
         let obj = {
-            item: item,
-            source: item.source,
-            resident: this._sourceIsResident(item.source),
-            contentUpdatedId: 0,
+            visible: source.policy.showInLockScreen,
+            detailed: source.policy.detailsInLockScreen,
             sourceDestroyId: 0,
+            sourceCountChangedId: 0,
+            sourceTitleChangedId: 0,
+            sourceUpdatedId: 0,
+            musicNotification: null,
             sourceBox: null,
+            titleLabel: null,
             countLabel: null,
         };
 
-        if (obj.resident) {
-            this._residentNotificationBox.add(item.notificationStackWidget);
-            item.closeButton.hide();
-            item.prepareNotificationStackForShowing();
-        } else {
-            [obj.sourceBox, obj.countLabel] = this._makeNotificationSource(item.source);
-            this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
-        }
+        obj.sourceBox = new St.BoxLayout({ style_class: 'screen-shield-notification-source' });
+        this._showSource(source, obj, obj.sourceBox);
+        this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
+
+        obj.sourceCountChangedId = source.connect('count-updated', Lang.bind(this, function(source) {
+            this._countChanged(source, obj);
+        }));
+        obj.sourceTitleChangedId = source.connect('title-changed', Lang.bind(this, function(source) {
+            this._titleChanged(source, obj);
+        }));
+        obj.policyChangedId = source.policy.connect('policy-changed', Lang.bind(this, function(policy, key) {
+            if (key == 'show-in-lock-screen')
+                this._visibleChanged(source, obj);
+            else
+                this._detailedChanged(source, obj);
+        }));
+        obj.sourceDestroyId = source.connect('destroy', Lang.bind(this, function(source) {
+            this._onSourceDestroy(source, obj);
+        }));
 
-        obj.contentUpdatedId = item.connect('content-updated', Lang.bind(this, this._onItemContentUpdated));
-        obj.sourceCountChangedId = item.source.connect('count-updated', Lang.bind(this, this._onSourceChanged));
-        obj.sourceTitleChangedId = item.source.connect('title-changed', Lang.bind(this, this._onSourceChanged));
-        obj.sourceDestroyId = item.source.connect('destroy', Lang.bind(this, this._onSourceDestroy));
-        this._items.push(obj);
+        this._sources.set(source, obj);
 
         if (!dontUpdateVisibility)
             this._updateVisibility();
     },
 
-    _findSource: function(source) {
-        for (let i = 0; i < this._items.length; i++) {
-            if (this._items[i].source == source)
-                return i;
-        }
-
-        return -1;
+    _titleChanged: function(source, obj) {
+        obj.titleLabel.text = source.title;
     },
 
-    _onItemContentUpdated: function(item) {
-        let obj = this._items[this._findSource(item.source)];
-        this._updateItem(obj);
-    },
+    _countChanged: function(source, obj) {
+        if (obj.detailed) {
+            // A new notification was pushed, or a previous notification was destroyed.
+            // Give up, and build the list again.
 
-    _onSourceChanged: function(source) {
-        let obj = this._items[this._findSource(source)];
-        this._updateItem(obj);
-    },
+            obj.sourceBox.destroy_all_children();
+            obj.titleLabel = obj.countLabel = null;
+            this._showSource(source, obj, obj.sourceBox);
+        } else {
+            let count = source.unseenCount;
+            obj.countLabel.text = this._makeNotificationCountText(count, source.isChat);
+        }
 
-    _updateItem: function(obj) {
-        let itemShouldBeResident = this._sourceIsResident(obj.source);
+        obj.sourceBox.visible = obj.visible &&
+            (source.unseenCount > (obj.musicNotification ? 1 : 0));
+        this._updateVisibility();
+    },
 
-        if (itemShouldBeResident && obj.resident) {
-            // Nothing to do here, the actor is already updated
+    _visibleChanged: function(source, obj) {
+        if (obj.visible == source.policy.showInLockScreen)
             return;
-        }
 
-        if (obj.resident && !itemShouldBeResident) {
-            // make into a regular item
-            obj.item.doneShowingNotificationStack();
-            this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
-
-            [obj.sourceBox, obj.countLabel] = this._makeNotificationSource(obj.source);
-            this._persistentNotificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
-        } else if (itemShouldBeResident && !obj.resident) {
-            // make into a resident item
-            obj.sourceBox.destroy();
-            obj.sourceBox = obj.countLabel = null;
-            obj.resident = true;
-
-            this._residentNotificationBox.add(obj.item.notificationStackWidget);
-            obj.item.closeButton.hide();
-            obj.item.prepareNotificationStackForShowing();
-        } else {
-            // just update the counter
-            let count = obj.source.unseenCount;
-            obj.countLabel.text = this._makeNotificationCountText(count, obj.source.isChat);
-            obj.sourceBox.visible = count != 0;
-        }
+        obj.visible = source.policy.showInLockScreen;
+        if (obj.musicNotification)
+            obj.musicNotification.actor.visible = obj.visible;
+        obj.sourceBox.visible = obj.visible &&
+            source.unseenCount > (obj.musicNotification ? 1 : 0);
 
         this._updateVisibility();
     },
 
-    _onSourceDestroy: function(source) {
-        let idx = this._findSource(source);
+    _detailedChanged: function(source, obj) {
+        if (obj.detailed == source.policy.detailsInLockScreen)
+            return;
+
+        obj.detailed = source.policy.detailsInLockScreen;
 
-        this._removeItem(this._items[idx]);
-        this._items.splice(idx, 1);
+        obj.sourceBox.destroy_all_children();
+        obj.titleLabel = obj.countLabel = null;
+        this._showSource(source, obj, obj.sourceBox);
+    },
 
+    _onSourceDestroy: function(source, obj) {
+        this._removeSource(source, obj);
         this._updateVisibility();
     },
 
-    _removeItem: function(obj) {
-        if (obj.resident) {
-            obj.item.doneShowingNotificationStack();
-            this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
-        } else {
-            obj.sourceBox.destroy();
-        }
+    _removeSource: function(source, obj) {
+        obj.sourceBox.destroy();
+        obj.sourceBox = obj.titleLabel = obj.countLabel = null;
+
+        source.disconnect(obj.sourceDestroyId);
+        source.disconnect(obj.sourceCountChangedId);
+        source.disconnect(obj.sourceTitleChangedId);
+        source.policy.disconnect(obj.policyChangedId);
 
-        obj.item.disconnect(obj.contentUpdatedId);
-        obj.source.disconnect(obj.sourceDestroyId);
-        obj.source.disconnect(obj.sourceCountChangedId);
-        obj.source.disconnect(obj.sourceTitleChangedId);
+        this._sources.delete(source);
     },
 });
 



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