Compare commits
24 Commits
citadel
...
message-tr
Author | SHA1 | Date | |
---|---|---|---|
|
8fc9d0c8ba | ||
|
803a204604 | ||
|
a02e6d30f7 | ||
|
a424bbbabf | ||
|
4ab513ca77 | ||
|
09653fbaf6 | ||
|
b1791951cb | ||
|
af1a3b11f5 | ||
|
ef49ada575 | ||
|
6c3b8e2add | ||
|
3658f8a8b4 | ||
|
74418f2129 | ||
|
3b4e2202f7 | ||
|
3b5c468cbf | ||
|
b0a0ee297c | ||
|
b48b21e578 | ||
|
e823a3b554 | ||
|
e5b12619ef | ||
|
242c2bce04 | ||
|
64373fe77e | ||
|
f106ee7182 | ||
|
56d2691c31 | ||
|
f883e32f26 | ||
|
c985c3cf78 |
@ -472,6 +472,27 @@ StTooltip {
|
||||
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 */
|
||||
.switcher-list {
|
||||
background: rgba(0,0,0,0.8);
|
||||
|
@ -16,6 +16,8 @@ dist_jsui_DATA = \
|
||||
link.js \
|
||||
lookingGlass.js \
|
||||
main.js \
|
||||
messageTray.js \
|
||||
notificationDaemon.js \
|
||||
overview.js \
|
||||
panel.js \
|
||||
placeDisplay.js \
|
||||
|
@ -15,11 +15,13 @@ const St = imports.gi.St;
|
||||
const Chrome = imports.ui.chrome;
|
||||
const Environment = imports.ui.environment;
|
||||
const ExtensionSystem = imports.ui.extensionSystem;
|
||||
const MessageTray = imports.ui.messageTray;
|
||||
const Overview = imports.ui.overview;
|
||||
const Panel = imports.ui.panel;
|
||||
const PlaceDisplay = imports.ui.placeDisplay;
|
||||
const RunDialog = imports.ui.runDialog;
|
||||
const LookingGlass = imports.ui.lookingGlass;
|
||||
const NotificationDaemon = imports.ui.notificationDaemon;
|
||||
const ShellDBus = imports.ui.shellDBus;
|
||||
const Sidebar = imports.ui.sidebar;
|
||||
const WindowManager = imports.ui.windowManager;
|
||||
@ -35,6 +37,9 @@ let overview = null;
|
||||
let runDialog = null;
|
||||
let lookingGlass = null;
|
||||
let wm = null;
|
||||
let notificationDaemon = null;
|
||||
let notificationPopup = null;
|
||||
let messageTray = null;
|
||||
let recorder = null;
|
||||
let shellDBusService = null;
|
||||
let modalCount = 0;
|
||||
@ -113,6 +118,9 @@ function start() {
|
||||
panel = new Panel.Panel();
|
||||
sidebar = new Sidebar.Sidebar();
|
||||
wm = new WindowManager.WindowManager();
|
||||
notificationDaemon = new NotificationDaemon.NotificationDaemon();
|
||||
notificationPopup = new MessageTray.Notification();
|
||||
messageTray = new MessageTray.MessageTray();
|
||||
|
||||
_startDate = new Date();
|
||||
|
||||
|
271
js/ui/messageTray.js
Normal file
271
js/ui/messageTray.js
Normal 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, "<$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
218
js/ui/notificationDaemon.js
Normal 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
5
src/gnome-shell.in
Normal file → Executable file
@ -357,8 +357,9 @@ try:
|
||||
shell = None
|
||||
if options.xephyr:
|
||||
xephyr = start_xephyr()
|
||||
# This makes us not grab the org.gnome.Panel name
|
||||
os.environ['GNOME_SHELL_NO_REPLACE_PANEL'] = '1'
|
||||
# This makes us not grab the org.gnome.Panel or
|
||||
# org.freedesktop.Notifications D-Bus names
|
||||
os.environ['GNOME_SHELL_NO_REPLACE'] = '1'
|
||||
shell = start_shell()
|
||||
else:
|
||||
xephyr = None
|
||||
|
@ -751,7 +751,7 @@ shell_global_grab_dbus_service (ShellGlobal *global)
|
||||
* unless a special environment variable is passed. The environment
|
||||
* variable is used by the gnome-shell (no --replace) launcher in
|
||||
* 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,
|
||||
"org.gnome.Panel", G_TYPE_UINT,
|
||||
|
@ -8,6 +8,7 @@
|
||||
#define GNOME_DESKTOP_USE_UNSTABLE_API
|
||||
#include <libgnomeui/gnome-desktop-thumbnail.h>
|
||||
#include <string.h>
|
||||
#include <glib.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
@ -17,6 +18,7 @@ typedef struct
|
||||
GIcon *icon;
|
||||
gchar *uri;
|
||||
gchar *thumbnail_uri;
|
||||
gchar *checksum;
|
||||
|
||||
/* This one is common to all */
|
||||
guint size;
|
||||
@ -51,6 +53,8 @@ cache_key_hash (gconstpointer a)
|
||||
base_hash = g_str_hash (akey->uri);
|
||||
else if (akey->thumbnail_uri)
|
||||
base_hash = g_str_hash (akey->thumbnail_uri);
|
||||
else if (akey->checksum)
|
||||
base_hash = g_str_hash (akey->checksum);
|
||||
else
|
||||
g_assert_not_reached ();
|
||||
return base_hash + 31*akey->size;
|
||||
@ -76,6 +80,8 @@ cache_key_equal (gconstpointer a,
|
||||
return strcmp (akey->uri, bkey->uri) == 0;
|
||||
else if (akey->thumbnail_uri && bkey->thumbnail_uri)
|
||||
return strcmp (akey->thumbnail_uri, bkey->thumbnail_uri) == 0;
|
||||
else if (akey->checksum && bkey->checksum)
|
||||
return strcmp (akey->checksum, bkey->checksum) == 0;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
@ -89,6 +95,7 @@ cache_key_dup (CacheKey *key)
|
||||
ret->icon = g_object_ref (key->icon);
|
||||
ret->uri = g_strdup (key->uri);
|
||||
ret->thumbnail_uri = g_strdup (key->thumbnail_uri);
|
||||
ret->checksum = g_strdup (key->checksum);
|
||||
ret->size = key->size;
|
||||
return ret;
|
||||
}
|
||||
@ -101,6 +108,7 @@ cache_key_destroy (gpointer a)
|
||||
g_object_unref (akey->icon);
|
||||
g_free (akey->uri);
|
||||
g_free (akey->thumbnail_uri);
|
||||
g_free (akey->checksum);
|
||||
g_free (akey);
|
||||
}
|
||||
|
||||
@ -318,7 +326,8 @@ on_image_size_prepared (GdkPixbufLoader *pixbuf_loader,
|
||||
}
|
||||
|
||||
static GdkPixbuf *
|
||||
impl_load_pixbuf_file (const char *uri,
|
||||
impl_load_pixbuf_data (const guchar *data,
|
||||
gsize size,
|
||||
int available_width,
|
||||
int available_height,
|
||||
GError **error)
|
||||
@ -326,22 +335,10 @@ impl_load_pixbuf_file (const char *uri,
|
||||
GdkPixbufLoader *pixbuf_loader = NULL;
|
||||
GdkPixbuf *rotated_pixbuf = NULL;
|
||||
GdkPixbuf *pixbuf;
|
||||
GFile *file = NULL;
|
||||
char *contents = NULL;
|
||||
gsize size;
|
||||
gboolean success;
|
||||
Dimensions available_dimensions;
|
||||
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 ();
|
||||
|
||||
available_dimensions.width = available_width;
|
||||
@ -349,10 +346,7 @@ impl_load_pixbuf_file (const char *uri,
|
||||
g_signal_connect (pixbuf_loader, "size-prepared",
|
||||
G_CALLBACK (on_image_size_prepared), &available_dimensions);
|
||||
|
||||
success = gdk_pixbuf_loader_write (pixbuf_loader,
|
||||
(const guchar *) contents,
|
||||
size,
|
||||
error);
|
||||
success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
|
||||
if (!success)
|
||||
goto out;
|
||||
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_CALLBACK (on_image_size_prepared), &available_dimensions);
|
||||
|
||||
success = gdk_pixbuf_loader_write (pixbuf_loader,
|
||||
(const guchar *) contents,
|
||||
size,
|
||||
error);
|
||||
success = gdk_pixbuf_loader_write (pixbuf_loader, data, size, error);
|
||||
if (!success)
|
||||
goto out;
|
||||
|
||||
@ -401,14 +392,36 @@ impl_load_pixbuf_file (const char *uri,
|
||||
}
|
||||
|
||||
out:
|
||||
g_free (contents);
|
||||
if (file)
|
||||
g_object_unref (file);
|
||||
if (pixbuf_loader)
|
||||
g_object_unref (pixbuf_loader);
|
||||
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 *
|
||||
impl_load_thumbnail (ShellTextureCache *cache,
|
||||
const char *uri,
|
||||
@ -655,6 +668,7 @@ typedef struct {
|
||||
gboolean thumbnail;
|
||||
char *mimetype;
|
||||
GtkRecentInfo *recent_info;
|
||||
char *checksum;
|
||||
GIcon *icon;
|
||||
GtkIconInfo *icon_info;
|
||||
guint width;
|
||||
@ -1137,6 +1151,128 @@ shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
|
||||
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:
|
||||
* @cache:
|
||||
|
@ -80,6 +80,21 @@ ClutterActor *shell_texture_cache_load_uri_sync (ShellTextureCache *cache,
|
||||
int available_height,
|
||||
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);
|
||||
|
||||
#endif /* __SHELL_TEXTURE_CACHE_H__ */
|
||||
|
@ -1418,3 +1418,36 @@ st_box_layout_get_n_children (StBoxLayout *self)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -95,6 +95,14 @@ void st_box_layout_destroy_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
|
||||
|
||||
#endif /* _ST_BOX_LAYOUT_H */
|
||||
|
Loading…
Reference in New Issue
Block a user