Compare commits

...

24 Commits

Author SHA1 Message Date
Dan Winship
8fc9d0c8ba Kill notification-daemon at startup when running in --replace mode
https://bugzilla.gnome.org/show_bug.cgi?id=606755
2010-01-12 17:12:48 -05:00
Dan Winship
803a204604 Line-wrap the notification content
Doesn't quite work for very long notifications yet, because the
message tray itself has a fixed height.

https://bugzilla.gnome.org/show_bug.cgi?id=606755
2010-01-12 17:12:45 -05:00
Dan Winship
a02e6d30f7 remove messaging.js; use notificationDaemon.js for empathy messages too
Empathy sends notifications on all the events we care about, so just use
those rather than fiddling with Telepathy to get duplicate copies of them.

https://bugzilla.gnome.org/show_bug.cgi?id=606331
2010-01-08 13:14:40 -05:00
Dan Winship
a424bbbabf Show both summary and body of notifications, and support body-markup
Previously we were only showing the summary for notifications, but most
notifications only make sense if you show both.

https://bugzilla.gnome.org/show_bug.cgi?id=606331
2010-01-08 13:14:34 -05:00
Marina Zhurakhinskaya
4ab513ca77 Combine notifications and the summary mode in a single message tray
We should either be showing the message tray in the notification or the summary mode.
This is best achieved if the message tray actor contains both, and either one is shown
at any given time.

Queue notifications so that each queued notification is shown when the previous one
times out or when the user is done interacting with the message tray and moves
the mouse away from it. (In the future, we will have some sort of an indication that
there are queued notifications and a way to have the next notification displayed
without moving the mouse away from the message tray.)
2010-01-06 16:27:08 -05:00
Dan Winship
09653fbaf6 Merge branch 'master' into message-tray
Conflicts:
	js/ui/main.js
2010-01-05 11:25:15 -05:00
Dan Winship
b1791951cb rebase fail. fix ef49ada575 2010-01-05 11:24:21 -05:00
Dan Winship
af1a3b11f5 Implement additional notification daemon icon types
https://bugzilla.gnome.org/show_bug.cgi?id=603546
2010-01-05 11:18:02 -05:00
Dan Winship
ef49ada575 add an explicit message tray Source type
https://bugzilla.gnome.org/show_bug.cgi?id=603546
2010-01-05 11:17:21 -05:00
Dan Winship
6c3b8e2add [messaging] update avatar code to use non-deprecated interfaces
Also fixes it to notice avatar updates

https://bugzilla.gnome.org/show_bug.cgi?id=603546
2010-01-05 11:15:46 -05:00
Dan Winship
3658f8a8b4 Fix icon/text alignment in message tray
https://bugzilla.gnome.org/show_bug.cgi?id=603546
2010-01-05 11:15:45 -05:00
Dan Winship
74418f2129 update for chrome.js changes in master 2009-12-02 17:14:04 -05:00
Dan Winship
3b4e2202f7 Merge branch 'master' into message-tray 2009-12-02 17:03:48 -05:00
Marina Zhurakhinskaya
3b5c468cbf Add a message tray with icons for ongoing conversations
Add a message tray that slides out when you move your mouse to
the bottom of the screen. The icons for ongoing conversations
are added to the message tray when the first message in the
conversation is received. The icon is removed when the corresponding
conversation window is closed.

Store the avatar icons in the texture cache and use the checksum for
the data bytes for the icon as the key. This allows to reuse the icon
data for the message tray icon.

Add st_box_layout_insert_actor() that allows inserting an actor at the
arbitrary position in the container. It is needed to be able to add the
icon representing the most recent conversation to the front of the list
of icons in the message tray.
2009-12-02 16:34:48 -05:00
Dan Winship
b0a0ee297c Merge branch 'master' into message-tray
Conflicts:
	data/theme/gnome-shell.css
2009-11-18 13:43:54 -05:00
Dan Winship
b48b21e578 add notificationDaemon.js
currently supports summary+icon only, and requires you to manually
kill the existing notification-daemon first
2009-11-18 13:42:38 -05:00
Dan Winship
e823a3b554 Fixed some problems noticed by marina 2009-11-18 13:42:07 -05:00
Dan Winship
e5b12619ef acquire the list of already-active telepathy connections at startup 2009-11-03 14:30:10 -05:00
Dan Winship
242c2bce04 fix tabs->spaces 2009-11-03 12:13:08 -05:00
Dan Winship
64373fe77e add avatar icons to message tray 2009-11-03 10:47:58 -05:00
Marina Zhurakhinskaya
f106ee7182 Add a notification popup for messages
Use the notification popup when the messaging module receives new messages

