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
This commit is contained in:
Giovanni Campagna 2013-01-30 19:47:55 +01:00
parent 098bd4509b
commit 8cb3884fae
4 changed files with 190 additions and 126 deletions

View File

@ -2417,6 +2417,7 @@ StScrollBar StButton#vhandle:active {
.screen-shield-notifications-box { .screen-shield-notifications-box {
spacing: 18px; spacing: 18px;
max-width: 34em;
} }
.screen-shield-notification-source { .screen-shield-notification-source {

View File

@ -345,6 +345,7 @@ const Notification = new Lang.Class({
this.resident = false; this.resident = false;
// 'transient' is a reserved keyword in JS, so we have to use an alternate variable name // 'transient' is a reserved keyword in JS, so we have to use an alternate variable name
this.isTransient = false; this.isTransient = false;
this.isMusic = false;
this.forFeedback = false; this.forFeedback = false;
this.expanded = false; this.expanded = false;
this.focused = false; this.focused = false;
@ -352,8 +353,8 @@ const Notification = new Lang.Class({
this._destroyed = false; this._destroyed = false;
this._useActionIcons = false; this._useActionIcons = false;
this._customContent = false; this._customContent = false;
this._bannerBodyText = null; this.bannerBodyText = null;
this._bannerBodyMarkup = false; this.bannerBodyMarkup = false;
this._titleFitsInBannerMode = true; this._titleFitsInBannerMode = true;
this._titleDirection = Clutter.TextDirection.DEFAULT; this._titleDirection = Clutter.TextDirection.DEFAULT;
this._spacing = 0; this._spacing = 0;
@ -505,12 +506,12 @@ const Notification = new Lang.Class({
// is done correctly automatically. // is done correctly automatically.
this._table.set_text_direction(this._titleDirection); 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 // 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 // expandable due to other elements in its content area or due to the banner
// not fitting fully in the single-line mode. // not fitting fully in the single-line mode.
this._bannerBodyText = this._customContent ? null : banner; this.bannerBodyText = this._customContent ? null : banner;
this._bannerBodyMarkup = params.bannerMarkup; this.bannerBodyMarkup = params.bannerMarkup;
banner = banner ? banner.replace(/\n/g, ' ') : ''; banner = banner ? banner.replace(/\n/g, ' ') : '';
@ -518,7 +519,7 @@ const Notification = new Lang.Class({
this._bannerLabel.queue_relayout(); this._bannerLabel.queue_relayout();
// Add the bannerBody now if we know for sure we'll need it // 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(); this._addBannerBody();
if (params.body) if (params.body)
@ -584,10 +585,10 @@ const Notification = new Lang.Class({
}, },
_addBannerBody: function() { _addBannerBody: function() {
if (this._bannerBodyText) { if (this.bannerBodyText) {
let text = this._bannerBodyText; let text = this.bannerBodyText;
this._bannerBodyText = null; this.bannerBodyText = null;
this.addBody(text, this._bannerBodyMarkup); this.addBody(text, this.bannerBodyMarkup);
} }
}, },
@ -879,7 +880,7 @@ const Notification = new Lang.Class({
}, },
_canExpandContent: function() { _canExpandContent: function() {
return this._bannerBodyText || return this.bannerBodyText ||
(!this._titleFitsInBannerMode && !this._table.has_style_class_name('multi-line-notification')); (!this._titleFitsInBannerMode && !this._table.has_style_class_name('multi-line-notification'));
}, },
@ -1279,7 +1280,16 @@ const Source = new Lang.Class({
hasResidentNotification: function() { hasResidentNotification: function() {
return this.notifications.some(function(n) { return n.resident; }); 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); Signals.addSignalMethods(Source.prototype);
@ -1777,7 +1787,7 @@ const MessageTray = new Lang.Class({
// So postpone calling _updateState() a tiny bit. // So postpone calling _updateState() a tiny bit.
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { this._updateState(); return false; })); 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(); this._updateNoMessagesLabel();
}, },
@ -1813,10 +1823,8 @@ const MessageTray = new Lang.Class({
this._updateState(); this._updateState();
}, },
getSummaryItems: function() { getSources: function() {
return this._sources.values().map(function(v) { return this._sources.keys();
return v.summaryItem;
});
}, },
_onSourceEnableChanged: function(policy, source) { _onSourceEnableChanged: function(policy, source) {

View File

@ -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 gicon = this._iconForNotificationData(icon, hints);
let gimage = this._imageForNotificationData(hints); let gimage = this._imageForNotificationData(hints);

View File

@ -13,6 +13,7 @@ const St = imports.gi.St;
const TweenerEquations = imports.tweener.equations; const TweenerEquations = imports.tweener.equations;
const GnomeSession = imports.misc.gnomeSession; const GnomeSession = imports.misc.gnomeSession;
const Hash = imports.misc.hash;
const Layout = imports.ui.layout; const Layout = imports.ui.layout;
const LoginManager = imports.misc.loginManager; const LoginManager = imports.misc.loginManager;
const Lightbox = imports.ui.lightbox; const Lightbox = imports.ui.lightbox;
@ -127,49 +128,48 @@ const NotificationsBox = new Lang.Class({
name: 'screenShieldNotifications', name: 'screenShieldNotifications',
style_class: 'screen-shield-notifications-box' }); style_class: 'screen-shield-notifications-box' });
this._residentNotificationBox = new St.BoxLayout({ vertical: true, this._musicBin = new St.Bin({ style_class: 'screen-shield-notifications-box',
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.actor.add(this._residentNotificationBox, { x_fill: true }); let scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START });
this._notificationBox = new St.BoxLayout({ vertical: true,
style_class: 'screen-shield-notifications-box' });
scrollView.add_actor(this._notificationBox);
this.actor.add(this._musicBin);
this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START }); this.actor.add(scrollView, { x_fill: true, x_align: St.Align.START });
this._items = []; this._sources = new Hash.Map();
Main.messageTray.getSummaryItems().forEach(Lang.bind(this, function(item) { Main.messageTray.getSources().forEach(Lang.bind(this, function(source) {
this._summaryItemAdded(Main.messageTray, item, true); this._sourceAdded(Main.messageTray, source, true);
})); }));
this._updateVisibility(); 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() { destroy: function() {
if (this._summaryAddedId) { if (this._sourceAddedId) {
Main.messageTray.disconnect(this._summaryAddedId); Main.messageTray.disconnect(this._sourceAddedId);
this._summaryAddedId = 0; this._sourceAddedId = 0;
} }
for (let i = 0; i < this._items.length; i++) let items = this._sources.items();
this._removeItem(this._items[i]); for (let i = 0; i < items.length; i++) {
this._items = []; let [source, obj] = items[i];
this._removeSource(source, obj);
}
this.actor.destroy(); this.actor.destroy();
}, },
_updateVisibility: function() { _updateVisibility: function() {
this._residentNotificationBox.visible = this._residentNotificationBox.get_n_children() > 0; this._musicBin.visible = this._musicBin.child != null && this._musicBin.child.visible;
this._persistentNotificationBox.visible = this._persistentNotificationBox.get_children().some(function(a) { this._notificationBox.visible = this._notificationBox.get_children().some(function(a) {
return a.visible; return a.visible;
}); });
this.actor.visible = this._residentNotificationBox.visible || this._persistentNotificationBox.visible; this.actor.visible = this._musicBin.visible || this._notificationBox.visible;
},
_sourceIsResident: function(source) {
return source.hasResidentNotification() && !source.isChat;
}, },
_makeNotificationCountText: function(count, isChat) { _makeNotificationCountText: function(count, isChat) {
@ -179,18 +179,16 @@ const NotificationsBox = new Lang.Class({
return ngettext("%d new notification", "%d new notifications", count).format(count); return ngettext("%d new notification", "%d new notifications", count).format(count);
}, },
_makeNotificationSource: function(source) { _makeNotificationSource: function(source, box) {
let box = new St.BoxLayout({ style_class: 'screen-shield-notification-source' });
let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE); let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
box.add(sourceActor.actor, { y_fill: true }); box.add(sourceActor.actor, { y_fill: true });
let textBox = new St.BoxLayout({ vertical: true }); let textBox = new St.BoxLayout({ vertical: true });
box.add(textBox, { y_fill: false, y_align: St.Align.START }); 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' }); style_class: 'screen-shield-notification-label' });
textBox.add(label); textBox.add(title);
let count = source.unseenCount; let count = source.unseenCount;
let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat), let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat),
@ -198,118 +196,172 @@ const NotificationsBox = new Lang.Class({
textBox.add(countLabel); textBox.add(countLabel);
box.visible = count != 0; box.visible = count != 0;
return [box, countLabel]; return [title, countLabel];
}, },
_summaryItemAdded: function(tray, item, dontUpdateVisibility) { _makeNotificationDetailedSource: function(source, box) {
// Ignore transient sources, or sources explicitly marked not to show let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
// in the lock screen box.add(sourceActor.actor, { y_fill: true });
if (item.source.isTransient || !item.source.showInLockScreen)
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));
},
_sourceAdded: function(tray, source, dontUpdateVisibility) {
// Ignore transient sources
if (source.isTransient)
return; return;
let obj = { let obj = {
item: item, visible: source.policy.showInLockScreen,
source: item.source, detailed: source.policy.detailsInLockScreen,
resident: this._sourceIsResident(item.source),
contentUpdatedId: 0,
sourceDestroyId: 0, sourceDestroyId: 0,
sourceCountChangedId: 0,
sourceTitleChangedId: 0,
sourceUpdatedId: 0,
musicNotification: null,
sourceBox: null, sourceBox: null,
titleLabel: null,
countLabel: null, countLabel: null,
}; };
if (obj.resident) { obj.sourceBox = new St.BoxLayout({ style_class: 'screen-shield-notification-source' });
this._residentNotificationBox.add(item.notificationStackWidget); this._showSource(source, obj, obj.sourceBox);
item.closeButton.hide(); this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });
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.contentUpdatedId = item.connect('content-updated', Lang.bind(this, this._onItemContentUpdated)); obj.sourceCountChangedId = source.connect('count-updated', Lang.bind(this, function(source) {
obj.sourceCountChangedId = item.source.connect('count-updated', Lang.bind(this, this._onSourceChanged)); this._countChanged(source, obj);
obj.sourceTitleChangedId = item.source.connect('title-changed', Lang.bind(this, this._onSourceChanged)); }));
obj.sourceDestroyId = item.source.connect('destroy', Lang.bind(this, this._onSourceDestroy)); obj.sourceTitleChangedId = source.connect('title-changed', Lang.bind(this, function(source) {
this._items.push(obj); 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);
}));
this._sources.set(source, obj);
if (!dontUpdateVisibility) if (!dontUpdateVisibility)
this._updateVisibility(); this._updateVisibility();
}, },
_findSource: function(source) { _titleChanged: function(source, obj) {
for (let i = 0; i < this._items.length; i++) { obj.titleLabel.text = source.title;
if (this._items[i].source == source) },
return i;
_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.
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);
} }
return -1; obj.sourceBox.visible = obj.visible &&
(source.unseenCount > (obj.musicNotification ? 1 : 0));
this._updateVisibility();
}, },
_onItemContentUpdated: function(item) { _visibleChanged: function(source, obj) {
let obj = this._items[this._findSource(item.source)]; if (obj.visible == source.policy.showInLockScreen)
this._updateItem(obj);
},
_onSourceChanged: function(source) {
let obj = this._items[this._findSource(source)];
this._updateItem(obj);
},
_updateItem: function(obj) {
let itemShouldBeResident = this._sourceIsResident(obj.source);
if (itemShouldBeResident && obj.resident) {
// Nothing to do here, the actor is already updated
return; return;
}
if (obj.resident && !itemShouldBeResident) { obj.visible = source.policy.showInLockScreen;
// make into a regular item if (obj.musicNotification)
obj.item.doneShowingNotificationStack(); obj.musicNotification.actor.visible = obj.visible;
this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget); obj.sourceBox.visible = obj.visible &&
source.unseenCount > (obj.musicNotification ? 1 : 0);
[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;
}
this._updateVisibility(); this._updateVisibility();
}, },
_onSourceDestroy: function(source) { _detailedChanged: function(source, obj) {
let idx = this._findSource(source); if (obj.detailed == source.policy.detailsInLockScreen)
return;
this._removeItem(this._items[idx]); obj.detailed = source.policy.detailsInLockScreen;
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(); this._updateVisibility();
}, },
_removeItem: function(obj) { _removeSource: function(source, obj) {
if (obj.resident) {
obj.item.doneShowingNotificationStack();
this._residentNotificationBox.remove_actor(obj.item.notificationStackWidget);
} else {
obj.sourceBox.destroy(); obj.sourceBox.destroy();
} obj.sourceBox = obj.titleLabel = obj.countLabel = null;
obj.item.disconnect(obj.contentUpdatedId); source.disconnect(obj.sourceDestroyId);
obj.source.disconnect(obj.sourceDestroyId); source.disconnect(obj.sourceCountChangedId);
obj.source.disconnect(obj.sourceCountChangedId); source.disconnect(obj.sourceTitleChangedId);
obj.source.disconnect(obj.sourceTitleChangedId); source.policy.disconnect(obj.policyChangedId);
this._sources.delete(source);
}, },
}); });