2010-01-13 20:05:20 +00:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const Clutter = imports.gi.Clutter;
|
2010-02-22 19:23:36 +00:00
|
|
|
const Gtk = imports.gi.Gtk;
|
2010-01-13 20:05:20 +00:00
|
|
|
const Lang = imports.lang;
|
|
|
|
const Mainloop = imports.mainloop;
|
2010-02-01 17:10:38 +00:00
|
|
|
const Pango = imports.gi.Pango;
|
2010-01-28 17:04:26 +00:00
|
|
|
const Shell = imports.gi.Shell;
|
2010-01-13 20:05:20 +00:00
|
|
|
const Signals = imports.signals;
|
2010-01-28 17:04:26 +00:00
|
|
|
const St = imports.gi.St;
|
2010-01-13 20:05:20 +00:00
|
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
|
|
|
|
const ANIMATION_TIME = 0.2;
|
|
|
|
const NOTIFICATION_TIMEOUT = 4;
|
2010-01-28 18:39:00 +00:00
|
|
|
const SUMMARY_TIMEOUT = 1;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-02-25 19:42:18 +00:00
|
|
|
const HIDE_TIMEOUT = 0.2;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
|
|
|
const ICON_SIZE = 24;
|
2010-06-11 21:19:18 +00:00
|
|
|
const BUTTON_ICON_SIZE = 36;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
const MAX_SOURCE_TITLE_WIDTH = 180;
|
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
const State = {
|
|
|
|
HIDDEN: 0,
|
|
|
|
SHOWING: 1,
|
|
|
|
SHOWN: 2,
|
|
|
|
HIDING: 3
|
2010-01-28 18:39:00 +00:00
|
|
|
};
|
|
|
|
|
2010-02-01 20:23:49 +00:00
|
|
|
function _cleanMarkup(text) {
|
2010-02-20 03:28:47 +00:00
|
|
|
// Support &, ", ', < and >, escape all other
|
|
|
|
// occurrences of '&'.
|
2010-05-13 19:46:04 +00:00
|
|
|
let _text = text.replace(/&(?!amp;|quot;|apos;|lt;|gt;)/g, '&');
|
2010-02-01 20:23:49 +00:00
|
|
|
// Support <b>, <i>, and <u>, escape anything else
|
|
|
|
// so it displays as raw markup.
|
2010-05-13 19:46:04 +00:00
|
|
|
return _text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, '<$1');
|
2010-02-01 20:23:49 +00:00
|
|
|
}
|
|
|
|
|
2010-02-01 17:10:38 +00:00
|
|
|
// Notification:
|
2010-02-22 22:19:32 +00:00
|
|
|
// @id: the notification's id
|
2010-02-01 17:10:38 +00:00
|
|
|
// @source: the notification's Source
|
|
|
|
// @title: the title
|
|
|
|
// @banner: the banner text
|
2010-02-12 20:15:03 +00:00
|
|
|
// @bannerBody: whether or not to promote the banner to the body on overflow
|
2010-02-01 17:10:38 +00:00
|
|
|
//
|
|
|
|
// Creates a notification. In banner mode, it will show
|
2010-02-01 20:23:49 +00:00
|
|
|
// @source's icon, @title (in bold) and @banner, all on a single line
|
2010-02-09 16:25:10 +00:00
|
|
|
// (with @banner ellipsized if necessary).
|
|
|
|
//
|
2010-02-22 19:23:36 +00:00
|
|
|
// Additional notification details can be added, in which case the
|
|
|
|
// notification can be expanded by moving the pointer into it. In
|
|
|
|
// expanded mode, the banner text disappears, and there can be one or
|
|
|
|
// more rows of additional content. This content is put inside a
|
|
|
|
// scrollview, so if it gets too tall, the notification will scroll
|
|
|
|
// rather than continuing to grow. In addition to this main content
|
|
|
|
// area, there is also a single-row "action area", which is not
|
|
|
|
// scrolled and can contain a single actor. There are also convenience
|
|
|
|
// methods for creating a button box in the action area.
|
2010-02-09 16:25:10 +00:00
|
|
|
//
|
2010-02-12 20:15:03 +00:00
|
|
|
// If @bannerBody is %true, then @banner will also be used as the body
|
|
|
|
// of the notification (as with addBody()) when the banner is expanded.
|
|
|
|
// In this case, if @banner is too long to fit in the single-line mode,
|
|
|
|
// the notification will be made expandable automatically.
|
2010-02-22 22:19:32 +00:00
|
|
|
function Notification(id, source, title, banner, bannerBody) {
|
|
|
|
this._init(id, source, title, banner, bannerBody);
|
2010-01-13 20:05:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Notification.prototype = {
|
2010-02-22 22:19:32 +00:00
|
|
|
_init: function(id, source, title, banner, bannerBody) {
|
|
|
|
this.id = id;
|
2010-01-28 18:39:00 +00:00
|
|
|
this.source = source;
|
2010-02-12 20:15:03 +00:00
|
|
|
this._bannerBody = bannerBody;
|
2010-04-28 19:34:27 +00:00
|
|
|
this.urgent = false;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-02-22 22:19:32 +00:00
|
|
|
source.connect('clicked', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this.emit('dismissed');
|
|
|
|
}));
|
|
|
|
|
2010-07-14 21:07:06 +00:00
|
|
|
source.connect('destroy', Lang.bind(this, this.destroy));
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this.actor = new St.Table({ name: 'notification',
|
|
|
|
reactive: true });
|
2010-04-29 14:54:05 +00:00
|
|
|
this.actor.connect('style-changed', Lang.bind(this, this._styleChanged));
|
2010-02-09 16:25:10 +00:00
|
|
|
this.update(title, banner, true);
|
|
|
|
},
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-02-09 16:25:10 +00:00
|
|
|
// update:
|
|
|
|
// @title: the new title
|
|
|
|
// @banner: the new banner
|
2010-02-22 19:23:36 +00:00
|
|
|
// @clear: whether or not to clear out body and action actors
|
2010-02-09 16:25:10 +00:00
|
|
|
//
|
|
|
|
// Updates the notification by regenerating its icon and updating
|
|
|
|
// the title/banner. If @clear is %true, it will also remove any
|
|
|
|
// additional actors/action buttons previously added.
|
|
|
|
update: function(title, banner, clear) {
|
2010-02-22 19:23:36 +00:00
|
|
|
if (this._icon)
|
|
|
|
this._icon.destroy();
|
|
|
|
if (this._bannerBox)
|
|
|
|
this._bannerBox.destroy();
|
|
|
|
if (this._scrollArea && (this._bannerBody || clear)) {
|
|
|
|
this._scrollArea.destroy();
|
|
|
|
this._scrollArea = null;
|
|
|
|
this._contentArea = null;
|
2010-02-09 16:25:10 +00:00
|
|
|
}
|
2010-02-22 19:23:36 +00:00
|
|
|
if (this._actionArea && clear) {
|
|
|
|
this._actionArea.destroy();
|
|
|
|
this._actionArea = null;
|
|
|
|
this._buttonBox = null;
|
2010-02-09 16:25:10 +00:00
|
|
|
}
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this._icon = this.source.createIcon(ICON_SIZE);
|
|
|
|
this._icon.reactive = true;
|
|
|
|
this.actor.add(this._icon, { row: 0,
|
|
|
|
col: 0,
|
|
|
|
x_expand: false,
|
|
|
|
y_expand: false,
|
|
|
|
y_fill: false });
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this._icon.connect('button-release-event', Lang.bind(this,
|
2010-02-15 17:27:28 +00:00
|
|
|
function () {
|
|
|
|
this.source.clicked();
|
|
|
|
}));
|
|
|
|
|
2010-02-01 17:10:38 +00:00
|
|
|
// The first line should have the title, followed by the
|
|
|
|
// banner text, but ellipsized if they won't both fit. We can't
|
|
|
|
// make St.Table or St.BoxLayout do this the way we want (don't
|
|
|
|
// show banner at all if title needs to be ellipsized), so we
|
|
|
|
// use Shell.GenericContainer.
|
|
|
|
this._bannerBox = new Shell.GenericContainer();
|
|
|
|
this._bannerBox.connect('get-preferred-width', Lang.bind(this, this._bannerBoxGetPreferredWidth));
|
|
|
|
this._bannerBox.connect('get-preferred-height', Lang.bind(this, this._bannerBoxGetPreferredHeight));
|
|
|
|
this._bannerBox.connect('allocate', Lang.bind(this, this._bannerBoxAllocate));
|
|
|
|
this.actor.add(this._bannerBox, { row: 0,
|
|
|
|
col: 1,
|
|
|
|
y_expand: false,
|
|
|
|
y_fill: false });
|
|
|
|
|
2010-02-09 16:20:51 +00:00
|
|
|
this._titleLabel = new St.Label();
|
2010-02-20 21:08:54 +00:00
|
|
|
title = title ? _cleanMarkup(title.replace(/\n/g, ' ')) : '';
|
2010-02-09 16:20:51 +00:00
|
|
|
this._titleLabel.clutter_text.set_markup('<b>' + title + '</b>');
|
|
|
|
this._bannerBox.add_actor(this._titleLabel);
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2010-02-12 20:15:03 +00:00
|
|
|
if (this._bannerBody)
|
|
|
|
this._bannerBodyText = banner;
|
|
|
|
else
|
|
|
|
this._bannerBodyText = null;
|
2010-02-09 16:25:10 +00:00
|
|
|
|
2010-02-09 16:20:51 +00:00
|
|
|
this._bannerLabel = new St.Label();
|
2010-02-20 21:08:54 +00:00
|
|
|
banner = banner ? _cleanMarkup(banner.replace(/\n/g, ' ')) : '';
|
2010-02-09 16:20:51 +00:00
|
|
|
this._bannerLabel.clutter_text.set_markup(banner);
|
|
|
|
this._bannerBox.add_actor(this._bannerLabel);
|
2010-04-29 14:54:05 +00:00
|
|
|
|
|
|
|
// Add the bannerBody now if we know for sure we'll need it
|
|
|
|
if (this._bannerBodyText && this._bannerBodyText.indexOf('\n') > -1)
|
|
|
|
this._addBannerBody();
|
2010-02-09 16:25:10 +00:00
|
|
|
},
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2010-02-09 16:25:10 +00:00
|
|
|
// addActor:
|
2010-02-22 19:23:36 +00:00
|
|
|
// @actor: actor to add to the body of the notification
|
2010-02-09 16:25:10 +00:00
|
|
|
//
|
2010-02-22 19:23:36 +00:00
|
|
|
// Appends @actor to the notification's body
|
|
|
|
addActor: function(actor) {
|
|
|
|
if (!this._scrollArea) {
|
|
|
|
this._scrollArea = new St.ScrollView({ name: 'notification-scrollview',
|
|
|
|
vscrollbar_policy: Gtk.PolicyType.AUTOMATIC,
|
|
|
|
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
|
|
|
vshadows: true });
|
|
|
|
this.actor.add(this._scrollArea, { row: 1,
|
|
|
|
col: 1 });
|
|
|
|
this._contentArea = new St.BoxLayout({ name: 'notification-body',
|
|
|
|
vertical: true });
|
|
|
|
this._scrollArea.add_actor(this._contentArea);
|
2010-02-01 17:10:38 +00:00
|
|
|
}
|
2010-02-01 20:23:49 +00:00
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this._contentArea.add(actor);
|
2010-02-01 20:41:22 +00:00
|
|
|
},
|
|
|
|
|
2010-02-09 16:25:10 +00:00
|
|
|
// addBody:
|
|
|
|
// @text: the text
|
|
|
|
//
|
|
|
|
// Adds a multi-line label containing @text to the notification.
|
2010-02-22 19:23:36 +00:00
|
|
|
//
|
|
|
|
// Return value: the newly-added label
|
|
|
|
addBody: function(text) {
|
2010-02-09 16:25:10 +00:00
|
|
|
let body = new St.Label();
|
|
|
|
body.clutter_text.line_wrap = true;
|
2010-02-12 21:10:24 +00:00
|
|
|
body.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
|
2010-02-09 16:25:10 +00:00
|
|
|
body.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
|
|
|
|
text = text ? _cleanMarkup(text) : '';
|
|
|
|
body.clutter_text.set_markup(text);
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this.addActor(body);
|
|
|
|
return body;
|
2010-02-09 16:25:10 +00:00
|
|
|
},
|
|
|
|
|
2010-02-12 20:15:03 +00:00
|
|
|
_addBannerBody: function() {
|
2010-02-22 19:23:36 +00:00
|
|
|
this.addBody(this._bannerBodyText);
|
2010-02-12 20:15:03 +00:00
|
|
|
this._bannerBodyText = null;
|
|
|
|
},
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
// scrollTo:
|
|
|
|
// @side: St.Side.TOP or St.Side.BOTTOM
|
|
|
|
//
|
|
|
|
// Scrolls the content area (if scrollable) to the indicated edge
|
|
|
|
scrollTo: function(side) {
|
|
|
|
// Hack to force a relayout, since the caller probably
|
|
|
|
// just added or removed something to scrollArea, and
|
|
|
|
// the adjustment needs to reflect that.
|
|
|
|
global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, 0, 0);
|
|
|
|
|
|
|
|
let adjustment = this._scrollArea.vscroll.adjustment;
|
|
|
|
if (side == St.Side.TOP)
|
|
|
|
adjustment.value = adjustment.lower;
|
|
|
|
else if (side == St.Side.BOTTOM)
|
|
|
|
adjustment.value = adjustment.upper;
|
|
|
|
},
|
|
|
|
|
|
|
|
// setActionArea:
|
|
|
|
// @actor: the actor
|
|
|
|
// @props: (option) St.Table child properties
|
|
|
|
//
|
|
|
|
// Puts @actor into the action area of the notification, replacing
|
|
|
|
// the previous contents
|
|
|
|
setActionArea: function(actor, props) {
|
|
|
|
if (this._actionArea) {
|
|
|
|
this._actionArea.destroy();
|
|
|
|
this._actionArea = null;
|
|
|
|
if (this._buttonBox)
|
|
|
|
this._buttonBox = null;
|
|
|
|
}
|
|
|
|
this._actionArea = actor;
|
|
|
|
|
|
|
|
if (!props)
|
|
|
|
props = {};
|
|
|
|
props.row = 2;
|
|
|
|
props.col = 1;
|
|
|
|
|
|
|
|
this.actor.add(this._actionArea, props);
|
|
|
|
},
|
|
|
|
|
|
|
|
// addButton:
|
2010-02-09 16:25:10 +00:00
|
|
|
// @id: the action ID
|
|
|
|
// @label: the label for the action's button
|
|
|
|
//
|
|
|
|
// Adds a button with the given @label to the notification. All
|
|
|
|
// action buttons will appear in a single row at the bottom of
|
|
|
|
// the notification.
|
|
|
|
//
|
|
|
|
// If the button is clicked, the notification will emit the
|
|
|
|
// %action-invoked signal with @id as a parameter
|
2010-02-22 19:23:36 +00:00
|
|
|
addButton: function(id, label) {
|
|
|
|
if (!this._buttonBox) {
|
2010-02-12 20:15:03 +00:00
|
|
|
if (this._bannerBodyText)
|
|
|
|
this._addBannerBody();
|
|
|
|
|
2010-02-09 16:25:10 +00:00
|
|
|
let box = new St.BoxLayout({ name: 'notification-actions' });
|
2010-02-22 19:23:36 +00:00
|
|
|
this.setActionArea(box, { x_expand: false,
|
|
|
|
x_fill: false,
|
|
|
|
x_align: St.Align.END });
|
|
|
|
this._buttonBox = box;
|
2010-02-01 20:41:22 +00:00
|
|
|
}
|
|
|
|
|
2010-06-11 21:19:18 +00:00
|
|
|
let button = new St.Button();
|
|
|
|
|
|
|
|
if (Gtk.IconTheme.get_default().has_icon(id)) {
|
|
|
|
button.add_style_class_name('notification-icon-button');
|
|
|
|
button.child = St.TextureCache.get_default().load_icon_name(id, BUTTON_ICON_SIZE);
|
|
|
|
} else {
|
|
|
|
button.add_style_class_name('notification-button');
|
|
|
|
button.label = label;
|
|
|
|
}
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this._buttonBox.add(button);
|
2010-02-01 20:41:22 +00:00
|
|
|
button.connect('clicked', Lang.bind(this, function() { this.emit('action-invoked', id); }));
|
2010-02-01 17:10:38 +00:00
|
|
|
},
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-04-28 19:34:27 +00:00
|
|
|
setUrgent: function(urgent) {
|
|
|
|
this.urgent = urgent;
|
|
|
|
},
|
|
|
|
|
2010-04-29 14:54:05 +00:00
|
|
|
_styleChanged: function() {
|
|
|
|
let [has_spacing, spacing] = this.actor.get_theme_node().get_length('spacing-columns', false);
|
|
|
|
this._spacing = has_spacing ? spacing : 0;
|
|
|
|
|
|
|
|
// Figure out now (before allocation starts) whether or not we
|
|
|
|
// need to be expandable, and add the expansion row if so
|
|
|
|
if (this._bannerBodyText) {
|
|
|
|
let [minBannerWidth, natBannerWidth] =
|
|
|
|
this._bannerBox.get_preferred_width(-1);
|
|
|
|
let [minNotificationWidth, natNotificationWidth] =
|
|
|
|
this.actor.get_preferred_width(-1);
|
|
|
|
|
|
|
|
if (natBannerWidth > natNotificationWidth)
|
|
|
|
this._addBannerBody();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-02-01 17:10:38 +00:00
|
|
|
_bannerBoxGetPreferredWidth: function(actor, forHeight, alloc) {
|
2010-02-09 16:20:51 +00:00
|
|
|
let [titleMin, titleNat] = this._titleLabel.get_preferred_width(forHeight);
|
|
|
|
let [bannerMin, bannerNat] = this._bannerLabel.get_preferred_width(forHeight);
|
2010-01-28 18:39:00 +00:00
|
|
|
|
2010-02-01 17:10:38 +00:00
|
|
|
alloc.min_size = titleMin;
|
2010-04-29 14:54:05 +00:00
|
|
|
alloc.natural_size = titleNat + this._spacing + bannerNat;
|
2010-02-01 17:10:38 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_bannerBoxGetPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
[alloc.min_size, alloc.natural_size] =
|
2010-02-09 16:20:51 +00:00
|
|
|
this._titleLabel.get_preferred_height(forWidth);
|
2010-02-01 17:10:38 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_bannerBoxAllocate: function(actor, box, flags) {
|
2010-02-09 16:20:51 +00:00
|
|
|
let [titleMinW, titleNatW] = this._titleLabel.get_preferred_width(-1);
|
|
|
|
let [titleMinH, titleNatH] = this._titleLabel.get_preferred_height(-1);
|
|
|
|
let [bannerMinW, bannerNatW] = this._bannerLabel.get_preferred_width(-1);
|
2010-02-01 17:10:38 +00:00
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
|
|
|
|
let titleBox = new Clutter.ActorBox();
|
|
|
|
titleBox.x1 = titleBox.y1 = 0;
|
|
|
|
titleBox.x2 = Math.min(titleNatW, availWidth);
|
|
|
|
titleBox.y2 = titleNatH;
|
2010-02-09 16:20:51 +00:00
|
|
|
this._titleLabel.allocate(titleBox, flags);
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2010-04-29 14:54:05 +00:00
|
|
|
if (titleBox.x2 + this._spacing > availWidth) {
|
2010-02-09 16:20:51 +00:00
|
|
|
this._bannerLabel.hide();
|
2010-02-03 20:15:12 +00:00
|
|
|
} else {
|
|
|
|
let bannerBox = new Clutter.ActorBox();
|
2010-04-29 14:54:05 +00:00
|
|
|
bannerBox.x1 = titleBox.x2 + this._spacing;
|
2010-02-03 20:15:12 +00:00
|
|
|
bannerBox.y1 = 0;
|
|
|
|
bannerBox.x2 = Math.min(bannerBox.x1 + bannerNatW, availWidth);
|
|
|
|
bannerBox.y2 = titleNatH;
|
2010-02-09 16:20:51 +00:00
|
|
|
this._bannerLabel.show();
|
|
|
|
this._bannerLabel.allocate(bannerBox, flags);
|
2010-02-09 16:25:10 +00:00
|
|
|
}
|
2010-02-01 17:10:38 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
popOut: function() {
|
2010-02-09 16:25:10 +00:00
|
|
|
if (this.actor.row_count <= 1)
|
2010-02-01 17:10:38 +00:00
|
|
|
return false;
|
|
|
|
|
2010-02-09 16:20:51 +00:00
|
|
|
Tweener.addTween(this._bannerLabel,
|
2010-02-01 17:10:38 +00:00
|
|
|
{ opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad' });
|
2010-02-01 17:10:38 +00:00
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
popIn: function() {
|
2010-02-09 16:25:10 +00:00
|
|
|
if (this.actor.row_count <= 1)
|
2010-02-01 17:10:38 +00:00
|
|
|
return false;
|
2010-02-09 16:20:51 +00:00
|
|
|
Tweener.addTween(this._bannerLabel,
|
2010-02-01 17:10:38 +00:00
|
|
|
{ opacity: 255,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad' });
|
2010-02-01 17:10:38 +00:00
|
|
|
return true;
|
2010-02-22 22:19:32 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
this.emit('destroy');
|
2010-01-13 20:05:20 +00:00
|
|
|
}
|
|
|
|
};
|
2010-02-01 20:41:22 +00:00
|
|
|
Signals.addSignalMethods(Notification.prototype);
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
function Source(id, title, createIcon) {
|
|
|
|
this._init(id, title, createIcon);
|
2010-01-13 20:05:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
Source.prototype = {
|
2010-06-23 19:20:39 +00:00
|
|
|
_init: function(id, title, createIcon) {
|
2010-01-13 20:05:20 +00:00
|
|
|
this.id = id;
|
2010-06-23 19:20:39 +00:00
|
|
|
this.title = title;
|
2010-01-13 20:05:20 +00:00
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
2010-02-01 20:23:49 +00:00
|
|
|
notify: function(notification) {
|
2010-02-25 19:42:18 +00:00
|
|
|
if (this.notification)
|
|
|
|
this.notification.disconnect(this._notificationDestroyedId);
|
|
|
|
|
|
|
|
this.notification = notification;
|
|
|
|
|
|
|
|
this._notificationDestroyedId = notification.connect('destroy', Lang.bind(this,
|
|
|
|
function () {
|
|
|
|
if (this.notification == notification) {
|
|
|
|
this.notification = null;
|
|
|
|
this._notificationDestroyedId = 0;
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
2010-02-01 20:23:49 +00:00
|
|
|
this.emit('notify', notification);
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
clicked: function() {
|
|
|
|
this.emit('clicked');
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
this.emit('destroy');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Signals.addSignalMethods(Source.prototype);
|
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
function SummaryItem(source, minTitleWidth) {
|
|
|
|
this._init(source, minTitleWidth);
|
|
|
|
}
|
|
|
|
|
|
|
|
SummaryItem.prototype = {
|
|
|
|
_init: function(source, minTitleWidth) {
|
|
|
|
this.source = source;
|
|
|
|
// The message tray items should all be the same width when expanded. Because the only variation is introduced by the width of the title,
|
|
|
|
// we pass in the desired minimum title width, which is the maximum title width of the items which are currently in the tray. If the width
|
|
|
|
// of the title of this item is greater (up to MAX_SOURCE_TITLE_WIDTH), then that width will be used, and the width of all the other items
|
|
|
|
// in the message tray will be readjusted.
|
|
|
|
this._minTitleWidth = minTitleWidth;
|
|
|
|
this.actor = new St.Button({ style_class: 'summary-source-button',
|
|
|
|
reactive: true,
|
|
|
|
track_hover: true });
|
|
|
|
|
|
|
|
this._sourceBox = new Shell.GenericContainer({ style_class: 'summary-source',
|
|
|
|
reactive: true });
|
|
|
|
this._sourceBox.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
|
|
this._sourceBox.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
|
|
this._sourceBox.connect('allocate', Lang.bind(this, this._allocate));
|
|
|
|
|
|
|
|
this._sourceIcon = source.createIcon(ICON_SIZE);
|
|
|
|
this._sourceTitleBin = new St.Bin({ y_align: St.Align.MIDDLE, x_fill: true });
|
|
|
|
this._sourceTitle = new St.Label({ style_class: 'source-title',
|
|
|
|
text: source.title });
|
|
|
|
this._sourceTitle.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
this._sourceTitleBin.child = this._sourceTitle;
|
|
|
|
|
|
|
|
this._sourceBox.add_actor(this._sourceIcon);
|
|
|
|
this._sourceBox.add_actor(this._sourceTitleBin);
|
|
|
|
this._widthFraction = 0;
|
|
|
|
this.actor.child = this._sourceBox;
|
|
|
|
},
|
|
|
|
|
|
|
|
getTitleNaturalWidth: function() {
|
|
|
|
let [sourceTitleBinMinWidth, sourceTitleBinNaturalWidth] =
|
|
|
|
this._sourceTitleBin.get_preferred_width(-1);
|
|
|
|
return Math.min(sourceTitleBinNaturalWidth, MAX_SOURCE_TITLE_WIDTH);
|
|
|
|
},
|
|
|
|
|
|
|
|
setMinTitleWidth: function(minTitleWidth) {
|
|
|
|
this._minTitleWidth = minTitleWidth;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
|
|
let [found, spacing] = this._sourceBox.get_theme_node().get_length('spacing', false);
|
|
|
|
if (!found)
|
|
|
|
spacing = 0;
|
|
|
|
let [sourceIconMinWidth, sourceIconNaturalWidth] = this._sourceIcon.get_preferred_width(forHeight);
|
|
|
|
let [sourceTitleBinMinWidth, sourceTitleBinNaturalWidth] =
|
|
|
|
this._sourceTitleBin.get_preferred_width(forHeight);
|
|
|
|
let minWidth = sourceIconNaturalWidth +
|
|
|
|
(this._widthFraction > 0 ? spacing : 0) +
|
|
|
|
this._widthFraction * Math.min(Math.max(sourceTitleBinNaturalWidth, this._minTitleWidth),
|
|
|
|
MAX_SOURCE_TITLE_WIDTH);
|
|
|
|
alloc.min_size = minWidth;
|
|
|
|
alloc.natural_size = minWidth;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
let [sourceIconMinHeight, sourceIconNaturalHeight] = this._sourceIcon.get_preferred_height(forWidth);
|
|
|
|
alloc.min_size = sourceIconNaturalHeight;
|
|
|
|
alloc.natural_size = sourceIconNaturalHeight;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function (actor, box, flags) {
|
|
|
|
let width = box.x2 - box.x1;
|
|
|
|
let height = box.y2 - box.y1;
|
|
|
|
|
|
|
|
let [sourceIconMinWidth, sourceIconNaturalWidth] = this._sourceIcon.get_preferred_width(-1);
|
|
|
|
let [sourceIconMinHeight, sourceIconNaturalHeight] = this._sourceIcon.get_preferred_height(-1);
|
|
|
|
|
|
|
|
let iconBox = new Clutter.ActorBox();
|
|
|
|
iconBox.x1 = 0;
|
|
|
|
iconBox.y1 = 0;
|
|
|
|
iconBox.x2 = sourceIconNaturalWidth;
|
|
|
|
iconBox.y2 = sourceIconNaturalHeight;
|
|
|
|
|
|
|
|
this._sourceIcon.allocate(iconBox, flags);
|
|
|
|
|
|
|
|
let [found, spacing] = this._sourceBox.get_theme_node().get_length('spacing', false);
|
|
|
|
if (!found)
|
|
|
|
spacing = 0;
|
|
|
|
|
|
|
|
let titleBox = new Clutter.ActorBox();
|
|
|
|
if (width > sourceIconNaturalWidth + spacing) {
|
|
|
|
titleBox.x1 = iconBox.x2 + spacing;
|
|
|
|
titleBox.x2 = width;
|
|
|
|
} else {
|
|
|
|
titleBox.x1 = iconBox.x2;
|
|
|
|
titleBox.x2 = iconBox.x2;
|
|
|
|
}
|
|
|
|
titleBox.y1 = 0;
|
|
|
|
titleBox.y2 = height;
|
|
|
|
|
|
|
|
this._sourceTitleBin.allocate(titleBox, flags);
|
|
|
|
|
|
|
|
this._sourceTitleBin.set_clip(0, 0, titleBox.x2 - titleBox.x1, height);
|
|
|
|
},
|
|
|
|
|
|
|
|
expand: function() {
|
|
|
|
// this._adjustEllipsization replaces some text with the dots at the end of the animation,
|
|
|
|
// and then we replace the dots with the text before we begin the animation to collapse
|
|
|
|
// the title. These changes are not noticeable at the speed with which we do the animation,
|
|
|
|
// while animating in the ellipsized mode does not look good.
|
|
|
|
Tweener.addTween(this,
|
|
|
|
{ widthFraction: 1,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: 'linear',
|
|
|
|
onComplete: this._adjustEllipsization,
|
|
|
|
onCompleteScope: this });
|
|
|
|
},
|
|
|
|
|
|
|
|
collapse: function() {
|
|
|
|
this._sourceTitle.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
Tweener.addTween(this,
|
|
|
|
{ widthFraction: 0,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: 'linear' });
|
|
|
|
},
|
|
|
|
|
|
|
|
_adjustEllipsization: function() {
|
|
|
|
let [sourceTitleBinMinWidth, sourceTitleBinNaturalWidth] = this._sourceTitleBin.get_preferred_width(-1);
|
|
|
|
if (sourceTitleBinNaturalWidth > MAX_SOURCE_TITLE_WIDTH)
|
|
|
|
this._sourceTitle.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
|
|
|
},
|
|
|
|
|
|
|
|
set widthFraction(widthFraction) {
|
|
|
|
this._widthFraction = widthFraction;
|
|
|
|
this._sourceBox.queue_relayout();
|
|
|
|
},
|
|
|
|
|
|
|
|
get widthFraction() {
|
|
|
|
return this._widthFraction;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
function MessageTray() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageTray.prototype = {
|
|
|
|
_init: function() {
|
2010-03-22 00:39:49 +00:00
|
|
|
this.actor = new St.Group({ name: 'message-tray',
|
|
|
|
reactive: true,
|
|
|
|
track_hover: true });
|
2010-03-15 16:20:10 +00:00
|
|
|
this.actor.connect('notify::hover', Lang.bind(this, this._onTrayHoverChanged));
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-04-08 18:36:20 +00:00
|
|
|
this._notificationBin = new St.Bin();
|
2010-03-22 00:39:49 +00:00
|
|
|
this.actor.add_actor(this._notificationBin);
|
2010-01-28 17:04:26 +00:00
|
|
|
this._notificationBin.hide();
|
2010-01-13 20:05:20 +00:00
|
|
|
this._notificationQueue = [];
|
2010-02-01 20:23:49 +00:00
|
|
|
this._notification = null;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-03-26 19:23:53 +00:00
|
|
|
this._summaryBin = new St.Bin({ anchor_gravity: Clutter.Gravity.NORTH_EAST });
|
2010-03-22 00:39:49 +00:00
|
|
|
this.actor.add_actor(this._summaryBin);
|
2010-02-09 18:31:39 +00:00
|
|
|
this._summary = new St.BoxLayout({ name: 'summary-mode',
|
2010-03-15 16:20:10 +00:00
|
|
|
reactive: true,
|
|
|
|
track_hover: true });
|
|
|
|
this._summary.connect('notify::hover', Lang.bind(this, this._onSummaryHoverChanged));
|
|
|
|
this._summaryBin.child = this._summary;
|
2010-02-11 20:39:43 +00:00
|
|
|
this._summaryBin.opacity = 0;
|
2010-02-09 18:31:39 +00:00
|
|
|
|
2010-02-25 19:42:18 +00:00
|
|
|
this._summaryNotificationBin = new St.Bin({ name: 'summary-notification-bin',
|
2010-03-26 19:23:53 +00:00
|
|
|
anchor_gravity: Clutter.Gravity.NORTH_EAST,
|
2010-02-25 19:42:18 +00:00
|
|
|
reactive: true,
|
|
|
|
track_hover: true });
|
2010-03-22 00:39:49 +00:00
|
|
|
this.actor.add_actor(this._summaryNotificationBin);
|
2010-02-25 19:42:18 +00:00
|
|
|
this._summaryNotificationBin.lower_bottom();
|
|
|
|
this._summaryNotificationBin.hide();
|
|
|
|
this._summaryNotification = null;
|
2010-06-23 19:20:39 +00:00
|
|
|
this._clickedSummaryItem = null;
|
2010-02-25 19:42:18 +00:00
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
this._trayState = State.HIDDEN;
|
|
|
|
this._trayLeftTimeoutId = 0;
|
|
|
|
this._pointerInTray = false;
|
|
|
|
this._summaryState = State.HIDDEN;
|
|
|
|
this._summaryTimeoutId = 0;
|
|
|
|
this._pointerInSummary = false;
|
|
|
|
this._notificationState = State.HIDDEN;
|
|
|
|
this._notificationTimeoutId = 0;
|
2010-02-25 19:42:18 +00:00
|
|
|
this._summaryNotificationState = State.HIDDEN;
|
|
|
|
this._summaryNotificationTimeoutId = 0;
|
2010-02-19 00:42:09 +00:00
|
|
|
this._overviewVisible = false;
|
2010-02-22 22:19:32 +00:00
|
|
|
this._notificationRemoved = false;
|
2010-02-09 18:31:39 +00:00
|
|
|
|
2010-02-12 17:33:00 +00:00
|
|
|
Main.chrome.addActor(this.actor, { affectsStruts: false,
|
|
|
|
visibleInOverview: true });
|
2010-04-28 15:03:58 +00:00
|
|
|
Main.chrome.trackActor(this._notificationBin);
|
|
|
|
Main.chrome.trackActor(this._summaryNotificationBin);
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-06-03 15:14:58 +00:00
|
|
|
global.gdk_screen.connect('monitors-changed', Lang.bind(this, this._setSizePosition));
|
|
|
|
|
2010-01-28 17:04:26 +00:00
|
|
|
this._setSizePosition();
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-02-19 00:42:09 +00:00
|
|
|
Main.overview.connect('showing', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this._overviewVisible = true;
|
|
|
|
this._updateState();
|
|
|
|
}));
|
|
|
|
Main.overview.connect('hiding', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this._overviewVisible = false;
|
|
|
|
this._updateState();
|
|
|
|
}));
|
2010-02-12 17:33:00 +00:00
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
this._summaryItems = {};
|
|
|
|
this._longestSummaryItem = null;
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2010-01-25 18:28:35 +00:00
|
|
|
_setSizePosition: function() {
|
|
|
|
let primary = global.get_primary_monitor();
|
2010-02-02 13:02:32 +00:00
|
|
|
this.actor.x = primary.x;
|
|
|
|
this.actor.y = primary.y + primary.height - 1;
|
2010-01-25 18:28:35 +00:00
|
|
|
this.actor.width = primary.width;
|
2010-04-08 18:36:20 +00:00
|
|
|
this._notificationBin.x = 0;
|
|
|
|
this._notificationBin.width = primary.width;
|
2010-01-28 17:04:26 +00:00
|
|
|
|
2010-03-26 19:23:53 +00:00
|
|
|
// These work because of their anchor_gravity
|
|
|
|
this._summaryBin.x = primary.width;
|
|
|
|
this._summaryNotificationBin.x = primary.width;
|
2010-01-25 18:28:35 +00:00
|
|
|
},
|
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
contains: function(source) {
|
2010-06-23 19:20:39 +00:00
|
|
|
return this._summaryItems.hasOwnProperty(source.id);
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
add: function(source) {
|
|
|
|
if (this.contains(source)) {
|
|
|
|
log('Trying to re-add source ' + source.id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
let minTitleWidth = (this._longestSummaryItem ? this._longestSummaryItem.getTitleNaturalWidth() : 0);
|
|
|
|
let summaryItem = new SummaryItem(source, minTitleWidth);
|
|
|
|
|
|
|
|
this._summary.insert_actor(summaryItem.actor, 0);
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
this._summaryNeedsToBeShown = true;
|
2010-06-23 19:20:39 +00:00
|
|
|
|
|
|
|
let newItemTitleWidth = summaryItem.getTitleNaturalWidth();
|
|
|
|
if (newItemTitleWidth > minTitleWidth) {
|
|
|
|
for (sourceId in this._summaryItems) {
|
|
|
|
this._summaryItems[sourceId].setMinTitleWidth(newItemTitleWidth);
|
|
|
|
}
|
|
|
|
summaryItem.setMinTitleWidth(newItemTitleWidth);
|
|
|
|
this._longestSummaryItem = summaryItem;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._summaryItems[source.id] = summaryItem;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-01-28 18:12:03 +00:00
|
|
|
source.connect('notify', Lang.bind(this, this._onNotify));
|
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
summaryItem.actor.connect('notify::hover', Lang.bind(this,
|
2010-02-25 19:42:18 +00:00
|
|
|
function () {
|
2010-06-23 19:20:39 +00:00
|
|
|
this._onSummaryItemHoverChanged(summaryItem);
|
2010-02-25 19:42:18 +00:00
|
|
|
}));
|
2010-06-23 19:20:39 +00:00
|
|
|
|
|
|
|
summaryItem.actor.connect('clicked', Lang.bind(this,
|
2010-01-13 20:05:20 +00:00
|
|
|
function () {
|
2010-06-23 19:20:39 +00:00
|
|
|
this._onSummaryItemClicked(summaryItem);
|
2010-01-13 20:05:20 +00:00
|
|
|
}));
|
|
|
|
|
|
|
|
source.connect('destroy', Lang.bind(this,
|
|
|
|
function () {
|
2010-02-22 22:19:32 +00:00
|
|
|
this.removeSource(source);
|
2010-01-13 20:05:20 +00:00
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2010-02-22 22:19:32 +00:00
|
|
|
removeSource: function(source) {
|
2010-01-13 20:05:20 +00:00
|
|
|
if (!this.contains(source))
|
|
|
|
return;
|
|
|
|
|
2010-01-28 18:39:00 +00:00
|
|
|
// remove all notifications with this source from the queue
|
|
|
|
let newNotificationQueue = [];
|
|
|
|
for (let i = 0; i < this._notificationQueue.length; i++) {
|
|
|
|
if (this._notificationQueue[i].source != source)
|
|
|
|
newNotificationQueue.push(this._notificationQueue[i]);
|
|
|
|
}
|
|
|
|
this._notificationQueue = newNotificationQueue;
|
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
this._summary.remove_actor(this._summaryItems[source.id].actor);
|
2010-02-22 22:19:32 +00:00
|
|
|
if (this._summary.get_children().length > 0)
|
|
|
|
this._summaryNeedsToBeShown = true;
|
|
|
|
else
|
|
|
|
this._summaryNeedsToBeShown = false;
|
2010-06-23 19:20:39 +00:00
|
|
|
|
|
|
|
delete this._summaryItems[source.id];
|
|
|
|
if (this._longestSummaryItem.source == source) {
|
|
|
|
|
|
|
|
let maxTitleWidth = 0;
|
|
|
|
this._longestSummaryItem = null;
|
|
|
|
for (sourceId in this._summaryItems) {
|
|
|
|
let summaryItem = this._summaryItems[sourceId];
|
|
|
|
if (summaryItem.getTitleNaturalWidth() > maxTitleWidth) {
|
|
|
|
maxTitleWidth = summaryItem.getTitleNaturalWidth();
|
|
|
|
this._longestSummaryItem = summaryItem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (sourceId in this._summaryItems) {
|
|
|
|
this._summaryItems[sourceId].setMinTitleWidth(maxTitleWidth);
|
|
|
|
}
|
|
|
|
}
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
|
2010-02-25 19:42:18 +00:00
|
|
|
let needUpdate = false;
|
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
if (this._notification && this._notification.source == source) {
|
|
|
|
if (this._notificationTimeoutId) {
|
|
|
|
Mainloop.source_remove(this._notificationTimeoutId);
|
|
|
|
this._notificationTimeoutId = 0;
|
|
|
|
}
|
2010-02-22 22:19:32 +00:00
|
|
|
this._notificationRemoved = true;
|
2010-02-25 19:42:18 +00:00
|
|
|
needUpdate = true;
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
}
|
2010-06-23 19:20:39 +00:00
|
|
|
if (this._clickedSummaryItem && this._clickedSummaryItem.source == source) {
|
|
|
|
this._clickedSummaryItem = null;
|
2010-02-25 19:42:18 +00:00
|
|
|
needUpdate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needUpdate);
|
|
|
|
this._updateState();
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2010-02-21 05:25:23 +00:00
|
|
|
removeSourceByApp: function(app) {
|
2010-06-23 19:20:39 +00:00
|
|
|
for (let sourceId in this._summaryItems)
|
|
|
|
if (this._summaryItems[sourceId].source.app == app)
|
|
|
|
this.removeSource(this._summaryItems[sourceId].source);
|
2010-02-21 05:25:23 +00:00
|
|
|
},
|
|
|
|
|
2010-02-22 22:19:32 +00:00
|
|
|
removeNotification: function(notification) {
|
|
|
|
if (this._notification == notification && (this._notificationState == State.SHOWN || this._notificationState == State.SHOWING)) {
|
|
|
|
if (this._notificationTimeoutId) {
|
|
|
|
Mainloop.source_remove(this._notificationTimeoutId);
|
|
|
|
this._notificationTimeoutId = 0;
|
|
|
|
}
|
|
|
|
this._notificationRemoved = true;
|
|
|
|
this._updateState();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let index = this._notificationQueue.indexOf(notification);
|
|
|
|
if (index != -1)
|
|
|
|
this._notificationQueue.splice(index, 1);
|
|
|
|
},
|
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
getSource: function(id) {
|
2010-06-23 19:20:39 +00:00
|
|
|
if (this._summaryItems[id])
|
|
|
|
return this._summaryItems[id].source;
|
|
|
|
return null;
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2010-02-22 22:19:32 +00:00
|
|
|
_getNotification: function(id, source) {
|
|
|
|
if (this._notification && this._notification.id == id)
|
|
|
|
return this._notification;
|
|
|
|
|
|
|
|
for (let i = 0; i < this._notificationQueue.length; i++) {
|
|
|
|
if (this._notificationQueue[i].id == id && this._notificationQueue[i].source == source)
|
|
|
|
return this._notificationQueue[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
lock: function() {
|
|
|
|
this._locked = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
unlock: function() {
|
|
|
|
this._locked = false;
|
|
|
|
|
|
|
|
this.actor.sync_hover();
|
|
|
|
this._summary.sync_hover();
|
|
|
|
|
|
|
|
this._updateState();
|
|
|
|
},
|
|
|
|
|
2010-02-01 20:23:49 +00:00
|
|
|
_onNotify: function(source, notification) {
|
2010-04-07 19:32:47 +00:00
|
|
|
if (notification == this._summaryNotification)
|
|
|
|
return;
|
|
|
|
|
2010-02-23 15:50:35 +00:00
|
|
|
if (this._getNotification(notification.id, source) == null) {
|
2010-02-22 22:19:32 +00:00
|
|
|
notification.connect('destroy',
|
|
|
|
Lang.bind(this, this.removeNotification));
|
2010-04-28 19:34:27 +00:00
|
|
|
|
|
|
|
if (notification.urgent)
|
|
|
|
this._notificationQueue.unshift(notification);
|
|
|
|
else
|
|
|
|
this._notificationQueue.push(notification);
|
2010-02-22 22:19:32 +00:00
|
|
|
}
|
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
this._updateState();
|
2010-01-28 18:12:03 +00:00
|
|
|
},
|
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
_onSummaryItemHoverChanged: function(summaryItem) {
|
|
|
|
if (summaryItem.actor.hover)
|
|
|
|
summaryItem.expand();
|
|
|
|
else
|
|
|
|
summaryItem.collapse();
|
2010-02-25 19:42:18 +00:00
|
|
|
},
|
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
_onSummaryItemClicked: function(summaryItem) {
|
|
|
|
if (!this._clickedSummaryItem || this._clickedSummaryItem != summaryItem)
|
2010-06-24 12:14:27 +00:00
|
|
|
this._clickedSummaryItem = summaryItem;
|
2010-06-23 19:20:39 +00:00
|
|
|
else
|
|
|
|
this._clickedSummaryItem = null;
|
2010-02-25 19:42:18 +00:00
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
this._updateState();
|
2010-02-25 19:42:18 +00:00
|
|
|
},
|
|
|
|
|
2010-03-15 16:20:10 +00:00
|
|
|
_onSummaryHoverChanged: function() {
|
|
|
|
this._pointerInSummary = this._summary.hover;
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
this._updateState();
|
|
|
|
},
|
2010-01-28 18:39:00 +00:00
|
|
|
|
2010-03-15 16:20:10 +00:00
|
|
|
_onTrayHoverChanged: function() {
|
|
|
|
if (this.actor.hover) {
|
|
|
|
if (this._trayLeftTimeoutId) {
|
|
|
|
Mainloop.source_remove(this._trayLeftTimeoutId);
|
|
|
|
this._trayLeftTimeoutId = 0;
|
|
|
|
return;
|
|
|
|
}
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-03-15 16:20:10 +00:00
|
|
|
this._pointerInTray = true;
|
|
|
|
this._updateState();
|
|
|
|
} else {
|
|
|
|
// We wait just a little before hiding the message tray in case the
|
|
|
|
// user quickly moves the mouse back into it.
|
2010-02-25 19:42:18 +00:00
|
|
|
let timeout = HIDE_TIMEOUT * 1000;
|
2010-03-15 16:20:10 +00:00
|
|
|
this._trayLeftTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(this, this._onTrayLeftTimeout));
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
}
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
_onTrayLeftTimeout: function() {
|
|
|
|
this._trayLeftTimeoutId = 0;
|
|
|
|
this._pointerInTray = false;
|
|
|
|
this._pointerInSummary = false;
|
|
|
|
this._updateState();
|
|
|
|
return false;
|
|
|
|
},
|
2010-01-28 18:39:00 +00:00
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
// All of the logic for what happens when occurs here; the various
|
|
|
|
// event handlers merely update variables such as
|
2010-05-13 19:46:04 +00:00
|
|
|
// 'this._pointerInTray', 'this._summaryState', etc, and
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
// _updateState() figures out what (if anything) needs to be done
|
|
|
|
// at the present time.
|
|
|
|
_updateState: function() {
|
|
|
|
// Notifications
|
|
|
|
let notificationsPending = this._notificationQueue.length > 0;
|
2010-02-22 22:19:32 +00:00
|
|
|
let notificationPinned = this._pointerInTray && !this._pointerInSummary && !this._notificationRemoved;
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
let notificationExpanded = this._notificationBin.y < 0;
|
2010-02-22 19:23:36 +00:00
|
|
|
let notificationExpired = (this._notificationTimeoutId == 0 && !this._pointerInTray && !this._locked) || this._notificationRemoved;
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
|
|
|
|
if (this._notificationState == State.HIDDEN) {
|
|
|
|
if (notificationsPending)
|
2010-01-28 18:39:00 +00:00
|
|
|
this._showNotification();
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
} else if (this._notificationState == State.SHOWN) {
|
|
|
|
if (notificationExpired)
|
2010-01-28 18:39:00 +00:00
|
|
|
this._hideNotification();
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
else if (notificationPinned && !notificationExpanded)
|
|
|
|
this._expandNotification();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Summary
|
2010-02-19 00:42:09 +00:00
|
|
|
let summarySummoned = this._pointerInSummary || this._overviewVisible;
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
let summaryPinned = this._summaryTimeoutId != 0 || this._pointerInTray || summarySummoned;
|
2010-02-22 22:19:32 +00:00
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
let notificationsVisible = (this._notificationState == State.SHOWING ||
|
|
|
|
this._notificationState == State.SHOWN);
|
|
|
|
let notificationsDone = !notificationsVisible && !notificationsPending;
|
|
|
|
|
|
|
|
if (this._summaryState == State.HIDDEN) {
|
|
|
|
if (notificationsDone && this._summaryNeedsToBeShown)
|
|
|
|
this._showSummary(true);
|
|
|
|
else if (!notificationsVisible && summarySummoned)
|
|
|
|
this._showSummary(false);
|
|
|
|
} else if (this._summaryState == State.SHOWN) {
|
|
|
|
if (!summaryPinned)
|
2010-01-28 18:39:00 +00:00
|
|
|
this._hideSummary();
|
|
|
|
}
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-02-25 19:42:18 +00:00
|
|
|
// Summary notification
|
2010-06-23 19:20:39 +00:00
|
|
|
let haveSummaryNotification = this._clickedSummaryItem != null;
|
2010-02-25 19:42:18 +00:00
|
|
|
let summaryNotificationIsMainNotification = (haveSummaryNotification &&
|
2010-06-23 19:20:39 +00:00
|
|
|
this._clickedSummaryItem.source.notification == this._notification);
|
2010-02-25 19:42:18 +00:00
|
|
|
let canShowSummaryNotification = this._summaryState == State.SHOWN;
|
|
|
|
let wrongSummaryNotification = (haveSummaryNotification &&
|
2010-06-23 19:20:39 +00:00
|
|
|
this._summaryNotification != this._clickedSummaryItem.source.notification);
|
2010-02-25 19:42:18 +00:00
|
|
|
|
|
|
|
if (this._summaryNotificationState == State.HIDDEN) {
|
|
|
|
if (haveSummaryNotification && !summaryNotificationIsMainNotification && canShowSummaryNotification)
|
|
|
|
this._showSummaryNotification();
|
|
|
|
} else if (this._summaryNotificationState == State.SHOWN) {
|
|
|
|
if (!haveSummaryNotification || !canShowSummaryNotification || wrongSummaryNotification)
|
|
|
|
this._hideSummaryNotification();
|
|
|
|
}
|
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
// Tray itself
|
|
|
|
let trayIsVisible = (this._trayState == State.SHOWING ||
|
|
|
|
this._trayState == State.SHOWN);
|
|
|
|
let trayShouldBeVisible = (!notificationsDone ||
|
|
|
|
this._summaryState == State.SHOWING ||
|
|
|
|
this._summaryState == State.SHOWN);
|
|
|
|
if (!trayIsVisible && trayShouldBeVisible)
|
|
|
|
this._showTray();
|
|
|
|
else if (trayIsVisible && !trayShouldBeVisible)
|
|
|
|
this._hideTray();
|
|
|
|
},
|
|
|
|
|
|
|
|
_tween: function(actor, statevar, value, params) {
|
|
|
|
let onComplete = params.onComplete;
|
|
|
|
let onCompleteScope = params.onCompleteScope;
|
|
|
|
let onCompleteParams = params.onCompleteParams;
|
|
|
|
|
|
|
|
params.onComplete = this._tweenComplete;
|
|
|
|
params.onCompleteScope = this;
|
|
|
|
params.onCompleteParams = [statevar, value, onComplete, onCompleteScope, onCompleteParams];
|
|
|
|
|
|
|
|
Tweener.addTween(actor, params);
|
|
|
|
|
|
|
|
let valuing = (value == State.SHOWN) ? State.SHOWING : State.HIDING;
|
|
|
|
this[statevar] = valuing;
|
|
|
|
},
|
|
|
|
|
|
|
|
_tweenComplete: function(statevar, value, onComplete, onCompleteScope, onCompleteParams) {
|
|
|
|
this[statevar] = value;
|
|
|
|
if (onComplete)
|
|
|
|
onComplete.apply(onCompleteScope, onCompleteParams);
|
|
|
|
this._updateState();
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2010-01-28 18:39:00 +00:00
|
|
|
_showTray: function() {
|
2010-01-13 20:05:20 +00:00
|
|
|
let primary = global.get_primary_monitor();
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this.actor, '_trayState', State.SHOWN,
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
{ y: primary.y + primary.height - this.actor.height,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad'
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
});
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2010-01-28 18:39:00 +00:00
|
|
|
_hideTray: function() {
|
2010-01-13 20:05:20 +00:00
|
|
|
let primary = global.get_primary_monitor();
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this.actor, '_trayState', State.HIDDEN,
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
{ y: primary.y + primary.height - 1,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad'
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
});
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2010-01-28 18:39:00 +00:00
|
|
|
_showNotification: function() {
|
2010-02-01 20:23:49 +00:00
|
|
|
this._notification = this._notificationQueue.shift();
|
|
|
|
this._notificationBin.child = this._notification.actor;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-01-28 17:04:26 +00:00
|
|
|
this._notificationBin.opacity = 0;
|
|
|
|
this._notificationBin.y = this.actor.height;
|
|
|
|
this._notificationBin.show();
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this._notificationBin, '_notificationState', State.SHOWN,
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
{ y: 0,
|
|
|
|
opacity: 255,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad',
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
onComplete: this._showNotificationCompleted,
|
|
|
|
onCompleteScope: this
|
|
|
|
});
|
2010-04-28 19:34:27 +00:00
|
|
|
|
|
|
|
if (this._notification.urgent) {
|
|
|
|
// This will overwrite the y tween, but leave the opacity
|
|
|
|
// tween, and so the onComplete will remain as well.
|
|
|
|
this._expandNotification();
|
|
|
|
}
|
2010-04-29 19:06:51 +00:00
|
|
|
|
|
|
|
let [x, y, mods] = global.get_pointer();
|
|
|
|
this._lastSeenMouseY = y;
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_showNotificationCompleted: function() {
|
|
|
|
this._notificationTimeoutId =
|
|
|
|
Mainloop.timeout_add(NOTIFICATION_TIMEOUT * 1000,
|
|
|
|
Lang.bind(this, this._notificationTimeout));
|
|
|
|
},
|
|
|
|
|
|
|
|
_notificationTimeout: function() {
|
2010-04-29 19:06:51 +00:00
|
|
|
let [x, y, mods] = global.get_pointer();
|
|
|
|
if (y > this._lastSeenMouseY + 10 && y < this.actor.y) {
|
|
|
|
// The mouse is moving towards the notification, so don't
|
|
|
|
// hide it yet. (We just create a new timeout (and destroy
|
|
|
|
// the old one) each time because the bookkeeping is
|
|
|
|
// simpler.)
|
|
|
|
this._lastSeenMouseY = y;
|
|
|
|
this._notificationTimeoutId =
|
|
|
|
Mainloop.timeout_add(1000,
|
|
|
|
Lang.bind(this, this._notificationTimeout));
|
|
|
|
} else {
|
|
|
|
this._notificationTimeoutId = 0;
|
|
|
|
this._updateState();
|
|
|
|
}
|
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
return false;
|
2010-01-28 18:39:00 +00:00
|
|
|
},
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-01-28 18:39:00 +00:00
|
|
|
_hideNotification: function() {
|
2010-02-01 20:23:49 +00:00
|
|
|
this._notification.popIn();
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
if (this._reExpandNotificationId) {
|
|
|
|
this._notificationBin.disconnect(this._reExpandNotificationId);
|
|
|
|
this._reExpandNotificationId = 0;
|
|
|
|
}
|
|
|
|
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this._notificationBin, '_notificationState', State.HIDDEN,
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
{ y: this.actor.height,
|
|
|
|
opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad',
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
onComplete: this._hideNotificationCompleted,
|
|
|
|
onCompleteScope: this
|
|
|
|
});
|
2010-01-28 18:39:00 +00:00
|
|
|
},
|
2010-01-13 20:05:20 +00:00
|
|
|
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
_hideNotificationCompleted: function() {
|
2010-02-25 21:19:11 +00:00
|
|
|
this._notificationRemoved = false;
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
this._notificationBin.hide();
|
|
|
|
this._notificationBin.child = null;
|
|
|
|
this._notification = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
_expandNotification: function() {
|
|
|
|
if (this._notification && this._notification.popOut()) {
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this._notificationBin, '_notificationState', State.SHOWN,
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
{ y: this.actor.height - this._notificationBin.height,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad'
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
});
|
2010-02-22 19:23:36 +00:00
|
|
|
|
|
|
|
if (!this._reExpandNotificationId)
|
|
|
|
this._reExpandNotificationId = this._notificationBin.connect('notify::height', Lang.bind(this, this._expandNotification));
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_showSummary: function(withTimeout) {
|
2010-02-02 13:02:32 +00:00
|
|
|
let primary = global.get_primary_monitor();
|
2010-01-28 18:39:00 +00:00
|
|
|
this._summaryBin.opacity = 0;
|
|
|
|
this._summaryBin.y = this.actor.height;
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this._summaryBin, '_summaryState', State.SHOWN,
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
{ y: 0,
|
|
|
|
opacity: 255,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad',
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
onComplete: this._showSummaryCompleted,
|
|
|
|
onCompleteScope: this,
|
|
|
|
onCompleteParams: [withTimeout]
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_showSummaryCompleted: function(withTimeout) {
|
|
|
|
this._summaryNeedsToBeShown = false;
|
|
|
|
|
|
|
|
if (withTimeout) {
|
|
|
|
this._summaryTimeoutId =
|
|
|
|
Mainloop.timeout_add(SUMMARY_TIMEOUT * 1000,
|
|
|
|
Lang.bind(this, this._summaryTimeout));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_summaryTimeout: function() {
|
|
|
|
this._summaryTimeoutId = 0;
|
|
|
|
this._updateState();
|
|
|
|
return false;
|
2010-01-28 18:39:00 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_hideSummary: function() {
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this._summaryBin, '_summaryState', State.HIDDEN,
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
{ opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad'
|
[MessageTray] reimplement the state machine
Previously, every time _updateState was called, it would make some
change, and so it was necessary to very carefully set up all the calls
to it, to ensure it was always called at exactly the right time. Now,
instead, we keep a bunch of state variables like "_notificationState"
and "_pointerInSummary", and potentially multiple timeouts, and
_updateState looks at all of them and figure out what, if anything,
needs to be changed.
By making the rules about what causes changes more explicit, it will
be easier to change those rules in the future as we add new
functionality.
Also, update the rules a bit, so that notifications can appear while
the summary is visible, and the summary only shows after a
notification if the summary has changed.
https://bugzilla.gnome.org/show_bug.cgi?id=609765
2010-02-11 20:31:12 +00:00
|
|
|
});
|
|
|
|
this._summaryNeedsToBeShown = false;
|
2010-02-25 19:42:18 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_showSummaryNotification: function() {
|
2010-06-23 19:20:39 +00:00
|
|
|
this._summaryNotification = this._clickedSummaryItem.source.notification;
|
2010-02-25 19:42:18 +00:00
|
|
|
|
|
|
|
let index = this._notificationQueue.indexOf(this._summaryNotification);
|
|
|
|
if (index != -1)
|
|
|
|
this._notificationQueue.splice(index, 1);
|
|
|
|
|
|
|
|
this._summaryNotificationBin.child = this._summaryNotification.actor;
|
|
|
|
this._summaryNotification.popOut();
|
|
|
|
|
|
|
|
this._summaryNotificationBin.opacity = 0;
|
|
|
|
this._summaryNotificationBin.y = this.actor.height;
|
|
|
|
this._summaryNotificationBin.show();
|
|
|
|
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this._summaryNotificationBin, '_summaryNotificationState', State.SHOWN,
|
2010-02-25 19:42:18 +00:00
|
|
|
{ y: this.actor.height - this._summaryNotificationBin.height,
|
|
|
|
opacity: 255,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad'
|
2010-02-25 19:42:18 +00:00
|
|
|
});
|
2010-04-07 16:53:52 +00:00
|
|
|
|
|
|
|
if (!this._reExpandSummaryNotificationId)
|
|
|
|
this._reExpandSummaryNotificationId = this._summaryNotificationBin.connect('notify::height', Lang.bind(this, this._reExpandSummaryNotification));
|
|
|
|
},
|
|
|
|
|
|
|
|
_reExpandSummaryNotification: function() {
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this._summaryNotificationBin, '_summaryNotificationState', State.SHOWN,
|
2010-04-07 16:53:52 +00:00
|
|
|
{ y: this.actor.height - this._summaryNotificationBin.height,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad'
|
2010-04-07 16:53:52 +00:00
|
|
|
});
|
2010-02-25 19:42:18 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_hideSummaryNotification: function() {
|
2010-06-23 19:20:39 +00:00
|
|
|
// Unset this._clickedSummaryItem if we are no longer showing the summary
|
|
|
|
if (this._summaryState != State.SHOWN)
|
|
|
|
this._clickedSummaryItem = null;
|
2010-02-25 19:42:18 +00:00
|
|
|
this._summaryNotification.popIn();
|
|
|
|
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this._summaryNotificationBin, '_summaryNotificationState', State.HIDDEN,
|
2010-02-25 19:42:18 +00:00
|
|
|
{ y: this.actor.height,
|
|
|
|
opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
2010-05-13 19:46:04 +00:00
|
|
|
transition: 'easeOutQuad',
|
2010-02-25 19:42:18 +00:00
|
|
|
onComplete: this._hideSummaryNotificationCompleted,
|
|
|
|
onCompleteScope: this
|
|
|
|
});
|
2010-04-07 16:53:52 +00:00
|
|
|
|
|
|
|
if (this._reExpandSummaryNotificationId) {
|
|
|
|
this._summaryNotificationBin.disconnect(this._reExpandSummaryNotificationId);
|
|
|
|
this._reExpandSummaryNotificationId = 0;
|
|
|
|
}
|
2010-02-25 19:42:18 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_hideSummaryNotificationCompleted: function() {
|
|
|
|
this._summaryNotificationBin.hide();
|
|
|
|
this._summaryNotificationBin.child = null;
|
|
|
|
this._summaryNotification = null;
|
2010-01-28 18:39:00 +00:00
|
|
|
}
|
2010-01-13 20:05:20 +00:00
|
|
|
};
|