https://bugzilla.gnome.org/show_bug.cgi?id=599193
2009-11-03 10:28:44 -05:00
Dan Winship
56d2691c31 Reorganize a bit, fix missing-first-message bug
https://bugzilla.gnome.org/show_bug.cgi?id=599193
2009-11-03 10:28:42 -05:00
Dan Winship
f883e32f26 Add shell_texture_cache_load_from_data, for loading non-file-based images
Based on a patch from Will Thompson

https://bugzilla.gnome.org/show_bug.cgi?id=599193
2009-11-03 10:28:34 -05:00
Dan Winship
c985c3cf78 Add some very minimal telepathy support
Original code from Will Thompson

https://bugzilla.gnome.org/show_bug.cgi?id=599193
2009-11-03 10:28:29 -05:00
11 changed files with 740 additions and 27 deletions

View File

@ -472,6 +472,27 @@ StTooltip {
color: #cccccc; color: #cccccc;
} }
/* Message Tray */
#message-tray {
background-gradient-direction: vertical;
background-gradient-start: rgba(0,0,0,0.01);
background-gradient-end: rgba(0,0,0,0.95);
height: 28px;
}
#notification {
border-radius: 5px;
background: rgba(0,0,0,0.9);
color: white;
padding: 2px 10px;
spacing: 10px;
}
#summary-mode {
spacing: 10px;
padding: 2px 4px;
}
/* App Switcher */ /* App Switcher */
.switcher-list { .switcher-list {
background: rgba(0,0,0,0.8); background: rgba(0,0,0,0.8);

View File

@ -16,6 +16,8 @@ dist_jsui_DATA = \
link.js \ link.js \
lookingGlass.js \ lookingGlass.js \
main.js \ main.js \
messageTray.js \
notificationDaemon.js \
overview.js \ overview.js \
panel.js \ panel.js \
placeDisplay.js \ placeDisplay.js \

View File

@ -15,11 +15,13 @@ const St = imports.gi.St;
const Chrome = imports.ui.chrome; const Chrome = imports.ui.chrome;
const Environment = imports.ui.environment; const Environment = imports.ui.environment;
const ExtensionSystem = imports.ui.extensionSystem; const ExtensionSystem = imports.ui.extensionSystem;
const MessageTray = imports.ui.messageTray;
const Overview = imports.ui.overview; const Overview = imports.ui.overview;
const Panel = imports.ui.panel; const Panel = imports.ui.panel;
const PlaceDisplay = imports.ui.placeDisplay; const PlaceDisplay = imports.ui.placeDisplay;
const RunDialog = imports.ui.runDialog; const RunDialog = imports.ui.runDialog;
const LookingGlass = imports.ui.lookingGlass; const LookingGlass = imports.ui.lookingGlass;
const NotificationDaemon = imports.ui.notificationDaemon;
const ShellDBus = imports.ui.shellDBus; const ShellDBus = imports.ui.shellDBus;
const Sidebar = imports.ui.sidebar; const Sidebar = imports.ui.sidebar;
const WindowManager = imports.ui.windowManager; const WindowManager = imports.ui.windowManager;
@ -35,6 +37,9 @@ let overview = null;
let runDialog = null; let runDialog = null;
let lookingGlass = null; let lookingGlass = null;
let wm = null; let wm = null;
let notificationDaemon = null;
let notificationPopup = null;
let messageTray = null;
let recorder = null; let recorder = null;
let shellDBusService = null; let shellDBusService = null;
let modalCount = 0; let modalCount = 0;
@ -113,6 +118,9 @@ function start() {
panel = new Panel.Panel(); panel = new Panel.Panel();
sidebar = new Sidebar.Sidebar(); sidebar = new Sidebar.Sidebar();
wm = new WindowManager.WindowManager(); wm = new WindowManager.WindowManager();
notificationDaemon = new NotificationDaemon.NotificationDaemon();
notificationPopup = new MessageTray.Notification();
messageTray = new MessageTray.MessageTray();
_startDate = new Date(); _startDate = new Date();

271
js/ui/messageTray.js Normal file
View File

@ -0,0 +1,271 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
const Pango = imports.gi.Pango;
const Shell = imports.gi.Shell;
const St = imports.gi.St;
const Signals = imports.signals;
const Tweener = imports.ui.tweener;
const Main = imports.ui.main;
const ANIMATION_TIME = 0.2;
const NOTIFICATION_TIMEOUT = 4;
const MESSAGE_TRAY_TIMEOUT = 0.2;
const ICON_SIZE = 24;
function Notification(icon, text) {
this._init(icon, text);
}
Notification.prototype = {
_init: function(icon, text) {
this.icon = icon;
this.text = text;
}
}
function NotificationBox() {
this._init();
}
NotificationBox.prototype = {
_init: function() {
this.actor = new St.BoxLayout({ name: 'notification' });
this._iconBox = new St.Bin();
this.actor.add(this._iconBox);
this._textBox = new Shell.GenericContainer();
this._textBox.connect('get-preferred-width', Lang.bind(this, this._textBoxGetPreferredWidth));
this._textBox.connect('get-preferred-height', Lang.bind(this, this._textBoxGetPreferredHeight));
this._textBox.connect('allocate', Lang.bind(this, this._textBoxAllocate));
this.actor.add(this._textBox, { expand: true, x_fill: false, y_fill: false, y_align: St.Align.MIDDLE });
this._text = new St.Label();
this._text.clutter_text.line_wrap = true;
this._text.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._textBox.add_actor(this._text);
},
_textBoxGetPreferredWidth: function (actor, forHeight, alloc) {
let [min, nat] = this._text.get_preferred_width(forHeight);
alloc.min_size = alloc.nat_size = Math.min(nat, global.screen_width / 2);
},
_textBoxGetPreferredHeight: function (actor, forWidth, alloc) {
// St.BoxLayout passes -1 for @forWidth, which isn't what we want.
let prefWidth = {};
this._textBoxGetPreferredWidth(this._textBox, -1, prefWidth);
[alloc.min_size, alloc.nat_size] = this._text.get_preferred_height(prefWidth.nat_size);
log('for width ' + prefWidth.nat_size + ', height ' + alloc.nat_size);
},
_textBoxAllocate: function (actor, box, flags) {
let childBox = new Clutter.ActorBox();
childBox.x1 = childBox.y1 = 0;
childBox.x2 = box.x2 - box.x1;
childBox.y2 = box.y2 - box.y1;
this._text.allocate(childBox, flags);
},
setContent: function(notification) {
this._iconBox.child = notification.icon;
// Support <b>, <i>, and <u>, escape anything else
// so it displays as raw markup.
let markup = notification.text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, "&lt;$1");
this._text.clutter_text.set_markup(markup);
}
};
function Source(id, createIcon) {
this._init(id, createIcon);
}
Source.prototype = {
_init: function(id, createIcon) {
this.id = id;
if (createIcon)
this.createIcon = createIcon;
},
// This can be overridden by a subclass, or by the createIcon
// parameter to _init()
createIcon: function(size) {
throw new Error('no implementation of createIcon in ' + this);
},
notify: function(text) {
Main.messageTray.showNotification(new Notification(this.createIcon(ICON_SIZE), text));
},
clicked: function() {
this.emit('clicked');
},
destroy: function() {
this.emit('destroy');
}
};
Signals.addSignalMethods(Source.prototype);
function MessageTray() {
this._init();
}
MessageTray.prototype = {
_init: function() {
this.actor = new St.BoxLayout({ name: 'message-tray',
reactive: true });
let primary = global.get_primary_monitor();
this.actor.x = 0;
this.actor.y = primary.height - 1;
this.actor.width = primary.width;
this._summaryBin = new St.Bin({ x_align: St.Align.END });
this.actor.add(this._summaryBin, { expand: true });
this._summaryBin.hide();
this._notificationBox = new NotificationBox();
this._notificationQueue = [];
this.actor.add(this._notificationBox.actor);
this._notificationBox.actor.hide();
Main.chrome.addActor(this.actor, { affectsStruts: false });
this.actor.connect('enter-event',
Lang.bind(this, this._onMessageTrayEntered));
this.actor.connect('leave-event',
Lang.bind(this, this._onMessageTrayLeft));
this._isShowing = false;
this.actor.show();
this._summary = new St.BoxLayout({ name: 'summary-mode' });
this._summaryBin.child = this._summary;
this._sources = {};
this._icons = {};
},
contains: function(source) {
return this._sources.hasOwnProperty(source.id);
},
add: function(source) {
if (this.contains(source)) {
log('Trying to re-add source ' + source.id);
return;
}
let iconBox = new St.Bin({ reactive: true });
iconBox.child = source.createIcon(ICON_SIZE);
this._summary.insert_actor(iconBox, 0);
this._icons[source.id] = iconBox;
this._sources[source.id] = source;
iconBox.connect('button-release-event', Lang.bind(this,
function () {
source.clicked();
}));
source.connect('destroy', Lang.bind(this,
function () {
this.remove(source);
}));
},
remove: function(source) {
if (!this.contains(source))
return;
this._summary.remove_actor(this._icons[source.id]);
delete this._icons[source.id];
delete this._sources[source.id];
},
getSource: function(id) {
return this._sources[id];
},
_onMessageTrayEntered: function() {
// Don't hide the message tray after a timeout if the user has moved the mouse over it.
// We might have a timeout in place if the user moved the mouse away from the message tray for a very short period of time
// or if we are showing a notification.
if (this._hideTimeoutId > 0)
Mainloop.source_remove(this._hideTimeoutId);
if (this._isShowing)
return;
// If the message tray was not already showing, we'll show it in the summary mode.
this._summaryBin.show();
this._show();
},
_onMessageTrayLeft: function() {
if (!this._isShowing)
return;
// We wait just a little before hiding the message tray in case the user will quickly move the mouse back over it.
this._hideTimeoutId = Mainloop.timeout_add(MESSAGE_TRAY_TIMEOUT * 1000, Lang.bind(this, this._hide));
},
_show: function() {
this._isShowing = true;
let primary = global.get_primary_monitor();
Tweener.addTween(this.actor,
{ y: primary.height - this.actor.height,
time: ANIMATION_TIME,
transition: "easeOutQuad"
});
},
_hide: function() {
this._hideTimeoutId = 0;
let primary = global.get_primary_monitor();
Tweener.addTween(this.actor,
{ y: primary.height - 1,
time: ANIMATION_TIME,
transition: "easeOutQuad",
onComplete: this._hideComplete,
onCompleteScope: this
});
return false;
},
_hideComplete: function() {
this._isShowing = false;
this._summaryBin.hide();
this._notificationBox.actor.hide();
if (this._notificationQueue.length > 0)
this.showNotification(this._notificationQueue.shift());
},
showNotification: function(notification) {
if (this._isShowing) {
this._notificationQueue.push(notification);
return;
}
this._notificationBox.setContent(notification);
this._notificationBox.actor.x = Math.round((this.actor.width - this._notificationBox.actor.width) / 2);
this._notificationBox.actor.show();
// Because we set up the timeout before we do the animation, we add ANIMATION_TIME to NOTIFICATION_TIMEOUT, so that
// NOTIFICATION_TIMEOUT represents the time the notifiation is fully shown.
this._hideTimeoutId = Mainloop.timeout_add((NOTIFICATION_TIMEOUT + ANIMATION_TIME) * 1000, Lang.bind(this, this._hide));
this._show();
}
};

218
js/ui/notificationDaemon.js Normal file
View File

@ -0,0 +1,218 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const DBus = imports.dbus;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Shell = imports.gi.Shell;
const Mainloop = imports.mainloop;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const Params = imports.misc.params;
let nextNotificationId = 1;
const NotificationDaemonIface = {
name: 'org.freedesktop.Notifications',
methods: [{ name: 'Notify',
inSignature: 'susssasa{sv}i',
outSignature: 'u'
},
{ name: 'CloseNotification',
inSignature: 'u',
outSignature: ''
},
{ name: 'GetCapabilities',
inSignature: '',
outSignature: 'as'
},
{ name: 'GetServerInformation',
inSignature: '',
outSignature: 'ssss'
}],
signals: [{ name: 'NotificationClosed',
inSignature: 'uu' },
{ name: 'ActionInvoked',
inSignature: 'us' }]
};
const NotificationClosedReason = {
EXPIRED: 1,
DISMISSED: 2,
APP_CLOSED: 3,
UNDEFINED: 4
};
const Urgency = {
LOW: 0,
NORMAL: 1,
CRITICAL: 2
};
function NotificationDaemon() {
this._init();
}
NotificationDaemon.prototype = {
_init: function() {
DBus.session.exportObject('/org/freedesktop/Notifications', this);
this._everAcquiredName = false;
DBus.session.acquire_name('org.freedesktop.Notifications',
// We pass MANY_INSTANCES so that if
// notification-daemon is running, we'll
// get queued behind it and then get the
// name after killing it below
DBus.MANY_INSTANCES,
Lang.bind(this, this._acquiredName),
Lang.bind(this, this._lostName));
},
_acquiredName: function() {
this._everAcquiredName = true;
},
_lostName: function() {
if (this._everAcquiredName)
log('Lost name org.freedesktop.Notifications!');
else if (GLib.getenv('GNOME_SHELL_NO_REPLACE'))
log('Failed to acquire org.freedesktop.Notifications');
else {
log('Failed to acquire org.freedesktop.Notifications; trying again');
// kill the notification-daemon. pkill is more portable
// than killall, but on Linux at least it won't match if
// you pass more than 15 characters of the process name...
// However, if you use the "-f" flag to match the entire
// command line, it will work, but we have to be careful
// in that case that we don't match "gedit
// notification-daemon.c" or whatever...
let p = new Shell.Process({ args: ['pkill', '-f',
'^([^ ]*/)?(notification-daemon|notify-osd)$']});
p.run();
}
},
_sourceId: function(id) {
return 'notification-' + id;
},
Notify: function(appName, replacesId, icon, summary, body,
actions, hints, timeout) {
let id, source = null;
if (replacesId != 0) {
id = replacesId;
source = Main.messageTray.getSource(this._sourceId(id));
// source may be null if the current source was destroyed
// right as the client sent the new notification
}
if (source == null) {
id = nextNotificationId++;
source = new Source(this._sourceId(id), icon, hints);
Main.messageTray.add(source);
source.connect('clicked', Lang.bind(this,
function() {
source.destroy();
this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
}));
}
summary = GLib.markup_escape_text(summary, -1);
if (body)
source.notify('<b>' + summary + '</b>: ' + body);
else
source.notify('<b>' + summary + '</b>');
return id;
},
CloseNotification: function(id) {
let source = Main.messageTray.getSource(this._sourceId(id));
if (source)
source.destroy();
this._emitNotificationClosed(id, NotificationClosedReason.APP_CLOSED);
},
GetCapabilities: function() {
return [
// 'actions',
'body',
// 'body-hyperlinks',
// 'body-images',
'body-markup',
// 'icon-multi',
'icon-static'
// 'sound',
];
},
GetServerInformation: function() {
return [
'GNOME Shell',
'GNOME',
'0.1', // FIXME, get this from somewhere
'1.0'
];
},
_emitNotificationClosed: function(id, reason) {
DBus.session.emit_signal('/org/freedesktop/Notifications',
'org.freedesktop.Notifications',
'NotificationClosed', 'uu',
[id, reason]);
}
};
DBus.conformExport(NotificationDaemon.prototype, NotificationDaemonIface);
function Source(sourceId, icon, hints) {
this._init(sourceId, icon, hints);
}
Source.prototype = {
__proto__: MessageTray.Source.prototype,
_init: function(sourceId, icon, hints) {
MessageTray.Source.prototype._init.call(this, sourceId);
hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);
this._icon = icon;
this._iconData = hints.icon_data;
this._urgency = hints.urgency;
},
createIcon: function(size) {
let textureCache = Shell.TextureCache.get_default();
if (this._icon) {
if (this._icon.substr(0, 7) == 'file://')
return textureCache.load_uri_async(this._icon, size, size);
else if (this._icon[0] == '/') {
let uri = GLib.filename_to_uri(this._icon, null);
return textureCache.load_uri_async(uri, size, size);
} else
return textureCache.load_icon_name(this._icon, size);
} else if (this._iconData) {
let [width, height, rowStride, hasAlpha,
bitsPerSample, nChannels, data] = this._iconData;
return textureCache.load_from_raw(data, data.length, hasAlpha,
width, height, rowStride, size);
} else {
let stockIcon;
switch (this._urgency) {
case Urgency.LOW:
case Urgency.NORMAL:
stockIcon = 'gtk-dialog-info';
break;
case Urgency.CRITICAL:
stockIcon = 'gtk-dialog-error';
break;
}
return textureCache.load_icon_name(stockIcon, size);
}
}
};

5
src/gnome-shell.in Normal file → Executable file
View File

@ -357,8 +357,9 @@ try:
shell = None shell = None
if options.xephyr: if options.xephyr:
xephyr = start_xephyr() xephyr = start_xephyr()
# This makes us not grab the org.gnome.Panel name # This makes us not grab the org.gnome.Panel or
os.environ['GNOME_SHELL_NO_REPLACE_PANEL'] = '1' # org.freedesktop.Notifications D-Bus names
os.environ['GNOME_SHELL_NO_REPLACE'] = '1'
shell = start_shell() shell = start_shell()
else: else:
xephyr = None xephyr = None

View File

@ -751,7 +751,7 @@ shell_global_grab_dbus_service (ShellGlobal *global)
* unless a special environment variable is passed. The environment * unless a special environment variable is passed. The environment
* variable is used by the gnome-shell (no --replace) launcher in * variable is used by the gnome-shell (no --replace) launcher in
* Xephyr */ * Xephyr */
if (!g_getenv ("GNOME_SHELL_NO_REPLACE_PANEL")) if (!g_getenv ("GNOME_SHELL_NO_REPLACE"))
{ {
if (!dbus_g_proxy_call (bus, "RequestName", &error, G_TYPE_STRING, if (!dbus_g_proxy_call (bus, "RequestName", &error, G_TYPE_STRING,
"org.gnome.Panel", G_TYPE_UINT, "org.gnome.Panel", G_TYPE_UINT,

View File

@ -8,6 +8,7 @@
#define GNOME_DESKTOP_USE_UNSTABLE_API #define GNOME_DESKTOP_USE_UNSTABLE_API
#include <libgnomeui/gnome-desktop-thumbnail.h> #include <libgnomeui/gnome-desktop-thumbnail.h>
#include <string.h> #include <string.h>
#include <glib.h>
typedef struct typedef struct
{ {
@ -17,6 +18,7 @@ typedef struct
GIcon *icon; GIcon *icon;
gchar *uri; gchar *uri;
gchar *thumbnail_uri; gchar *thumbnail_uri;
gchar *checksum;
/* This one is common to all */ /* This one is common to all */
guint size; guint size;
@ -51,6 +53,8 @@ cache_key_hash (gconstpointer a)
base_hash = g_str_hash (akey->uri); base_hash = g_str_hash (akey->uri);
else if (akey->thumbnail_uri) else if (akey->thumbnail_uri)
base_hash = g_str_hash (akey->thumbnail_uri); base_hash = g_str_hash (akey->thumbnail_uri);
else if (akey->checksum)
base_hash = g_str_hash (akey->checksum);
else else
g_assert_not_reached (); g_assert_not_reached ();
return base_hash + 31*akey->size; return base_hash + 31*akey->size;
@ -76,6 +80,8 @@ cache_key_equal (gconstpointer a,
return strcmp (akey->uri, bkey->uri) == 0; return strcmp (akey->uri, bkey->uri) == 0;
else if (akey->thumbnail_uri && bkey->thumbnail_uri) else if (akey->thumbnail_uri && bkey->thumbnail_uri)
return strcmp (akey->thumbnail_uri, bkey->thumbnail_uri) == 0; return strcmp (akey->thumbnail_uri, bkey->thumbnail_uri) == 0;
else if (akey->checksum && bkey->checksum)
return strcmp (akey->checksum, bkey->checksum) == 0;
return FALSE; return FALSE;
} }
@ -89,6 +95,7 @@ cache_key_dup (CacheKey *key)
ret->icon = g_object_ref (key->icon); ret->icon = g_object_ref (key->icon);
ret->uri = g_strdup (key->uri); ret->uri = g_strdup (key->uri);
ret->thumbnail_uri = g_strdup (key->thumbnail_uri); ret->thumbnail_uri = g_strdup (key->thumbnail_uri);
ret->checksum = g_strdup (key->checksum);
ret->size = key->size; ret->size = key->size;
return ret; return ret;
} }
@ -101,6 +108,7 @@ cache_key_destroy (gpointer a)
g_object_unref (akey->icon); g_object_unref (akey->icon);
g_free (akey->uri); g_free (akey->uri);
g_free (akey->thumbnail_uri); g_free (akey->thumbnail_uri);
g_free (akey->checksum);
g_free (akey); g_free (akey);
} }
@ -318,7 +326,8 @@ on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
} }
static GdkPixbuf * static GdkPixbuf *
impl_load_pixbuf_file (const char *uri, impl_load_pixbuf_data (const guchar *data,
gsize size,
int available_width, int available_width,
int available_height, int available_height,
GError **error) GError **error)
@ -326,22 +335,10 @@ impl_load_pixbuf_file (const char *uri,
GdkPixbufLoader *pixbuf_loader = NULL; GdkPixbufLoader *pixbuf_loader = NULL;
GdkPixbuf *rotated_pixbuf = NULL; GdkPixbuf *rotated_pixbuf = NULL;
GdkPixbuf *pixbuf; GdkPixbuf *pixbuf;
GFile *file = NULL;
char *contents = NULL;
gsize size;
gboolean success; gboolean success;
Dimensions available_dimensions; Dimensions available_dimensions;
int width_before_rotation, width_after_rotation; int width_before_rotation, width_after_rotation;
file = g_file_new_for_uri (uri);
success = g_file_load_contents (file, NULL, &contents, &size, NULL, error);
if (!success)
{
goto out;
}
pixbuf_loader = gdk_pixbuf_loader_new (); pixbuf_loader = gdk_pixbuf_loader_new ();
available_dimensions.width = available_width; available_dimensions.width = available_width;
@ -349,10 +346,7 @@ impl_load_pixbuf_file (const char *uri,
g_signal_connect (pixbuf_loader, "size-prepared", g_signal_connect (pixbuf_loader, "size-prepared",
G_CALLBACK (on_image_size_prepared), &available_dimensions); G_CALLBACK (on_image_size_prepared), &available_dimensions);
success = gdk_pixbuf_loader_write (pixbuf_loader, success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
(const guchar *) contents,
size,
error);
if (!success) if (!success)
goto out; goto out;
success = gdk_pixbuf_loader_close (pixbuf_loader, error); success = gdk_pixbuf_loader_close (pixbuf_loader, error);
@ -384,10 +378,7 @@ impl_load_pixbuf_file (const char *uri,
g_signal_connect (pixbuf_loader, "size-prepared", g_signal_connect (pixbuf_loader, "size-prepared",
G_CALLBACK (on_image_size_prepared), &available_dimensions); G_CALLBACK (on_image_size_prepared), &available_dimensions);
success = gdk_pixbuf_loader_write (pixbuf_loader, success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
(const guchar *) contents,
size,
error);
if (!success) if (!success)
goto out; goto out;
@ -401,14 +392,36 @@ impl_load_pixbuf_file (const char *uri,
} }
out: out:
g_free (contents);
if (file)
g_object_unref (file);
if (pixbuf_loader) if (pixbuf_loader)
g_object_unref (pixbuf_loader); g_object_unref (pixbuf_loader);
return rotated_pixbuf; return rotated_pixbuf;
} }
static GdkPixbuf *
impl_load_pixbuf_file (const char *uri,
int available_width,
int available_height,
GError **error)
{
GdkPixbuf *pixbuf = NULL;
GFile *file;
char *contents = NULL;
gsize size;
file = g_file_new_for_uri (uri);
if (g_file_load_contents (file, NULL, &contents, &size, NULL, error))
{
pixbuf = impl_load_pixbuf_data ((const guchar *) contents, size,
available_width, available_height,
error);
}
g_object_unref (file);
g_free (contents);
return pixbuf;
}
static GdkPixbuf * static GdkPixbuf *
impl_load_thumbnail (ShellTextureCache *cache, impl_load_thumbnail (ShellTextureCache *cache,
const char *uri, const char *uri,
@ -655,6 +668,7 @@ typedef struct {
gboolean thumbnail; gboolean thumbnail;
char *mimetype; char *mimetype;
GtkRecentInfo *recent_info; GtkRecentInfo *recent_info;
char *checksum;
GIcon *icon; GIcon *icon;
GtkIconInfo *icon_info; GtkIconInfo *icon_info;
guint width; guint width;
@ -1137,6 +1151,128 @@ shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
return CLUTTER_ACTOR (texture); return CLUTTER_ACTOR (texture);
} }
/**
* shell_texture_cache_load_from_data:
* @cache: The texture cache instance
* @data: Image data in PNG, GIF, etc format
* @len: length of @data
* @size: Size in pixels to use for the resulting texture
* @error: Return location for error
*
* Synchronously creates an image from @data. The image is scaled down
* to fit the available width and height dimensions, but the image is
* never scaled up beyond its actual size. The pixbuf is rotated
* according to the associated orientation setting.
*
* Return value: (transfer none): A new #ClutterActor with the image data loaded if it was
* generated succesfully, %NULL otherwise
*/
ClutterActor *
shell_texture_cache_load_from_data (ShellTextureCache *cache,
const guchar *data,
gsize len,
int size,
GError **error)
{
ClutterTexture *texture;
CoglHandle texdata;
GdkPixbuf *pixbuf;
CacheKey key;
gchar *checksum;
texture = create_default_texture (cache);
clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size);
checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA1, data, len);
memset (&key, 0, sizeof(key));
key.size = size;
key.checksum = checksum;
texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key);
if (texdata == NULL)
{
pixbuf = impl_load_pixbuf_data (data, len, size, size, error);
if (!pixbuf)
{
g_object_unref (texture);
return NULL;
}
texdata = pixbuf_to_cogl_handle (pixbuf);
g_object_unref (pixbuf);
set_texture_cogl_texture (texture, texdata);
g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), texdata);
}
g_free (key.checksum);
set_texture_cogl_texture (texture, texdata);
return CLUTTER_ACTOR (texture);
}
/**
* shell_texture_cache_load_from_raw:
* @cache: a #ShellTextureCache
* @data: raw pixel data
* @len: the length of @data
* @has_alpha: whether @data includes an alpha channel
* @width: width in pixels of @data
* @height: width in pixels of @data
* @rowstride: rowstride of @data
* @size: size of icon to return
*
* Creates (or retrieves from cache) an icon based on raw pixel data.
*
* Return value: (transfer none): a new #ClutterActor displaying a
* pixbuf created from @data and the other parameters.
**/
ClutterActor *
shell_texture_cache_load_from_raw (ShellTextureCache *cache,
const guchar *data,
gsize len,
gboolean has_alpha,
int width,
int height,
int rowstride,
int size,
GError **error)
{
ClutterTexture *texture;
CoglHandle texdata;
CacheKey key;
gchar *checksum;
texture = create_default_texture (cache);
clutter_actor_set_size (CLUTTER_ACTOR (texture), size, size);
/* In theory, two images of different size could have the same
* pixel data. We ignore that theory.
*/
checksum = g_compute_checksum_for_data (G_CHECKSUM_SHA1, data, len);
memset (&key, 0, sizeof(key));
key.size = size;
key.checksum = checksum;
texdata = g_hash_table_lookup (cache->priv->keyed_cache, &key);
if (texdata == NULL)
{
texdata = cogl_texture_new_from_data (width, height, COGL_TEXTURE_NONE,
has_alpha ? COGL_PIXEL_FORMAT_RGBA_8888 : COGL_PIXEL_FORMAT_RGB_888,
COGL_PIXEL_FORMAT_ANY,
rowstride, data);
g_hash_table_insert (cache->priv->keyed_cache, cache_key_dup (&key), texdata);
}
g_free (key.checksum);
set_texture_cogl_texture (texture, texdata);
return CLUTTER_ACTOR (texture);
}
/** /**
* shell_texture_cache_load_thumbnail: * shell_texture_cache_load_thumbnail:
* @cache: * @cache:

View File

@ -80,6 +80,21 @@ ClutterActor *shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
int available_height, int available_height,
GError **error); GError **error);
ClutterActor *shell_texture_cache_load_from_data (ShellTextureCache *cache,
const guchar *data,
gsize len,
int size,
GError **error);
ClutterActor *shell_texture_cache_load_from_raw (ShellTextureCache *cache,
const guchar *data,
gsize len,
gboolean has_alpha,
int width,
int height,
int rowstride,
int size,
GError **error);
gboolean shell_texture_cache_pixbuf_equal (ShellTextureCache *cache, GdkPixbuf *a, GdkPixbuf *b); gboolean shell_texture_cache_pixbuf_equal (ShellTextureCache *cache, GdkPixbuf *a, GdkPixbuf *b);
#endif /* __SHELL_TEXTURE_CACHE_H__ */ #endif /* __SHELL_TEXTURE_CACHE_H__ */

