2010-01-13 15:05:20 -05:00
|
|
|
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
|
|
|
|
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
|
|
const Lang = imports.lang;
|
|
|
|
const Mainloop = imports.mainloop;
|
2010-02-01 12:10:38 -05:00
|
|
|
const Pango = imports.gi.Pango;
|
2010-01-28 12:04:26 -05:00
|
|
|
const Shell = imports.gi.Shell;
|
2010-01-13 15:05:20 -05:00
|
|
|
const Signals = imports.signals;
|
2010-01-28 12:04:26 -05:00
|
|
|
const St = imports.gi.St;
|
2010-01-13 15:05:20 -05:00
|
|
|
const Tweener = imports.ui.tweener;
|
|
|
|
|
|
|
|
const Main = imports.ui.main;
|
|
|
|
|
|
|
|
const ANIMATION_TIME = 0.2;
|
|
|
|
const NOTIFICATION_TIMEOUT = 4;
|
2010-01-28 13:39:00 -05:00
|
|
|
const SUMMARY_TIMEOUT = 1;
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-02-25 14:42:18 -05:00
|
|
|
const HIDE_TIMEOUT = 0.2;
|
2010-01-13 15:05:20 -05:00
|
|
|
|
|
|
|
const ICON_SIZE = 24;
|
|
|
|
|
[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 15:31:12 -05:00
|
|
|
const State = {
|
|
|
|
HIDDEN: 0,
|
|
|
|
SHOWING: 1,
|
|
|
|
SHOWN: 2,
|
|
|
|
HIDING: 3
|
2010-01-28 13:39:00 -05:00
|
|
|
};
|
|
|
|
|
2010-02-01 15:23:49 -05:00
|
|
|
function _cleanMarkup(text) {
|
2010-02-19 22:28:47 -05:00
|
|
|
// Support &, ", ', < and >, escape all other
|
|
|
|
// occurrences of '&'.
|
|
|
|
let _text = text.replace(/&(?!amp;|quot;|apos;|lt;|gt;)/g, "&");
|
2010-02-01 15:23:49 -05:00
|
|
|
// Support <b>, <i>, and <u>, escape anything else
|
|
|
|
// so it displays as raw markup.
|
2010-02-19 22:28:47 -05:00
|
|
|
return _text.replace(/<(\/?[^biu]>|[^>\/][^>])/g, "<$1");
|
2010-02-01 15:23:49 -05:00
|
|
|
}
|
|
|
|
|
2010-02-01 12:10:38 -05:00
|
|
|
// Notification:
|
2010-02-22 17:19:32 -05:00
|
|
|
// @id: the notification's id
|
2010-02-01 12:10:38 -05:00
|
|
|
// @source: the notification's Source
|
|
|
|
// @title: the title
|
|
|
|
// @banner: the banner text
|
2010-02-12 15:15:03 -05:00
|
|
|
// @bannerBody: whether or not to promote the banner to the body on overflow
|
2010-02-01 12:10:38 -05:00
|
|
|
//
|
|
|
|
// Creates a notification. In banner mode, it will show
|
2010-02-01 15:23:49 -05:00
|
|
|
// @source's icon, @title (in bold) and @banner, all on a single line
|
2010-02-09 11:25:10 -05:00
|
|
|
// (with @banner ellipsized if necessary).
|
|
|
|
//
|
|
|
|
// Additional notification details can be added via addBody(),
|
|
|
|
// addAction(), and addActor(). If any of these are called, then the
|
|
|
|
// notification will expand to show the additional actors (while
|
|
|
|
// hiding the @banner) if the pointer is moved into it while it is
|
|
|
|
// visible.
|
|
|
|
//
|
2010-02-12 15:15:03 -05: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 17:19:32 -05:00
|
|
|
function Notification(id, source, title, banner, bannerBody) {
|
|
|
|
this._init(id, source, title, banner, bannerBody);
|
2010-01-13 15:05:20 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
Notification.prototype = {
|
2010-02-22 17:19:32 -05:00
|
|
|
_init: function(id, source, title, banner, bannerBody) {
|
|
|
|
this.id = id;
|
2010-01-28 13:39:00 -05:00
|
|
|
this.source = source;
|
2010-02-12 15:15:03 -05:00
|
|
|
this._bannerBody = bannerBody;
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-02-22 17:19:32 -05:00
|
|
|
source.connect('clicked', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this.emit('dismissed');
|
|
|
|
}));
|
|
|
|
|
2010-02-01 12:10:38 -05:00
|
|
|
this.actor = new St.Table({ name: 'notification' });
|
2010-02-09 11:25:10 -05:00
|
|
|
this.update(title, banner, true);
|
|
|
|
},
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-02-09 11:25:10 -05:00
|
|
|
// update:
|
|
|
|
// @title: the new title
|
|
|
|
// @banner: the new banner
|
|
|
|
// @clear: whether or not to clear out extra actors
|
|
|
|
//
|
|
|
|
// 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) {
|
|
|
|
let children = this.actor.get_children();
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
let meta = this.actor.get_child_meta(children[i]);
|
2010-02-22 14:06:13 -05:00
|
|
|
if (clear || meta.row == 0 || (this._bannerBody && meta.row == 1))
|
2010-02-09 11:25:10 -05:00
|
|
|
children[i].destroy();
|
|
|
|
}
|
|
|
|
if (clear) {
|
|
|
|
this.actions = {};
|
|
|
|
this._actionBox = null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let icon = this.source.createIcon(ICON_SIZE);
|
2010-02-15 12:27:28 -05:00
|
|
|
icon.reactive = true;
|
2010-02-01 15:23:49 -05:00
|
|
|
this.actor.add(icon, { row: 0,
|
|
|
|
col: 0,
|
|
|
|
x_expand: false,
|
|
|
|
y_expand: false,
|
|
|
|
y_fill: false });
|
2010-02-01 12:10:38 -05:00
|
|
|
|
2010-02-15 12:27:28 -05:00
|
|
|
icon.connect('button-release-event', Lang.bind(this,
|
|
|
|
function () {
|
|
|
|
this.source.clicked();
|
|
|
|
}));
|
|
|
|
|
2010-02-01 12:10:38 -05: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 11:20:51 -05:00
|
|
|
this._titleLabel = new St.Label();
|
2010-02-20 16:08:54 -05:00
|
|
|
title = title ? _cleanMarkup(title.replace(/\n/g, ' ')) : '';
|
2010-02-09 11:20:51 -05:00
|
|
|
this._titleLabel.clutter_text.set_markup('<b>' + title + '</b>');
|
|
|
|
this._bannerBox.add_actor(this._titleLabel);
|
2010-02-01 12:10:38 -05:00
|
|
|
|
2010-02-12 15:15:03 -05:00
|
|
|
if (this._bannerBody)
|
|
|
|
this._bannerBodyText = banner;
|
|
|
|
else
|
|
|
|
this._bannerBodyText = null;
|
2010-02-09 11:25:10 -05:00
|
|
|
|
2010-02-09 11:20:51 -05:00
|
|
|
this._bannerLabel = new St.Label();
|
2010-02-20 16:08:54 -05:00
|
|
|
banner = banner ? _cleanMarkup(banner.replace(/\n/g, ' ')) : '';
|
2010-02-09 11:20:51 -05:00
|
|
|
this._bannerLabel.clutter_text.set_markup(banner);
|
|
|
|
this._bannerBox.add_actor(this._bannerLabel);
|
2010-02-09 11:25:10 -05:00
|
|
|
},
|
2010-02-01 12:10:38 -05:00
|
|
|
|
2010-02-09 11:25:10 -05:00
|
|
|
// addActor:
|
|
|
|
// @actor: actor to add to the notification
|
|
|
|
// @props: (optional) child properties
|
|
|
|
//
|
|
|
|
// Adds @actor to the notification's St.Table, using @props.
|
|
|
|
//
|
|
|
|
// If @props does not specify a %row, then @actor will be added
|
|
|
|
// to the bottom of the notification (unless there are action
|
|
|
|
// buttons present, in which case it will be added above them).
|
|
|
|
//
|
|
|
|
// If @props does not specify a %col, it will default to column 1.
|
|
|
|
// (Normally only the icon is in column 0.)
|
|
|
|
//
|
|
|
|
// If @props specifies an already-occupied cell, then the existing
|
|
|
|
// contents of the table will be shifted down to make room for it.
|
|
|
|
addActor: function(actor, props) {
|
|
|
|
if (!props)
|
|
|
|
props = {};
|
|
|
|
|
|
|
|
if (!('col' in props))
|
|
|
|
props.col = 1;
|
|
|
|
|
|
|
|
if ('row' in props) {
|
|
|
|
let children = this.actor.get_children();
|
|
|
|
let i, meta, collision = false;
|
|
|
|
|
|
|
|
for (i = 0; i < children.length; i++) {
|
|
|
|
meta = this.actor.get_child_meta(children[i]);
|
|
|
|
if (meta.row == props.row && meta.col == props.col) {
|
|
|
|
collision = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (collision) {
|
|
|
|
for (i = 0; i < children.length; i++) {
|
|
|
|
meta = this.actor.get_child_meta(children[i]);
|
|
|
|
if (meta.row >= props.row)
|
|
|
|
meta.row++;
|
|
|
|
}
|
|
|
|
}
|
2010-02-01 12:10:38 -05:00
|
|
|
} else {
|
2010-02-09 11:25:10 -05:00
|
|
|
if (this._actionBox) {
|
|
|
|
props.row = this.actor.row_count - 1;
|
|
|
|
this.actor.get_child_meta(this._actionBox).row++;
|
|
|
|
} else {
|
|
|
|
props.row = this.actor.row_count;
|
|
|
|
}
|
2010-02-01 12:10:38 -05:00
|
|
|
}
|
2010-02-01 15:23:49 -05:00
|
|
|
|
2010-02-09 11:25:10 -05:00
|
|
|
this.actor.add(actor, props);
|
2010-02-01 15:41:22 -05:00
|
|
|
},
|
|
|
|
|
2010-02-09 11:25:10 -05:00
|
|
|
// addBody:
|
|
|
|
// @text: the text
|
|
|
|
// @props: (optional) properties for addActor()
|
|
|
|
//
|
|
|
|
// Adds a multi-line label containing @text to the notification.
|
|
|
|
addBody: function(text, props) {
|
|
|
|
let body = new St.Label();
|
|
|
|
body.clutter_text.line_wrap = true;
|
2010-02-12 16:10:24 -05:00
|
|
|
body.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
|
2010-02-09 11:25:10 -05:00
|
|
|
body.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
|
|
|
|
text = text ? _cleanMarkup(text) : '';
|
|
|
|
body.clutter_text.set_markup(text);
|
|
|
|
|
|
|
|
this.addActor(body, props);
|
|
|
|
},
|
|
|
|
|
2010-02-12 15:15:03 -05:00
|
|
|
_addBannerBody: function() {
|
|
|
|
this.addBody(this._bannerBodyText, { row: 1 });
|
|
|
|
this._bannerBodyText = null;
|
|
|
|
},
|
|
|
|
|
2010-02-09 11:25:10 -05:00
|
|
|
// addAction:
|
|
|
|
// @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-01 15:41:22 -05:00
|
|
|
addAction: function(id, label) {
|
2010-02-09 11:20:51 -05:00
|
|
|
if (!this._actionBox) {
|
2010-02-12 15:15:03 -05:00
|
|
|
if (this._bannerBodyText)
|
|
|
|
this._addBannerBody();
|
|
|
|
|
2010-02-09 11:25:10 -05:00
|
|
|
let box = new St.BoxLayout({ name: 'notification-actions' });
|
|
|
|
this.addActor(box, { x_expand: false,
|
|
|
|
x_fill: false,
|
2010-02-15 12:08:45 -05:00
|
|
|
x_align: St.Align.END });
|
2010-02-09 11:25:10 -05:00
|
|
|
this._actionBox = box;
|
2010-02-01 15:41:22 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
let button = new St.Button({ style_class: 'notification-button',
|
|
|
|
label: label });
|
2010-02-09 11:20:51 -05:00
|
|
|
this._actionBox.add(button);
|
2010-02-01 15:41:22 -05:00
|
|
|
button.connect('clicked', Lang.bind(this, function() { this.emit('action-invoked', id); }));
|
2010-02-01 12:10:38 -05:00
|
|
|
},
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-02-01 12:10:38 -05:00
|
|
|
_bannerBoxGetPreferredWidth: function(actor, forHeight, alloc) {
|
2010-02-09 11:20:51 -05:00
|
|
|
let [titleMin, titleNat] = this._titleLabel.get_preferred_width(forHeight);
|
|
|
|
let [bannerMin, bannerNat] = this._bannerLabel.get_preferred_width(forHeight);
|
2010-02-01 12:10:38 -05:00
|
|
|
let [has_spacing, spacing] = this.actor.get_theme_node().get_length('spacing-columns', false);
|
2010-01-28 13:39:00 -05:00
|
|
|
|
2010-02-01 12:10:38 -05:00
|
|
|
alloc.min_size = titleMin;
|
|
|
|
alloc.natural_size = titleNat + (has_spacing ? spacing : 0) + bannerNat;
|
|
|
|
},
|
|
|
|
|
|
|
|
_bannerBoxGetPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
[alloc.min_size, alloc.natural_size] =
|
2010-02-09 11:20:51 -05:00
|
|
|
this._titleLabel.get_preferred_height(forWidth);
|
2010-02-01 12:10:38 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
_bannerBoxAllocate: function(actor, box, flags) {
|
2010-02-09 11:20:51 -05: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 12:10:38 -05:00
|
|
|
let [has_spacing, spacing] = this.actor.get_theme_node().get_length('spacing-columns', false);
|
|
|
|
if (!has_spacing)
|
|
|
|
spacing = 0;
|
|
|
|
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 11:20:51 -05:00
|
|
|
this._titleLabel.allocate(titleBox, flags);
|
2010-02-01 12:10:38 -05:00
|
|
|
|
2010-02-09 11:25:10 -05:00
|
|
|
let overflow = false;
|
2010-02-03 15:15:12 -05:00
|
|
|
if (titleBox.x2 + spacing > availWidth) {
|
2010-02-09 11:20:51 -05:00
|
|
|
this._bannerLabel.hide();
|
2010-02-09 11:25:10 -05:00
|
|
|
overflow = true;
|
2010-02-03 15:15:12 -05:00
|
|
|
} else {
|
|
|
|
let bannerBox = new Clutter.ActorBox();
|
|
|
|
bannerBox.x1 = titleBox.x2 + spacing;
|
|
|
|
bannerBox.y1 = 0;
|
|
|
|
bannerBox.x2 = Math.min(bannerBox.x1 + bannerNatW, availWidth);
|
|
|
|
bannerBox.y2 = titleNatH;
|
2010-02-09 11:20:51 -05:00
|
|
|
this._bannerLabel.show();
|
|
|
|
this._bannerLabel.allocate(bannerBox, flags);
|
2010-02-03 15:15:12 -05:00
|
|
|
|
|
|
|
if (bannerBox.x2 < bannerBox.x1 + bannerNatW)
|
2010-02-09 11:25:10 -05:00
|
|
|
overflow = true;
|
|
|
|
}
|
|
|
|
|
2010-02-22 16:05:42 -05:00
|
|
|
if (this._bannerBodyText &&
|
|
|
|
(overflow || this._bannerBodyText.indexOf('\n') > -1))
|
2010-02-12 15:15:03 -05:00
|
|
|
this._addBannerBody();
|
2010-02-01 12:10:38 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
popOut: function() {
|
2010-02-09 11:25:10 -05:00
|
|
|
if (this.actor.row_count <= 1)
|
2010-02-01 12:10:38 -05:00
|
|
|
return false;
|
|
|
|
|
2010-02-09 11:20:51 -05:00
|
|
|
Tweener.addTween(this._bannerLabel,
|
2010-02-01 12:10:38 -05:00
|
|
|
{ opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad" });
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
|
|
|
|
popIn: function() {
|
2010-02-09 11:25:10 -05:00
|
|
|
if (this.actor.row_count <= 1)
|
2010-02-01 12:10:38 -05:00
|
|
|
return false;
|
2010-02-09 11:20:51 -05:00
|
|
|
Tweener.addTween(this._bannerLabel,
|
2010-02-01 12:10:38 -05:00
|
|
|
{ opacity: 255,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad" });
|
|
|
|
return true;
|
2010-02-22 17:19:32 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
this.emit('destroy');
|
2010-01-13 15:05:20 -05:00
|
|
|
}
|
|
|
|
};
|
2010-02-01 15:41:22 -05:00
|
|
|
Signals.addSignalMethods(Notification.prototype);
|
2010-01-13 15:05:20 -05:00
|
|
|
|
|
|
|
function Source(id, createIcon) {
|
|
|
|
this._init(id, createIcon);
|
|
|
|
}
|
|
|
|
|
|
|
|
Source.prototype = {
|
|
|
|
_init: function(id, createIcon) {
|
|
|
|
this.id = id;
|
2010-01-28 13:12:03 -05:00
|
|
|
this.text = null;
|
2010-01-13 15:05:20 -05:00
|
|
|
if (createIcon)
|
|
|
|
this.createIcon = createIcon;
|
2010-02-22 17:26:00 -05:00
|
|
|
this.handleReplacing = true;
|
2010-01-13 15:05:20 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
// 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 15:23:49 -05:00
|
|
|
notify: function(notification) {
|
2010-02-25 14:42:18 -05: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 15:23:49 -05:00
|
|
|
this.emit('notify', notification);
|
2010-01-13 15:05:20 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
clicked: function() {
|
|
|
|
this.emit('clicked');
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function() {
|
|
|
|
this.emit('destroy');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
Signals.addSignalMethods(Source.prototype);
|
|
|
|
|
|
|
|
function MessageTray() {
|
|
|
|
this._init();
|
|
|
|
}
|
|
|
|
|
|
|
|
MessageTray.prototype = {
|
|
|
|
_init: function() {
|
|
|
|
this.actor = new St.BoxLayout({ name: 'message-tray',
|
2010-03-15 12:20:10 -04:00
|
|
|
reactive: true,
|
|
|
|
track_hover: true });
|
|
|
|
this.actor.connect('notify::hover', Lang.bind(this, this._onTrayHoverChanged));
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-02-12 16:10:24 -05:00
|
|
|
this._notificationBin = new St.Bin({ reactive: true,
|
2010-03-26 15:23:53 -04:00
|
|
|
anchor_gravity: Clutter.Gravity.NORTH });
|
2010-01-28 12:04:26 -05:00
|
|
|
this.actor.add(this._notificationBin);
|
|
|
|
this._notificationBin.hide();
|
2010-01-13 15:05:20 -05:00
|
|
|
this._notificationQueue = [];
|
2010-02-01 15:23:49 -05:00
|
|
|
this._notification = null;
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-03-26 15:23:53 -04:00
|
|
|
this._summaryBin = new St.Bin({ anchor_gravity: Clutter.Gravity.NORTH_EAST });
|
2010-01-28 12:04:26 -05:00
|
|
|
this.actor.add(this._summaryBin);
|
2010-02-09 13:31:39 -05:00
|
|
|
this._summary = new St.BoxLayout({ name: 'summary-mode',
|
2010-03-15 12:20:10 -04:00
|
|
|
reactive: true,
|
|
|
|
track_hover: true });
|
|
|
|
this._summary.connect('notify::hover', Lang.bind(this, this._onSummaryHoverChanged));
|
|
|
|
this._summaryBin.child = this._summary;
|
2010-02-11 15:39:43 -05:00
|
|
|
this._summaryBin.opacity = 0;
|
2010-02-09 13:31:39 -05:00
|
|
|
|
2010-02-25 14:42:18 -05:00
|
|
|
this._summaryNotificationBin = new St.Bin({ name: 'summary-notification-bin',
|
2010-03-26 15:23:53 -04:00
|
|
|
anchor_gravity: Clutter.Gravity.NORTH_EAST,
|
2010-02-25 14:42:18 -05:00
|
|
|
reactive: true,
|
|
|
|
track_hover: true });
|
|
|
|
this.actor.add(this._summaryNotificationBin);
|
|
|
|
this._summaryNotificationBin.lower_bottom();
|
|
|
|
this._summaryNotificationBin.hide();
|
|
|
|
this._summaryNotificationBin.connect('notify::hover', Lang.bind(this, this._onSummaryNotificationHoverChanged));
|
|
|
|
this._summaryNotification = null;
|
|
|
|
this._hoverSource = null;
|
|
|
|
|
[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 15:31:12 -05: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 14:42:18 -05:00
|
|
|
this._summaryNotificationState = State.HIDDEN;
|
|
|
|
this._summaryNotificationTimeoutId = 0;
|
2010-02-18 19:42:09 -05:00
|
|
|
this._overviewVisible = false;
|
2010-02-22 17:19:32 -05:00
|
|
|
this._notificationRemoved = false;
|
2010-02-09 13:31:39 -05:00
|
|
|
|
2010-02-12 12:33:00 -05:00
|
|
|
Main.chrome.addActor(this.actor, { affectsStruts: false,
|
|
|
|
visibleInOverview: true });
|
2010-02-01 12:10:38 -05:00
|
|
|
Main.chrome.trackActor(this._notificationBin, { affectsStruts: false });
|
2010-02-25 14:42:18 -05:00
|
|
|
Main.chrome.trackActor(this._summaryNotificationBin, { affectsStruts: false });
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-01-28 12:04:26 -05:00
|
|
|
global.connect('screen-size-changed',
|
|
|
|
Lang.bind(this, this._setSizePosition));
|
|
|
|
this._setSizePosition();
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-02-18 19:42:09 -05: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 12:33:00 -05:00
|
|
|
|
2010-01-13 15:05:20 -05:00
|
|
|
this._sources = {};
|
|
|
|
this._icons = {};
|
|
|
|
},
|
|
|
|
|
2010-01-25 13:28:35 -05:00
|
|
|
_setSizePosition: function() {
|
|
|
|
let primary = global.get_primary_monitor();
|
2010-02-02 08:02:32 -05:00
|
|
|
this.actor.x = primary.x;
|
|
|
|
this.actor.y = primary.y + primary.height - 1;
|
2010-01-25 13:28:35 -05:00
|
|
|
this.actor.width = primary.width;
|
2010-01-28 12:04:26 -05:00
|
|
|
|
2010-03-26 15:23:53 -04:00
|
|
|
// These work because of their anchor_gravity
|
|
|
|
this._notificationBin.x = primary.width / 2;
|
|
|
|
this._summaryBin.x = primary.width;
|
|
|
|
this._summaryNotificationBin.x = primary.width;
|
2010-01-25 13:28:35 -05:00
|
|
|
},
|
|
|
|
|
2010-01-13 15:05:20 -05:00
|
|
|
contains: function(source) {
|
|
|
|
return this._sources.hasOwnProperty(source.id);
|
|
|
|
},
|
|
|
|
|
|
|
|
add: function(source) {
|
|
|
|
if (this.contains(source)) {
|
|
|
|
log('Trying to re-add source ' + source.id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-02-25 14:42:18 -05:00
|
|
|
let iconBox = new St.Clickable({ style_class: 'summary-icon',
|
|
|
|
reactive: true });
|
2010-01-13 15:05:20 -05:00
|
|
|
iconBox.child = source.createIcon(ICON_SIZE);
|
|
|
|
this._summary.insert_actor(iconBox, 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 15:31:12 -05:00
|
|
|
this._summaryNeedsToBeShown = true;
|
2010-01-13 15:05:20 -05:00
|
|
|
this._icons[source.id] = iconBox;
|
|
|
|
this._sources[source.id] = source;
|
|
|
|
|
2010-01-28 13:12:03 -05:00
|
|
|
source.connect('notify', Lang.bind(this, this._onNotify));
|
|
|
|
|
2010-02-25 14:42:18 -05:00
|
|
|
iconBox.connect('notify::hover', Lang.bind(this,
|
|
|
|
function () {
|
|
|
|
this._onSourceHoverChanged(source, iconBox.hover);
|
|
|
|
}));
|
2010-03-15 12:20:10 -04:00
|
|
|
iconBox.connect('clicked', Lang.bind(this,
|
2010-01-13 15:05:20 -05:00
|
|
|
function () {
|
|
|
|
source.clicked();
|
|
|
|
}));
|
|
|
|
|
|
|
|
source.connect('destroy', Lang.bind(this,
|
|
|
|
function () {
|
2010-02-22 17:19:32 -05:00
|
|
|
this.removeSource(source);
|
2010-01-13 15:05:20 -05:00
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2010-02-22 17:19:32 -05:00
|
|
|
removeSource: function(source) {
|
2010-01-13 15:05:20 -05:00
|
|
|
if (!this.contains(source))
|
|
|
|
return;
|
|
|
|
|
2010-01-28 13:39:00 -05: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-01-13 15:05:20 -05:00
|
|
|
this._summary.remove_actor(this._icons[source.id]);
|
2010-02-22 17:19:32 -05:00
|
|
|
if (this._summary.get_children().length > 0)
|
|
|
|
this._summaryNeedsToBeShown = true;
|
|
|
|
else
|
|
|
|
this._summaryNeedsToBeShown = false;
|
2010-01-13 15:05:20 -05:00
|
|
|
delete this._icons[source.id];
|
|
|
|
delete this._sources[source.id];
|
[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 15:31:12 -05:00
|
|
|
|
2010-02-25 14:42:18 -05: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 15:31:12 -05:00
|
|
|
if (this._notification && this._notification.source == source) {
|
|
|
|
if (this._notificationTimeoutId) {
|
|
|
|
Mainloop.source_remove(this._notificationTimeoutId);
|
|
|
|
this._notificationTimeoutId = 0;
|
|
|
|
}
|
2010-02-22 17:19:32 -05:00
|
|
|
this._notificationRemoved = true;
|
2010-02-25 14:42:18 -05: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 15:31:12 -05:00
|
|
|
}
|
2010-02-25 14:42:18 -05:00
|
|
|
if (this._hoverSource == source) {
|
|
|
|
this._hoverSource = null;
|
|
|
|
needUpdate = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (needUpdate);
|
|
|
|
this._updateState();
|
2010-01-13 15:05:20 -05:00
|
|
|
},
|
|
|
|
|
2010-02-21 00:25:23 -05:00
|
|
|
removeSourceByApp: function(app) {
|
|
|
|
for (let source in this._sources)
|
|
|
|
if (this._sources[source].app == app)
|
|
|
|
this.removeSource(this._sources[source]);
|
|
|
|
},
|
|
|
|
|
2010-02-22 17:19:32 -05: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 15:05:20 -05:00
|
|
|
getSource: function(id) {
|
|
|
|
return this._sources[id];
|
|
|
|
},
|
|
|
|
|
2010-02-22 17:19:32 -05: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-01 15:23:49 -05:00
|
|
|
_onNotify: function(source, notification) {
|
2010-02-22 17:26:00 -05:00
|
|
|
if (!notification.source.handleReplacing || this._getNotification(notification.id, source) == null) {
|
2010-02-22 17:19:32 -05:00
|
|
|
notification.connect('destroy',
|
|
|
|
Lang.bind(this, this.removeNotification));
|
|
|
|
this._notificationQueue.push(notification);
|
|
|
|
}
|
|
|
|
|
[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 15:31:12 -05:00
|
|
|
this._updateState();
|
2010-01-28 13:12:03 -05:00
|
|
|
},
|
|
|
|
|
2010-02-25 14:42:18 -05:00
|
|
|
_onSourceHoverChanged: function(source, hover) {
|
|
|
|
if (!source.notification)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (this._summaryNotificationTimeoutId != 0) {
|
|
|
|
Mainloop.source_remove(this._summaryNotificationTimeoutId);
|
|
|
|
this._summaryNotificationTimeoutId = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (hover) {
|
|
|
|
this._hoverSource = source;
|
|
|
|
this._updateState();
|
|
|
|
} else if (this._hoverSource == source) {
|
|
|
|
let timeout = HIDE_TIMEOUT * 1000;
|
|
|
|
this._summaryNotificationTimeoutId = Mainloop.timeout_add(timeout, Lang.bind(this, this._onSourceHoverChangedTimeout, source));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onSourceHoverChangedTimeout: function(source) {
|
|
|
|
this._summaryNotificationTimeoutId = 0;
|
|
|
|
if (this._hoverSource == source) {
|
|
|
|
this._hoverSource = null;
|
|
|
|
this._updateState();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onSummaryNotificationHoverChanged: function() {
|
|
|
|
if (!this._summaryNotification)
|
|
|
|
return;
|
|
|
|
this._onSourceHoverChanged(this._summaryNotification.source,
|
|
|
|
this._summaryNotificationBin.hover);
|
|
|
|
},
|
|
|
|
|
2010-03-15 12:20:10 -04: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 15:31:12 -05:00
|
|
|
this._updateState();
|
|
|
|
},
|
2010-01-28 13:39:00 -05:00
|
|
|
|
2010-03-15 12:20:10 -04:00
|
|
|
_onTrayHoverChanged: function() {
|
|
|
|
if (this.actor.hover) {
|
|
|
|
if (this._trayLeftTimeoutId) {
|
|
|
|
Mainloop.source_remove(this._trayLeftTimeoutId);
|
|
|
|
this._trayLeftTimeoutId = 0;
|
|
|
|
return;
|
|
|
|
}
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-03-15 12:20:10 -04: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 14:42:18 -05:00
|
|
|
let timeout = HIDE_TIMEOUT * 1000;
|
2010-03-15 12:20:10 -04: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 15:31:12 -05:00
|
|
|
}
|
2010-01-13 15:05:20 -05: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 15:31:12 -05:00
|
|
|
_onTrayLeftTimeout: function() {
|
|
|
|
this._trayLeftTimeoutId = 0;
|
|
|
|
this._pointerInTray = false;
|
|
|
|
this._pointerInSummary = false;
|
|
|
|
this._updateState();
|
|
|
|
return false;
|
|
|
|
},
|
2010-01-28 13:39:00 -05: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 15:31:12 -05:00
|
|
|
// All of the logic for what happens when occurs here; the various
|
|
|
|
// event handlers merely update variables such as
|
|
|
|
// "this._pointerInTray", "this._summaryState", etc, and
|
|
|
|
// _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 17:19:32 -05: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 15:31:12 -05:00
|
|
|
let notificationExpanded = this._notificationBin.y < 0;
|
2010-02-22 17:19:32 -05:00
|
|
|
let notificationExpired = (this._notificationTimeoutId == 0 && !this._pointerInTray) || 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 15:31:12 -05:00
|
|
|
|
|
|
|
if (this._notificationState == State.HIDDEN) {
|
|
|
|
if (notificationsPending)
|
2010-01-28 13:39:00 -05: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 15:31:12 -05:00
|
|
|
} else if (this._notificationState == State.SHOWN) {
|
|
|
|
if (notificationExpired)
|
2010-01-28 13:39:00 -05: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 15:31:12 -05:00
|
|
|
else if (notificationPinned && !notificationExpanded)
|
|
|
|
this._expandNotification();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Summary
|
2010-02-18 19:42:09 -05: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 15:31:12 -05:00
|
|
|
let summaryPinned = this._summaryTimeoutId != 0 || this._pointerInTray || summarySummoned;
|
2010-02-22 17:19:32 -05: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 15:31:12 -05: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 13:39:00 -05:00
|
|
|
this._hideSummary();
|
|
|
|
}
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-02-25 14:42:18 -05:00
|
|
|
// Summary notification
|
|
|
|
let haveSummaryNotification = this._hoverSource != null;
|
|
|
|
let summaryNotificationIsMainNotification = (haveSummaryNotification &&
|
|
|
|
this._hoverSource.notification == this._notification);
|
|
|
|
let canShowSummaryNotification = this._summaryState == State.SHOWN;
|
|
|
|
let wrongSummaryNotification = (haveSummaryNotification &&
|
|
|
|
this._summaryNotification != this._hoverSource.notification);
|
|
|
|
|
|
|
|
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 15:31:12 -05: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 15:05:20 -05:00
|
|
|
},
|
|
|
|
|
2010-01-28 13:39:00 -05:00
|
|
|
_showTray: function() {
|
2010-01-13 15:05:20 -05:00
|
|
|
let primary = global.get_primary_monitor();
|
[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 15:31:12 -05:00
|
|
|
this._tween(this.actor, "_trayState", State.SHOWN,
|
|
|
|
{ y: primary.y + primary.height - this.actor.height,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad"
|
|
|
|
});
|
2010-01-13 15:05:20 -05:00
|
|
|
},
|
|
|
|
|
2010-01-28 13:39:00 -05:00
|
|
|
_hideTray: function() {
|
2010-01-13 15:05:20 -05:00
|
|
|
let primary = global.get_primary_monitor();
|
[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 15:31:12 -05:00
|
|
|
this._tween(this.actor, "_trayState", State.HIDDEN,
|
|
|
|
{ y: primary.y + primary.height - 1,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad"
|
|
|
|
});
|
2010-01-13 15:05:20 -05:00
|
|
|
},
|
|
|
|
|
2010-01-28 13:39:00 -05:00
|
|
|
_showNotification: function() {
|
2010-02-01 15:23:49 -05:00
|
|
|
this._notification = this._notificationQueue.shift();
|
|
|
|
this._notificationBin.child = this._notification.actor;
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-01-28 12:04:26 -05:00
|
|
|
this._notificationBin.opacity = 0;
|
|
|
|
this._notificationBin.y = this.actor.height;
|
|
|
|
this._notificationBin.show();
|
2010-01-13 15:05:20 -05: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 15:31:12 -05:00
|
|
|
this._tween(this._notificationBin, "_notificationState", State.SHOWN,
|
|
|
|
{ y: 0,
|
|
|
|
opacity: 255,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad",
|
|
|
|
onComplete: this._showNotificationCompleted,
|
|
|
|
onCompleteScope: this
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_showNotificationCompleted: function() {
|
|
|
|
this._notificationTimeoutId =
|
|
|
|
Mainloop.timeout_add(NOTIFICATION_TIMEOUT * 1000,
|
|
|
|
Lang.bind(this, this._notificationTimeout));
|
|
|
|
},
|
|
|
|
|
|
|
|
_notificationTimeout: function() {
|
|
|
|
this._notificationTimeoutId = 0;
|
|
|
|
this._updateState();
|
|
|
|
return false;
|
2010-01-28 13:39:00 -05:00
|
|
|
},
|
2010-01-13 15:05:20 -05:00
|
|
|
|
2010-01-28 13:39:00 -05:00
|
|
|
_hideNotification: function() {
|
2010-02-01 15:23:49 -05:00
|
|
|
this._notification.popIn();
|
2010-02-01 12:10:38 -05: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 15:31:12 -05:00
|
|
|
this._tween(this._notificationBin, "_notificationState", State.HIDDEN,
|
|
|
|
{ y: this.actor.height,
|
|
|
|
opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad",
|
|
|
|
onComplete: this._hideNotificationCompleted,
|
|
|
|
onCompleteScope: this
|
|
|
|
});
|
2010-01-28 13:39:00 -05:00
|
|
|
},
|
2010-01-13 15:05:20 -05: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 15:31:12 -05:00
|
|
|
_hideNotificationCompleted: function() {
|
2010-02-25 16:19:11 -05: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 15:31:12 -05:00
|
|
|
this._notificationBin.hide();
|
|
|
|
this._notificationBin.child = null;
|
|
|
|
this._notification = null;
|
|
|
|
},
|
|
|
|
|
|
|
|
_expandNotification: function() {
|
|
|
|
if (this._notification && this._notification.popOut()) {
|
|
|
|
this._tween(this._notificationBin, "_notificationState", State.SHOWN,
|
|
|
|
{ y: this.actor.height - this._notificationBin.height,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad"
|
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_showSummary: function(withTimeout) {
|
2010-02-02 08:02:32 -05:00
|
|
|
let primary = global.get_primary_monitor();
|
2010-01-28 13:39:00 -05:00
|
|
|
this._summaryBin.opacity = 0;
|
|
|
|
this._summaryBin.y = this.actor.height;
|
[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 15:31:12 -05:00
|
|
|
this._tween(this._summaryBin, "_summaryState", State.SHOWN,
|
|
|
|
{ y: 0,
|
|
|
|
opacity: 255,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad",
|
|
|
|
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 13:39:00 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
_hideSummary: function() {
|
[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 15:31:12 -05:00
|
|
|
this._tween(this._summaryBin, "_summaryState", State.HIDDEN,
|
|
|
|
{ opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad"
|
|
|
|
});
|
|
|
|
this._summaryNeedsToBeShown = false;
|
2010-02-25 14:42:18 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
_showSummaryNotification: function() {
|
|
|
|
this._summaryNotification = this._hoverSource.notification;
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
this._tween(this._summaryNotificationBin, "_summaryNotificationState", State.SHOWN,
|
|
|
|
{ y: this.actor.height - this._summaryNotificationBin.height,
|
|
|
|
opacity: 255,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad"
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_hideSummaryNotification: function() {
|
|
|
|
this._summaryNotification.popIn();
|
|
|
|
|
|
|
|
this._tween(this._summaryNotificationBin, "_summaryNotificationState", State.HIDDEN,
|
|
|
|
{ y: this.actor.height,
|
|
|
|
opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: "easeOutQuad",
|
|
|
|
onComplete: this._hideSummaryNotificationCompleted,
|
|
|
|
onCompleteScope: this
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
_hideSummaryNotificationCompleted: function() {
|
|
|
|
this._summaryNotificationBin.hide();
|
|
|
|
this._summaryNotificationBin.child = null;
|
|
|
|
this._summaryNotification = null;
|
2010-01-28 13:39:00 -05:00
|
|
|
}
|
2010-01-13 15:05:20 -05:00
|
|
|
};
|