diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index 73c86e20d..babb938c9 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1257,6 +1257,10 @@ StScrollBar StButton#vhandle:hover
padding: 8px;
}
+.secondary-icon {
+ icon-size: 1.09em;
+}
+
.hotplug-transient-box {
spacing: 6px;
padding: 2px 72px 2px 12px;
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index 1e110ca6c..f0b343e7a 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -493,6 +493,7 @@ const Notification = new Lang.Class({
params = Params.parse(params, { customContent: false,
body: null,
icon: null,
+ secondaryIcon: null,
titleMarkup: false,
bannerMarkup: false,
bodyMarkup: false,
@@ -507,6 +508,11 @@ const Notification = new Lang.Class({
this._icon = null;
}
+ if (this._secondaryIcon && (params.secondaryIcon || params.clear)) {
+ this._secondaryIcon.destroy();
+ this._secondaryIcon = null;
+ }
+
// We always clear the content area if we don't have custom
// content because it might contain the @banner that didn't
// fit in the banner mode.
@@ -542,6 +548,13 @@ const Notification = new Lang.Class({
y_align: St.Align.START });
}
+ if (!this._secondaryIcon) {
+ this._secondaryIcon = params.secondaryIcon;
+
+ if (this._secondaryIcon)
+ this._bannerBox.add_actor(this._secondaryIcon);
+ }
+
this.title = title;
title = title ? _fixMarkup(title.replace(/\n/g, ' '), params.titleMarkup) : '';
this._titleLabel.clutter_text.set_markup('' + title + '');
@@ -815,8 +828,15 @@ const Notification = new Lang.Class({
let [titleMin, titleNat] = this._titleLabel.get_preferred_width(forHeight);
let [bannerMin, bannerNat] = this._bannerLabel.get_preferred_width(forHeight);
- alloc.min_size = titleMin;
- alloc.natural_size = titleNat + this._spacing + bannerNat;
+ if (this._secondaryIcon) {
+ let [secondaryIconMin, secondaryIconNat] = this._secondaryIcon.get_preferred_width(forHeight);
+
+ alloc.min_size = secondaryIconMin + this._spacing + titleMin;
+ alloc.natural_size = secondaryIconNat + this._spacing + titleNat + this._spacing + bannerNat;
+ } else {
+ alloc.min_size = titleMin;
+ alloc.natural_size = titleNat + this._spacing + bannerNat;
+ }
},
_bannerBoxGetPreferredHeight: function(actor, forWidth, alloc) {
@@ -831,14 +851,42 @@ const Notification = new Lang.Class({
let [titleMinH, titleNatH] = this._titleLabel.get_preferred_height(availWidth);
let [bannerMinW, bannerNatW] = this._bannerLabel.get_preferred_width(availWidth);
+ let rtl = (this._titleDirection == Clutter.TextDirection.RTL);
+ let x = rtl ? availWidth : 0;
+
+ if (this._secondaryIcon) {
+ let [iconMinW, iconNatW] = this._secondaryIcon.get_preferred_width(-1);
+ let [iconMinH, iconNatH] = this._secondaryIcon.get_preferred_height(availWidth);
+
+ let secondaryIconBox = new Clutter.ActorBox();
+ let secondaryIconBoxW = Math.min(iconNatW, availWidth);
+
+ // allocate secondary icon box
+ if (rtl) {
+ secondaryIconBox.x1 = x - secondaryIconBoxW;
+ secondaryIconBox.x2 = x;
+ x = x - (secondaryIconBoxW + this._spacing);
+ } else {
+ secondaryIconBox.x1 = x;
+ secondaryIconBox.x2 = x + secondaryIconBoxW;
+ x = x + secondaryIconBoxW + this._spacing;
+ }
+ secondaryIconBox.y1 = 0;
+ // Using titleNatH ensures that the secondary icon is centered vertically
+ secondaryIconBox.y2 = titleNatH;
+
+ availWidth = availWidth - (secondaryIconBoxW + this._spacing);
+ this._secondaryIcon.allocate(secondaryIconBox, flags);
+ }
+
let titleBox = new Clutter.ActorBox();
let titleBoxW = Math.min(titleNatW, availWidth);
- if (this._titleDirection == Clutter.TextDirection.RTL) {
+ if (rtl) {
titleBox.x1 = availWidth - titleBoxW;
titleBox.x2 = availWidth;
} else {
- titleBox.x1 = 0;
- titleBox.x2 = titleBoxW;
+ titleBox.x1 = x;
+ titleBox.x2 = titleBox.x1 + titleBoxW;
}
titleBox.y1 = 0;
titleBox.y2 = titleNatH;
@@ -852,7 +900,7 @@ const Notification = new Lang.Class({
} else {
let bannerBox = new Clutter.ActorBox();
- if (this._titleDirection == Clutter.TextDirection.RTL) {
+ if (rtl) {
bannerBox.x1 = 0;
bannerBox.x2 = titleBox.x1 - this._spacing;
diff --git a/js/ui/telepathyClient.js b/js/ui/telepathyClient.js
index cfeee4747..46d7ef0e9 100644
--- a/js/ui/telepathyClient.js
+++ b/js/ui/telepathyClient.js
@@ -500,6 +500,37 @@ const ChatSource = new Lang.Class({
return this._iconBox;
},
+ createSecondaryIcon: function() {
+ let iconBox = new St.Bin();
+ iconBox.child = new St.Icon({ style_class: 'secondary-icon',
+ icon_type: St.IconType.FULLCOLOR });
+ let presenceType = this._contact.get_presence_type();
+
+ switch (presenceType) {
+ case Tp.ConnectionPresenceType.AVAILABLE:
+ iconBox.child.icon_name = 'user-available';
+ break;
+ case Tp.ConnectionPresenceType.BUSY:
+ iconBox.child.icon_name = 'user-busy';
+ break;
+ case Tp.ConnectionPresenceType.OFFLINE:
+ iconBox.child.icon_name = 'user-offline';
+ break;
+ case Tp.ConnectionPresenceType.HIDDEN:
+ iconBox.child.icon_name = 'user-invisible';
+ break;
+ case Tp.ConnectionPresenceType.AWAY:
+ iconBox.child.icon_name = 'user-away';
+ break;
+ case Tp.ConnectionPresenceType.EXTENDED_AWAY:
+ iconBox.child.icon_name = 'user-idle';
+ break;
+ default:
+ iconBox.child.icon_name = 'user-offline';
+ }
+ return iconBox;
+ },
+
_updateAvatarIcon: function() {
this._setSummaryIcon(this.createNotificationIcon());
this._notification.update(this._notification.title, null, { customContent: true, icon: this.createNotificationIcon() });
@@ -664,38 +695,14 @@ const ChatSource = new Lang.Class({
},
_presenceChanged: function (contact, presence, status, message) {
- let msg, shouldNotify, title;
-
- if (this._presence == presence)
- return;
+ let msg, title;
title = GLib.markup_escape_text(this.title, -1);
- if (presence == Tp.ConnectionPresenceType.AVAILABLE) {
- msg = _("%s is online.").format(title);
- shouldNotify = (this._presence == Tp.ConnectionPresenceType.OFFLINE);
- } else if (presence == Tp.ConnectionPresenceType.OFFLINE) {
- presence = Tp.ConnectionPresenceType.OFFLINE;
- msg = _("%s is offline.").format(title);
- shouldNotify = (this._presence != Tp.ConnectionPresenceType.OFFLINE);
- } else if (presence == Tp.ConnectionPresenceType.AWAY ||
- presence == Tp.ConnectionPresenceType.EXTENDED_AWAY) {
- msg = _("%s is away.").format(title);
- shouldNotify = false;
- } else if (presence == Tp.ConnectionPresenceType.BUSY) {
- msg = _("%s is busy.").format(title);
- shouldNotify = false;
- } else
- return;
-
- this._presence = presence;
+ this._notification.update(this._notification.title, null, { customContent: true, secondaryIcon: this.createSecondaryIcon() });
if (message)
msg += ' (' + GLib.markup_escape_text(message, -1) + ')';
-
- this._notification.appendPresence(msg, shouldNotify);
- if (shouldNotify)
- this.notify();
},
_pendingRemoved: function(channel, message) {
@@ -722,7 +729,7 @@ const ChatNotification = new Lang.Class({
Extends: MessageTray.Notification,
_init: function(source) {
- this.parent(source, source.title, null, { customContent: true });
+ this.parent(source, source.title, null, { customContent: true, secondaryIcon: source.createSecondaryIcon() });
this.setResident(true);
this._responseEntry = new St.Entry({ style_class: 'chat-response',
@@ -932,19 +939,6 @@ const ChatNotification = new Lang.Class({
return false;
},
- appendPresence: function(text, asTitle) {
- if (asTitle)
- this.update(text, null, { customContent: true, titleMarkup: true });
- else
- this.update(this.source.title, null, { customContent: true });
-
- let label = this._append({ body: text,
- group: 'meta',
- styles: ['chat-meta-message'] });
-
- this._filterMessages();
- },
-
appendAliasChange: function(oldAlias, newAlias) {
oldAlias = GLib.markup_escape_text(oldAlias, -1);
newAlias = GLib.markup_escape_text(newAlias, -1);