View File

@ -1418,3 +1418,36 @@ st_box_layout_get_n_children (StBoxLayout *self)
{ {
return g_list_length (self->priv->children); return g_list_length (self->priv->children);
} }
void
st_box_layout_move_child (StBoxLayout *self,
ClutterActor *actor,
int pos)
{
StBoxLayoutPrivate *priv = ST_BOX_LAYOUT (self)->priv;
GList *item = NULL;
item = g_list_find (priv->children, actor);
if (item == NULL)
{
g_warning ("Actor of type '%s' is not a child of the StBoxLayout container",
g_type_name (G_OBJECT_TYPE (actor)));
return;
}
priv->children = g_list_delete_link (priv->children, item);
priv->children = g_list_insert (priv->children, actor, pos);
clutter_actor_queue_relayout ((ClutterActor*) self);
}
void
st_box_layout_insert_actor (StBoxLayout *self,
ClutterActor *actor,
int pos)
{
clutter_container_add_actor((ClutterContainer*) self, actor);
st_box_layout_move_child(self, actor, pos);
}

View File

@ -95,6 +95,14 @@ void st_box_layout_destroy_children (StBoxLayout *box);
guint st_box_layout_get_n_children (StBoxLayout *box); guint st_box_layout_get_n_children (StBoxLayout *box);
void st_box_layout_move_child (StBoxLayout *self,
ClutterActor *actor,
int pos);
void st_box_layout_insert_actor (StBoxLayout *self,
ClutterActor *actor,
int pos);
G_END_DECLS G_END_DECLS
#endif /* _ST_BOX_LAYOUT_H */ #endif /* _ST_BOX_LAYOUT_H */