[gnome-shell/goa-client] Initial goa client work
- From: David Zeuthen <davidz src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-shell/goa-client] Initial goa client work
- Date: Mon, 16 May 2011 21:11:06 +0000 (UTC)
commit a355398b0f34e37d4bd3f64d1d152479b5bf0a25
Author: David Zeuthen <davidz redhat com>
Date: Mon May 16 17:07:42 2011 -0400
Initial goa client work
Signed-off-by: David Zeuthen <davidz redhat com>
data/theme/gnome-shell.css | 46 +++++
js/Makefile.am | 1 +
js/ui/goaClient.js | 464 ++++++++++++++++++++++++++++++++++++++++++++
js/ui/main.js | 3 +
js/ui/messageTray.js | 4 +
src/st/st-texture-cache.c | 13 +-
6 files changed, 524 insertions(+), 7 deletions(-)
---
diff --git a/data/theme/gnome-shell.css b/data/theme/gnome-shell.css
index ae7f59b..091d98f 100644
--- a/data/theme/gnome-shell.css
+++ b/data/theme/gnome-shell.css
@@ -1655,3 +1655,49 @@ StTooltip StLabel {
.magnifier-zoom-region.full-screen {
border-width: 0px;
}
+
+
+/* goa message popup */
+
+.goa-message-table {
+}
+
+.goa-message-base {
+ font-size: 9pt;
+}
+
+.goa-message-from-header {
+ color: #666666;
+ font-weight: bold;
+}
+
+.goa-message-subject-header {
+ color: #666666;
+ font-weight: bold;
+}
+
+.goa-message-date-header {
+ color: #666666;
+ font-weight: bold;
+}
+
+.goa-message-from {
+ font-weight: bold;
+ min-width: 125px;
+}
+
+.goa-message-hbox {
+ spacing: 0.25em;
+ min-width: 300px;
+}
+
+.goa-message-subject {
+}
+
+.goa-message-excerpt {
+ color: rgba(153, 153, 153, 1.0);
+}
+
+.goa-message-date {
+ min-width: 80px;
+}
diff --git a/js/Makefile.am b/js/Makefile.am
index a085bfc..856935e 100644
--- a/js/Makefile.am
+++ b/js/Makefile.am
@@ -26,6 +26,7 @@ nobase_dist_js_DATA = \
ui/endSessionDialog.js \
ui/environment.js \
ui/extensionSystem.js \
+ ui/goaClient.js \
ui/iconGrid.js \
ui/lightbox.js \
ui/link.js \
diff --git a/js/ui/goaClient.js b/js/ui/goaClient.js
new file mode 100644
index 0000000..91986d1
--- /dev/null
+++ b/js/ui/goaClient.js
@@ -0,0 +1,464 @@
+/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
+
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
+const Goa = imports.gi.Goa;
+const Lang = imports.lang;
+const Mainloop = imports.mainloop;
+const Shell = imports.gi.Shell;
+const Signals = imports.signals;
+const St = imports.gi.St;
+const Gettext = imports.gettext.domain('gnome-shell');
+const _ = Gettext.gettext;
+const C_ = Gettext.pgettext;
+const Gtk = imports.gi.Gtk;
+const Pango = imports.gi.Pango;
+
+const History = imports.misc.history;
+const Main = imports.ui.main;
+const MessageTray = imports.ui.messageTray;
+
+// ----------------------------------------------------------------------------------------------------
+
+function Client() {
+ this._init();
+}
+
+Client.prototype = {
+ _init : function() {
+ this._client = null;
+
+ this._accountIdToMailMonitor = {}
+ this._mailSource = null;
+
+ // TODO: need to call refreshAllMonitors() when network-connectivity changes
+
+ Goa.Client.new(null, /* cancellable */
+ Lang.bind(this, this._onClientConstructed));
+ },
+
+ _onClientConstructed : function(object, asyncRes) {
+ this._client = object.new_finish(asyncRes);
+ this._updateAccounts();
+ this._client.connect('account-added', Lang.bind(this, this._updateAccounts));
+ this._client.connect('account-removed', Lang.bind(this, this._updateAccounts));
+ this._client.connect('account-changed', Lang.bind(this, this._updateAccounts));
+ },
+
+ _updateAccounts : function () {
+
+ let objects = this._client.get_accounts();
+ let mailIds = {};
+
+ // Add monitors for accounts that now exist
+ for (let n = 0; n < objects.length; n++) {
+ let object = objects[n];
+ let id = object.account.id;
+
+ if (object.mail) {
+ mailIds[id] = true;
+ if (!(id in this._accountIdToMailMonitor)) {
+ let monitor = new MailMonitor(this, object);
+ this._accountIdToMailMonitor[id] = monitor;
+ }
+ }
+ }
+
+ // Nuke monitors for accounts that are now non-existant
+ let monitorsToRemove = []
+ for (let existingMonitorId in this._accountIdToMailMonitor) {
+ if (!(existingMonitorId in mailIds)) {
+ monitorsToRemove.push(existingMonitorId);
+ }
+ }
+ for (let n = 0; n < monitorsToRemove.length; n++) {
+ let id = monitorsToRemove[n];
+ let monitor = this._accountIdToMailMonitor[id];
+ delete this._accountIdToMailMonitor[id]
+ monitor.destroy();
+ }
+ },
+
+ _ensureMailSource: function() {
+ if (!this._mailSource) {
+ this._mailSource = new MailSource(this);
+ this._mailSource.connect('destroy', Lang.bind(this,
+ function () {
+ this._mailSource = null;
+ }));
+ Main.messageTray.add(this._mailSource);
+ }
+ },
+
+ addPendingMessage: function(message) {
+ this._ensureMailSource();
+ this._mailSource.addMessage(message);
+ },
+
+ refreshAllMonitors: function() {
+ log('Refreshing all mail monitors');
+ for (let id in this._accountIdToMailMonitor) {
+ let monitor = this._accountIdToMailMonitor[id];
+ monitor.refresh();
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+function Message(uid, from, subject, excerpt, uri) {
+ this._init(uid, from, subject, excerpt, uri);
+}
+
+Message.prototype = {
+ _init: function(uid, from, subject, excerpt, uri) {
+ this.uid = uid;
+ this.from = from;
+ this.subject = subject;
+ this.excerpt = excerpt;
+ this.uri = uri;
+ this.receivedAt = new Date();
+ }
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+function MailMonitor(client, accountObject) {
+ this._init(client, accountObject);
+}
+
+MailMonitor.prototype = {
+ _init : function(client, accountObject) {
+ this._client = client;
+ this._accountObject = accountObject;
+ this._account = this._accountObject.get_account();
+ this._mail = this._accountObject.get_mail();
+
+ // Create the remote monitor object
+ this._proxy = null;
+ this._cancellable = new Gio.Cancellable();
+ this._mail.call_create_monitor(this._cancellable, Lang.bind(this, this._onMonitorCreated));
+ },
+
+ destroy : function() {
+ this._cancellable.cancel();
+ if (this._proxy) {
+ // We don't really care if this fails or not
+ this._proxy.call_close(null, Lang.bind(this, function() { }));
+ this._proxy.disconnect(this._messageReceivedId);
+ this._proxy = null;
+ }
+ },
+
+ refresh : function() {
+ if (this._proxy) {
+ // We don't really care if this fails or not
+ log('Refreshing mail monitor for account ' + this._account.name);
+ this._proxy.call_refresh(null, Lang.bind(this, function() { }));
+ }
+ },
+
+ _onMonitorCreated : function(mail, asyncRes) {
+ // TODO: a (gboolean, object_path) tuple is returned here
+ // See https://bugzilla.gnome.org/show_bug.cgi?id=649657
+ let ret = mail.call_create_monitor_finish(asyncRes);
+ let object_path = ret[1];
+ Goa.MailMonitorProxy.new_for_bus(Gio.BusType.SESSION,
+ Gio.DBusProxyFlags.NONE,
+ 'org.gnome.OnlineAccounts',
+ object_path,
+ null, /* cancellable */
+ Lang.bind(this, this._onMonitorProxyConstructed));
+ },
+
+ _onMonitorProxyConstructed : function(monitor, asyncRes) {
+ this._proxy = monitor.new_for_bus_finish(asyncRes);
+
+ // Now listen for changes on the mail monitor proxy
+ this._messageReceivedId = this._proxy.connect('message-received',
+ Lang.bind(this, this._onMessageReceived));
+ },
+
+ _onMessageReceived : function(monitor, uid, from, subject, excerpt, uri) {
+ let message = new Message(uid, from, subject, excerpt, uri);
+ if (!Main.messageTray.getBusy()) {
+ let source = new Source(this._client, message);
+ let notification = new Notification(source, this._client, message);
+ // If the user is not marked as busy, present the notification to the user
+ Main.messageTray.add(source);
+ source.notify(notification);
+ } else {
+ // ... otherwise, if the user is busy, just add it to the MailSource's list
+ // of pending messages
+ this._client.addPendingMessage(message);
+ }
+ }
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+function Source(client, message) {
+ this._init(client, message);
+}
+
+Source.prototype = {
+ __proto__: MessageTray.Source.prototype,
+
+ _init : function(client, message) {
+ this._client = client;
+ this._message = message;
+
+ // Init super class and add ourselves to the message tray
+ MessageTray.Source.prototype._init.call(this, 'Message from ' + _stripEmailAddress(this._message.from));
+ this.setTransient(true);
+ this.isChat = true;
+ this._setSummaryIcon(this.createNotificationIcon());
+ },
+
+ createNotificationIcon : function() {
+ // TODO: use account icon
+ let icon = new St.Icon({ icon_type: St.IconType.FULLCOLOR,
+ icon_size: this.ICON_SIZE,
+ icon_name: 'mail-send'});
+ return icon;
+ }
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+function _stripEmailAddress(name_and_addr) {
+ let bracketStartPos = name_and_addr.indexOf(' <');
+ if (bracketStartPos == -1) {
+ return name_and_addr;
+ } else {
+ return name_and_addr.slice(0, bracketStartPos);
+ }
+}
+
+function Notification(source, client, message) {
+ this._init(source, client, message);
+}
+
+Notification.prototype = {
+ __proto__: MessageTray.Notification.prototype,
+
+ _init : function(source, client, message) {
+ this._message = message;
+ this._client = client;
+ this._ignore = false;
+ this._alreadyExpanded = false;
+
+ this._strippedFrom = _stripEmailAddress(this._message.from);
+
+ let title = this._strippedFrom;
+ let banner = this._message.subject + ' \u2014 ' + this._message.excerpt; // â?? U+2014 EM DASH
+
+ // Init super class
+ MessageTray.Notification.prototype._init.call(this, source, title, banner);
+
+ // Change the contents once expanded
+ this.connect('expanded', Lang.bind (this, this._onExpanded));
+
+ this.update(title, banner);
+ this.setUrgency(MessageTray.Urgency.NORMAL);
+ this.setTransient(true);
+
+ this.addButton('ignore', 'Ignore');
+ this.addButton('junk', 'Junk');
+ if (this._message.uri.length > 0) {
+ this.addButton('open', 'Open');
+ }
+ this.connect('action-invoked', Lang.bind(this,
+ function(notification, id) {
+ if (id == 'ignore') {
+ this._actionIgnore();
+ } else if (id == 'junk') {
+ this._actionJunk();
+ } else if (id == 'open') {
+ this._actionOpen();
+ }
+ }));
+ this.connect('clicked', Lang.bind(this,
+ function() {
+ if (this._message.uri.length > 0) {
+ this._actionOpen();
+ }
+ }));
+ // Hmm, should be ::done-displaying instead?
+ this.connect('destroy', Lang.bind(this, this._onDestroyed));
+ },
+
+ _onExpanded : function() {
+ if (this._alreadyExpanded)
+ return;
+ this._alreadyExpanded = true;
+ let escapedExcerpt = GLib.markup_escape_text(this._message.excerpt, -1);
+ let bannerMarkup = '<b>Subject:</b> ' + this._message.subject + '\n';
+ // TODO: if available, insert other headers such as Cc
+ bannerMarkup += '\n' + escapedExcerpt;
+ this.update(this._strippedFrom, bannerMarkup, {bannerMarkup: true});
+ },
+
+ _onDestroyed : function(reason) {
+ // If not ignoring the message, push it onto the Mail source
+ if (!this._ignore) {
+ this._client.addPendingMessage(this._message);
+ }
+ },
+
+ _actionIgnore : function() {
+ this._ignore = true;
+ },
+
+ _actionJunk : function() {
+ this._ignore = true;
+ log('TODO: actually junk the message');
+ },
+
+ _actionOpen : function() {
+ this._ignore = true;
+ Gio.app_info_launch_default_for_uri(this._message.uri,
+ global.create_app_launch_context());
+ }
+
+}
+
+// ----------------------------------------------------------------------------------------------------
+
+function _sameDay(dateA, dateB) {
+ return (dateA.getDate() == dateB.getDate() &&
+ dateA.getMonth() == dateB.getMonth() &&
+ dateA.getYear() == dateB.getYear());
+}
+
+function _sameYear(dateA, dateB) {
+ return (dateA.getYear() == dateB.getYear());
+}
+
+function _formatRelativeDate(date) {
+ let ret = ''
+ let now = new Date();
+ if (_sameDay(date, now)) {
+ ret = date.toLocaleFormat("%l:%M %p");
+ } else {
+ if (_sameYear(date, now)) {
+ ret = date.toLocaleFormat("%B %e");
+ } else {
+ ret = date.toLocaleFormat("%B %e, %Y");
+ }
+ }
+ return ret;
+}
+
+function _addMessageToTable(table, message) {
+ let formattedExcerpt = message.excerpt.replace(/\r/g, '').replace(/\n/g, ' ');
+ let formattedDate = _formatRelativeDate(message.receivedAt);
+
+ let fromLabel = new St.Label({ style_class: 'goa-message-base goa-message-from',
+ text: _stripEmailAddress(message.from)});
+ let hbox = new St.BoxLayout({ style_class: 'goa-message-hbox', vertical: false });
+ let subjectLabel = new St.Label({ style_class: 'goa-message-base goa-message-subject',
+ text: message.subject });
+ let excerptLabel = new St.Label({ style_class: 'goa-message-base goa-message-excerpt',
+ text: formattedExcerpt });
+ let dateLabel = new St.Label({ style_class: 'goa-message-base goa-message-date',
+ text: formattedDate });
+
+ excerptLabel.clutter_text.line_wrap = false;
+ excerptLabel.clutter_text.ellipsize = Pango.EllipsizeMode.END;
+
+ hbox.add(subjectLabel, { x_fill: true,
+ y_fill: false,
+ x_align: St.Align.END,
+ y_align: St.Align.START });
+ hbox.add(excerptLabel, { x_fill: true,
+ y_fill: false,
+ x_align: St.Align.END,
+ y_align: St.Align.START });
+
+ let n = table.get_row_count();
+ table.add(fromLabel, { x_fill: true, x_expand: true, row: n, col: 0 });
+ table.add(hbox, { row: n, col: 1 });
+ table.add(dateLabel, { row: n, col: 2 });
+}
+
+function MailSource(client) {
+ this._init(client);
+}
+
+MailSource.prototype = {
+ __proto__: MessageTray.Source.prototype,
+
+ _init : function(client) {
+ this._client = client;
+ this._pendingMessages = [];
+
+ // Init super class and add ourselves to the message tray
+ MessageTray.Source.prototype._init.call(this, 'Mail');
+
+ // Create the notification
+ this._notification = new MessageTray.Notification(this)
+ this._notification.setUrgency(MessageTray.Urgency.NORMAL);
+ this._notification.setResident(true);
+ this._updateNotification();
+ this.pushNotification(this._notification);
+ // Refresh all monitors everytime the "Mail" notification is displayed
+ this._notification.connect('expanded', Lang.bind(this,
+ function() {
+ this._client.refreshAllMonitors();
+ }));
+ },
+
+ createNotificationIcon : function() {
+ let numPending = this._pendingMessages.length;
+ let baseIcon = new Gio.ThemedIcon({ name: 'mail-mark-unread'});
+ let numerableIcon = new Gtk.NumerableIcon({ gicon: baseIcon });
+ numerableIcon.set_count(numPending);
+ let icon = new St.Icon({ icon_type: St.IconType.FULLCOLOR,
+ icon_size: this.ICON_SIZE });
+ icon.set_gicon(numerableIcon);
+ return icon;
+ },
+
+ _updateNotification: function() {
+ if (!this._notification)
+ return
+
+ let title = 'Mail';
+ let banner = ''
+ let table = new St.Table({ homogeneous: false,
+ style_class: 'goa-message-table',
+ reactive: true });
+
+ for (let n = 0; n < this._pendingMessages.length; n++)
+ _addMessageToTable (table, this._pendingMessages[n]);
+
+ this._notification.update(title, banner, { clear: true,
+ icon: this.createNotificationIcon() });
+ this._notification.addActor(table);
+ this._notification.addButton('clear', 'Clear');
+ this._notification.connect('action-invoked', Lang.bind(this,
+ function(notification, id) {
+ if (id == 'clear') {
+ this.clearMessages();
+ }
+ }));
+ },
+
+ addMessage: function(message) {
+ this._pendingMessages.push(message);
+ // Update notification
+ this._updateNotification();
+ // Update icon with latest pending count
+ this._setSummaryIcon(this.createNotificationIcon());
+ },
+
+ clearMessages: function() {
+ let notification = this._notification;
+ this._notification = null;
+ if (notification)
+ notification.destroy();
+ this.destroy();
+ },
+}
+
diff --git a/js/ui/main.js b/js/ui/main.js
index 1d27b4c..05764d5 100644
--- a/js/ui/main.js
+++ b/js/ui/main.js
@@ -29,6 +29,7 @@ const WindowAttentionHandler = imports.ui.windowAttentionHandler;
const Scripting = imports.ui.scripting;
const ShellDBus = imports.ui.shellDBus;
const TelepathyClient = imports.ui.telepathyClient;
+const GoaClient = imports.ui.goaClient;
const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler;
@@ -50,6 +51,7 @@ let messageTray = null;
let notificationDaemon = null;
let windowAttentionHandler = null;
let telepathyClient = null;
+let goaClient = null;
let ctrlAltTabManager = null;
let recorder = null;
let shellDBusService = null;
@@ -139,6 +141,7 @@ function start() {
notificationDaemon = new NotificationDaemon.NotificationDaemon();
windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
telepathyClient = new TelepathyClient.Client();
+ goaClient = new GoaClient.Client();
overview.init();
statusIconDispatcher.start(messageTray.actor);
diff --git a/js/ui/messageTray.js b/js/ui/messageTray.js
index 8c7dbf8..9155e48 100644
--- a/js/ui/messageTray.js
+++ b/js/ui/messageTray.js
@@ -1301,6 +1301,10 @@ MessageTray.prototype = {
},
+ getBusy: function(source) {
+ return this._busy;
+ },
+
contains: function(source) {
return this._getIndexOfSummaryItemForSource(source) >= 0;
},
diff --git a/src/st/st-texture-cache.c b/src/st/st-texture-cache.c
index fba794a..c866cdd 100644
--- a/src/st/st-texture-cache.c
+++ b/src/st/st-texture-cache.c
@@ -1174,17 +1174,17 @@ load_gicon_with_colors (StTextureCache *cache,
{
AsyncTextureLoadData *request;
ClutterActor *texture;
- char *gicon_string;
+ guint gicon_hash;
char *key;
GtkIconTheme *theme;
GtkIconInfo *info;
- gicon_string = g_icon_to_string (icon);
+ gicon_hash = g_icon_hash (icon);
if (colors)
{
/* This raises some doubts about the practice of using string keys */
- key = g_strdup_printf (CACHE_PREFIX_GICON "icon=%s,size=%d,colors=%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x",
- gicon_string, size,
+ key = g_strdup_printf (CACHE_PREFIX_GICON "icon_hash=%u,size=%d,colors=%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x,%2x%2x%2x%2x",
+ gicon_hash, size,
colors->foreground.red, colors->foreground.blue, colors->foreground.green, colors->foreground.alpha,
colors->warning.red, colors->warning.blue, colors->warning.green, colors->warning.alpha,
colors->error.red, colors->error.blue, colors->error.green, colors->error.alpha,
@@ -1192,10 +1192,9 @@ load_gicon_with_colors (StTextureCache *cache,
}
else
{
- key = g_strdup_printf (CACHE_PREFIX_GICON "icon=%s,size=%d",
- gicon_string, size);
+ key = g_strdup_printf (CACHE_PREFIX_GICON "icon_hash=%u,size=%d",
+ gicon_hash, size);
}
- g_free (gicon_string);
if (create_texture_and_ensure_request (cache, key, size, &request, &texture))
{
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]