MessageTray: rework icon handling to split model and view
To allow more than one summary icon actor for a source we split the model of the source icon (which is iconName, if the default implementation is used, or a GIcon otherwise) and replace createNotificationIcon() with a generic createIcon(size). Also, the actual source actor is split into a separate class, that handles the notification counter automatically. https://bugzilla.gnome.org/show_bug.cgi?id=619955
This commit is contained in:
parent
26d3b1929e
commit
ac6c808124
@ -502,9 +502,9 @@ const AutorunTransientSource = new Lang.Class({
|
|||||||
this.notify(this._notification);
|
this.notify(this._notification);
|
||||||
},
|
},
|
||||||
|
|
||||||
createNotificationIcon: function() {
|
createIcon: function(size) {
|
||||||
return new St.Icon({ gicon: this.mount.get_icon(),
|
return new St.Icon({ gicon: this.mount.get_icon(),
|
||||||
icon_size: this.ICON_SIZE });
|
icon_size: size });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -394,7 +394,7 @@ Signals.addSignalMethods(FocusGrabber.prototype);
|
|||||||
// the content area (as with addBody()).
|
// the content area (as with addBody()).
|
||||||
//
|
//
|
||||||
// By default, the icon shown is created by calling
|
// By default, the icon shown is created by calling
|
||||||
// source.createNotificationIcon(). However, if @params contains an 'icon'
|
// source.createIcon(). However, if @params contains an 'icon'
|
||||||
// parameter, the passed in icon will be used.
|
// parameter, the passed in icon will be used.
|
||||||
//
|
//
|
||||||
// If @params contains a 'titleMarkup', 'bannerMarkup', or
|
// If @params contains a 'titleMarkup', 'bannerMarkup', or
|
||||||
@ -539,7 +539,7 @@ const Notification = new Lang.Class({
|
|||||||
this._table.remove_style_class_name('multi-line-notification');
|
this._table.remove_style_class_name('multi-line-notification');
|
||||||
|
|
||||||
if (!this._icon) {
|
if (!this._icon) {
|
||||||
this._icon = params.icon || this.source.createNotificationIcon();
|
this._icon = params.icon || this.source.createIcon(this.source.ICON_SIZE);
|
||||||
this._table.add(this._icon, { row: 0,
|
this._table.add(this._icon, { row: 0,
|
||||||
col: 0,
|
col: 0,
|
||||||
x_expand: false,
|
x_expand: false,
|
||||||
@ -1029,24 +1029,19 @@ const Notification = new Lang.Class({
|
|||||||
});
|
});
|
||||||
Signals.addSignalMethods(Notification.prototype);
|
Signals.addSignalMethods(Notification.prototype);
|
||||||
|
|
||||||
const Source = new Lang.Class({
|
const SourceActor = new Lang.Class({
|
||||||
Name: 'MessageTraySource',
|
Name: 'SourceActor',
|
||||||
|
|
||||||
ICON_SIZE: 24,
|
_init: function(source, size) {
|
||||||
|
this._source = source;
|
||||||
_init: function(title, iconName, iconType) {
|
|
||||||
this.title = title;
|
|
||||||
this.iconName = iconName;
|
|
||||||
this.iconType = iconType;
|
|
||||||
|
|
||||||
this.actor = new Shell.GenericContainer();
|
this.actor = new Shell.GenericContainer();
|
||||||
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
||||||
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
||||||
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
||||||
this.actor.connect('destroy', Lang.bind(this,
|
this.actor.connect('destroy', Lang.bind(this, function() {
|
||||||
function() {
|
this._actorDestroyed = true;
|
||||||
this._actorDestroyed = true;
|
}));
|
||||||
}));
|
|
||||||
this._actorDestroyed = false;
|
this._actorDestroyed = false;
|
||||||
|
|
||||||
this._counterLabel = new St.Label();
|
this._counterLabel = new St.Label();
|
||||||
@ -1054,21 +1049,20 @@ const Source = new Lang.Class({
|
|||||||
child: this._counterLabel });
|
child: this._counterLabel });
|
||||||
this._counterBin.hide();
|
this._counterBin.hide();
|
||||||
|
|
||||||
this._iconBin = new St.Bin({ width: this.ICON_SIZE,
|
this._iconBin = new St.Bin({ width: size,
|
||||||
height: this.ICON_SIZE,
|
height: size,
|
||||||
x_fill: true,
|
x_fill: true,
|
||||||
y_fill: true });
|
y_fill: true });
|
||||||
|
|
||||||
this.actor.add_actor(this._iconBin);
|
this.actor.add_actor(this._iconBin);
|
||||||
this.actor.add_actor(this._counterBin);
|
this.actor.add_actor(this._counterBin);
|
||||||
|
|
||||||
this.isTransient = false;
|
this._source.connect('count-changed', Lang.bind(this, this._updateCount));
|
||||||
this.isChat = false;
|
this._updateCount();
|
||||||
this.isMuted = false;
|
},
|
||||||
|
|
||||||
this.notifications = [];
|
setIcon: function(icon) {
|
||||||
|
this._iconBin.child = icon;
|
||||||
this._setSummaryIcon(this.createNotificationIcon());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_getPreferredWidth: function (actor, forHeight, alloc) {
|
_getPreferredWidth: function (actor, forHeight, alloc) {
|
||||||
@ -1106,15 +1100,44 @@ const Source = new Lang.Class({
|
|||||||
this._counterBin.allocate(childBox, flags);
|
this._counterBin.allocate(childBox, flags);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_updateCount: function() {
|
||||||
|
if (this._actorDestroyed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this._counterBin.visible = this._source.countVisible;
|
||||||
|
this._counterLabel.set_text(this._source.count.toString());
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const Source = new Lang.Class({
|
||||||
|
Name: 'MessageTraySource',
|
||||||
|
|
||||||
|
ICON_SIZE: 24,
|
||||||
|
|
||||||
|
_init: function(title, iconName, iconType) {
|
||||||
|
this.title = title;
|
||||||
|
this.iconName = iconName;
|
||||||
|
this.iconType = iconType;
|
||||||
|
|
||||||
|
this.count = 0;
|
||||||
|
|
||||||
|
this.isTransient = false;
|
||||||
|
this.isChat = false;
|
||||||
|
this.isMuted = false;
|
||||||
|
|
||||||
|
this.notifications = [];
|
||||||
|
|
||||||
|
this.mainIcon = new SourceActor(this, this.ICON_SIZE);
|
||||||
|
this._setSummaryIcon(this.createIcon(this.ICON_SIZE));
|
||||||
|
},
|
||||||
|
|
||||||
_setCount: function(count, visible) {
|
_setCount: function(count, visible) {
|
||||||
if (isNaN(parseInt(count)))
|
if (isNaN(parseInt(count)))
|
||||||
throw new Error("Invalid notification count: " + count);
|
throw new Error("Invalid notification count: " + count);
|
||||||
|
|
||||||
if (this._actorDestroyed)
|
this.count = count;
|
||||||
return;
|
this.countVisible = visible;
|
||||||
|
this.emit('count-changed');
|
||||||
this._counterBin.visible = visible;
|
|
||||||
this._counterLabel.set_text(count.toString());
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_updateCount: function() {
|
_updateCount: function() {
|
||||||
@ -1138,19 +1161,19 @@ const Source = new Lang.Class({
|
|||||||
this.emit('muted-changed');
|
this.emit('muted-changed');
|
||||||
},
|
},
|
||||||
|
|
||||||
// Called to create a new icon actor (of size this.ICON_SIZE).
|
// Called to create a new icon actor.
|
||||||
// Provides a sane default implementation, override if you need
|
// Provides a sane default implementation, override if you need
|
||||||
// something more fancy.
|
// something more fancy.
|
||||||
createNotificationIcon: function() {
|
createIcon: function(size) {
|
||||||
return new St.Icon({ icon_name: this.iconName,
|
return new St.Icon({ icon_name: this.iconName,
|
||||||
icon_type: this.iconType,
|
icon_type: this.iconType,
|
||||||
icon_size: this.ICON_SIZE });
|
icon_size: size });
|
||||||
},
|
},
|
||||||
|
|
||||||
// Unlike createNotificationIcon, this always returns the same actor;
|
// Unlike createIcon, this always returns the same actor;
|
||||||
// there is only one summary icon actor for a Source.
|
// there is only one summary icon actor for a Source.
|
||||||
getSummaryIcon: function() {
|
getSummaryIcon: function() {
|
||||||
return this.actor;
|
return this.mainIcon.actor;
|
||||||
},
|
},
|
||||||
|
|
||||||
pushNotification: function(notification) {
|
pushNotification: function(notification) {
|
||||||
@ -1196,9 +1219,7 @@ const Source = new Lang.Class({
|
|||||||
|
|
||||||
//// Protected methods ////
|
//// Protected methods ////
|
||||||
_setSummaryIcon: function(icon) {
|
_setSummaryIcon: function(icon) {
|
||||||
if (this._iconBin.child)
|
this.mainIcon.setIcon(icon);
|
||||||
this._iconBin.child.destroy();
|
|
||||||
this._iconBin.child = icon;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
open: function(notification) {
|
open: function(notification) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
||||||
|
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
|
const GdkPixbuf = imports.gi.GdkPixbuf;
|
||||||
const Gio = imports.gi.Gio;
|
const Gio = imports.gi.Gio;
|
||||||
const GLib = imports.gi.GLib;
|
const GLib = imports.gi.GLib;
|
||||||
const Lang = imports.lang;
|
const Lang = imports.lang;
|
||||||
@ -108,9 +109,7 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
Lang.bind(this, this._onFocusAppChanged));
|
Lang.bind(this, this._onFocusAppChanged));
|
||||||
},
|
},
|
||||||
|
|
||||||
_iconForNotificationData: function(icon, hints, size) {
|
_iconForNotificationData: function(icon, hints) {
|
||||||
let textureCache = St.TextureCache.get_default();
|
|
||||||
|
|
||||||
// If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
|
// If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
|
||||||
// and don't show a large image. There are currently many applications that use
|
// and don't show a large image. There are currently many applications that use
|
||||||
// notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
|
// notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
|
||||||
@ -121,20 +120,18 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
// a large image.
|
// a large image.
|
||||||
if (icon) {
|
if (icon) {
|
||||||
if (icon.substr(0, 7) == 'file://')
|
if (icon.substr(0, 7) == 'file://')
|
||||||
return textureCache.load_uri_async(icon, size, size);
|
return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) });
|
||||||
else if (icon[0] == '/') {
|
else if (icon[0] == '/') {
|
||||||
let uri = GLib.filename_to_uri(icon, null);
|
return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) });
|
||||||
return textureCache.load_uri_async(uri, size, size);
|
|
||||||
} else
|
} else
|
||||||
return new St.Icon({ icon_name: icon,
|
return new Gio.ThemedIcon({ name: icon });
|
||||||
icon_type: St.IconType.FULLCOLOR,
|
|
||||||
icon_size: size });
|
|
||||||
} else if (hints['image-data']) {
|
} else if (hints['image-data']) {
|
||||||
let [width, height, rowStride, hasAlpha,
|
let [width, height, rowStride, hasAlpha,
|
||||||
bitsPerSample, nChannels, data] = hints['image-data'];
|
bitsPerSample, nChannels, data] = hints['image-data'];
|
||||||
return textureCache.load_from_raw(data, hasAlpha, width, height, rowStride, size);
|
return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
|
||||||
|
bitsPerSample, width, height, rowStride);
|
||||||
} else if (hints['image-path']) {
|
} else if (hints['image-path']) {
|
||||||
return textureCache.load_uri_async(GLib.filename_to_uri(hints['image-path'], null), size, size);
|
return new Gio.FileIcon({ file: Gio.File.new_for_path(hints['image-path']) });
|
||||||
} else {
|
} else {
|
||||||
let stockIcon;
|
let stockIcon;
|
||||||
switch (hints.urgency) {
|
switch (hints.urgency) {
|
||||||
@ -146,9 +143,7 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
stockIcon = 'gtk-dialog-error';
|
stockIcon = 'gtk-dialog-error';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return new St.Icon({ icon_name: stockIcon,
|
return new Gio.ThemedIcon({ name: stockIcon });
|
||||||
icon_type: St.IconType.FULLCOLOR,
|
|
||||||
icon_size: size });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -341,7 +336,10 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
[ndata.id, ndata.icon, ndata.summary, ndata.body,
|
[ndata.id, ndata.icon, ndata.summary, ndata.body,
|
||||||
ndata.actions, ndata.hints, ndata.notification];
|
ndata.actions, ndata.hints, ndata.notification];
|
||||||
|
|
||||||
let iconActor = this._iconForNotificationData(icon, hints, source.ICON_SIZE);
|
let gicon = this._iconForNotificationData(icon, hints);
|
||||||
|
let iconActor = new St.Icon({ gicon: gicon,
|
||||||
|
icon_type: St.IconType.FULLCOLOR,
|
||||||
|
icon_size: source.ICON_SIZE });
|
||||||
|
|
||||||
if (notification == null) {
|
if (notification == null) {
|
||||||
notification = new MessageTray.Notification(source, summary, body,
|
notification = new MessageTray.Notification(source, summary, body,
|
||||||
@ -421,8 +419,8 @@ const NotificationDaemon = new Lang.Class({
|
|||||||
// of the 'transient' hint with hints['transient'] rather than hints.transient
|
// of the 'transient' hint with hints['transient'] rather than hints.transient
|
||||||
notification.setTransient(hints['transient'] == true);
|
notification.setTransient(hints['transient'] == true);
|
||||||
|
|
||||||
let sourceIconActor = source.useNotificationIcon ? this._iconForNotificationData(icon, hints, source.ICON_SIZE) : null;
|
let sourceGIcon = source.useNotificationIcon ? this._iconForNotificationData(icon, hints) : null;
|
||||||
source.processNotification(notification, sourceIconActor);
|
source.processNotification(notification, sourceGIcon);
|
||||||
},
|
},
|
||||||
|
|
||||||
CloseNotification: function(id) {
|
CloseNotification: function(id) {
|
||||||
@ -534,11 +532,10 @@ const Source = new Lang.Class({
|
|||||||
this.destroy();
|
this.destroy();
|
||||||
},
|
},
|
||||||
|
|
||||||
processNotification: function(notification, icon) {
|
processNotification: function(notification, gicon) {
|
||||||
if (!this.app)
|
if (gicon)
|
||||||
this._setApp();
|
this._gicon = gicon;
|
||||||
if (!this.app && icon)
|
this._setSummaryIcon(this.createIcon(this.ICON_SIZE));
|
||||||
this._setSummaryIcon(icon);
|
|
||||||
|
|
||||||
let tracker = Shell.WindowTracker.get_default();
|
let tracker = Shell.WindowTracker.get_default();
|
||||||
if (notification.resident && this.app && tracker.focus_app == this.app)
|
if (notification.resident && this.app && tracker.focus_app == this.app)
|
||||||
@ -606,7 +603,7 @@ const Source = new Lang.Class({
|
|||||||
// notification-based icons (ie, not a trayicon) or if it was unset before
|
// notification-based icons (ie, not a trayicon) or if it was unset before
|
||||||
if (!this.trayIcon) {
|
if (!this.trayIcon) {
|
||||||
this.useNotificationIcon = false;
|
this.useNotificationIcon = false;
|
||||||
this._setSummaryIcon(this.app.create_icon_texture (this.ICON_SIZE));
|
this._setSummaryIcon(this.createIcon(this.ICON_SIZE));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -640,8 +637,18 @@ const Source = new Lang.Class({
|
|||||||
this.parent();
|
this.parent();
|
||||||
},
|
},
|
||||||
|
|
||||||
createNotificationIcon: function() {
|
createIcon: function(size) {
|
||||||
// We set the summary icon ourselves.
|
if (this.trayIcon) {
|
||||||
return null;
|
return new Clutter.Clone({ width: size,
|
||||||
|
height: size,
|
||||||
|
source: this.trayIcon });
|
||||||
|
} else if (this.app) {
|
||||||
|
return this.app.create_icon_texture(size);
|
||||||
|
} else if (this._gicon) {
|
||||||
|
return new St.Icon({ gicon: this._gicon,
|
||||||
|
icon_size: size });
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -482,9 +482,9 @@ const ChatSource = new Lang.Class({
|
|||||||
this._notification.appendAliasChange(oldAlias, newAlias);
|
this._notification.appendAliasChange(oldAlias, newAlias);
|
||||||
},
|
},
|
||||||
|
|
||||||
createNotificationIcon: function() {
|
createIcon: function(size) {
|
||||||
this._iconBox = new St.Bin({ style_class: 'avatar-box' });
|
this._iconBox = new St.Bin({ style_class: 'avatar-box' });
|
||||||
this._iconBox._size = this.ICON_SIZE;
|
this._iconBox._size = size;
|
||||||
let textureCache = St.TextureCache.get_default();
|
let textureCache = St.TextureCache.get_default();
|
||||||
let file = this._contact.get_avatar_file();
|
let file = this._contact.get_avatar_file();
|
||||||
|
|
||||||
@ -532,8 +532,8 @@ const ChatSource = new Lang.Class({
|
|||||||
},
|
},
|
||||||
|
|
||||||
_updateAvatarIcon: function() {
|
_updateAvatarIcon: function() {
|
||||||
this._setSummaryIcon(this.createNotificationIcon());
|
this._setSummaryIcon(this.createIcon(this.ICON_SIZE));
|
||||||
this._notification.update(this._notification.title, null, { customContent: true, icon: this.createNotificationIcon() });
|
this._notification.update(this._notification.title, null, { customContent: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
open: function(notification) {
|
open: function(notification) {
|
||||||
@ -1031,9 +1031,9 @@ const ApproverSource = new Lang.Class({
|
|||||||
this.parent();
|
this.parent();
|
||||||
},
|
},
|
||||||
|
|
||||||
createNotificationIcon: function() {
|
createIcon: function(size) {
|
||||||
return new St.Icon({ gicon: this._gicon,
|
return new St.Icon({ gicon: this._gicon,
|
||||||
icon_size: this.ICON_SIZE });
|
icon_size: size });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -73,8 +73,8 @@ const Source = new Lang.Class({
|
|||||||
this.signalIDs = [];
|
this.signalIDs = [];
|
||||||
},
|
},
|
||||||
|
|
||||||
createNotificationIcon : function() {
|
createIcon : function(size) {
|
||||||
return this._app.create_icon_texture(this.ICON_SIZE);
|
return this._app.create_icon_texture(size);
|
||||||
},
|
},
|
||||||
|
|
||||||
open : function(notification) {
|
open : function(notification) {
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include "shell-util.h"
|
#include "shell-util.h"
|
||||||
#include <glib/gi18n-lib.h>
|
#include <glib/gi18n-lib.h>
|
||||||
#include <gtk/gtk.h>
|
#include <gtk/gtk.h>
|
||||||
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||||
|
|
||||||
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
|
#ifdef HAVE__NL_TIME_FIRST_WEEKDAY
|
||||||
#include <langinfo.h>
|
#include <langinfo.h>
|
||||||
@ -818,3 +819,33 @@ shell_util_wifexited (int status,
|
|||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* shell_util_create_pixbuf_from_data:
|
||||||
|
* @data: (array length=len) (element-type guint8) (transfer full):
|
||||||
|
* @len:
|
||||||
|
* @colorspace:
|
||||||
|
* @has_alpha:
|
||||||
|
* @bits_per_sample:
|
||||||
|
* @width:
|
||||||
|
* @height:
|
||||||
|
* @rowstride:
|
||||||
|
*
|
||||||
|
* Workaround for non-introspectability of gdk_pixbuf_from_data().
|
||||||
|
*
|
||||||
|
* Returns: (transfer full):
|
||||||
|
*/
|
||||||
|
GdkPixbuf *
|
||||||
|
shell_util_create_pixbuf_from_data (const guchar *data,
|
||||||
|
gsize len,
|
||||||
|
GdkColorspace colorspace,
|
||||||
|
gboolean has_alpha,
|
||||||
|
int bits_per_sample,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rowstride)
|
||||||
|
{
|
||||||
|
return gdk_pixbuf_new_from_data (data, colorspace, has_alpha,
|
||||||
|
bits_per_sample, width, height, rowstride,
|
||||||
|
(GdkPixbufDestroyNotify) g_free, NULL);
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
#include <clutter/clutter.h>
|
#include <clutter/clutter.h>
|
||||||
#include <libsoup/soup.h>
|
#include <libsoup/soup.h>
|
||||||
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
@ -44,6 +45,14 @@ gboolean shell_session_is_active_for_systemd (void);
|
|||||||
gboolean shell_util_wifexited (int status,
|
gboolean shell_util_wifexited (int status,
|
||||||
int *exit);
|
int *exit);
|
||||||
|
|
||||||
|
GdkPixbuf *shell_util_create_pixbuf_from_data (const guchar *data,
|
||||||
|
gsize len,
|
||||||
|
GdkColorspace colorspace,
|
||||||
|
gboolean has_alpha,
|
||||||
|
int bits_per_sample,
|
||||||
|
int width,
|
||||||
|
int height,
|
||||||
|
int rowstride);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user