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);