2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2010-01-13 20:05:20 +00:00
|
|
|
|
|
|
|
const Clutter = imports.gi.Clutter;
|
2010-11-23 23:27:47 +00:00
|
|
|
const GLib = imports.gi.GLib;
|
|
|
|
const Gio = imports.gi.Gio;
|
2010-02-22 19:23:36 +00:00
|
|
|
const Gtk = imports.gi.Gtk;
|
2012-08-20 04:06:50 +00:00
|
|
|
const GnomeDesktop = imports.gi.GnomeDesktop;
|
2012-02-20 19:27:27 +00:00
|
|
|
const Atk = imports.gi.Atk;
|
2010-01-13 20:05:20 +00:00
|
|
|
const Lang = imports.lang;
|
|
|
|
const Mainloop = imports.mainloop;
|
2010-09-09 20:18:07 +00:00
|
|
|
const Meta = imports.gi.Meta;
|
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
|
|
|
|
2010-10-19 19:17:36 +00:00
|
|
|
const BoxPointer = imports.ui.boxpointer;
|
2012-08-09 00:30:13 +00:00
|
|
|
const CtrlAltTab = imports.ui.ctrlAltTab;
|
2011-02-19 15:49:56 +00:00
|
|
|
const GnomeSession = imports.misc.gnomeSession;
|
2012-02-28 20:04:00 +00:00
|
|
|
const GrabHelper = imports.ui.grabHelper;
|
2012-11-04 17:49:14 +00:00
|
|
|
const Hash = imports.misc.hash;
|
2012-08-08 17:51:31 +00:00
|
|
|
const Lightbox = imports.ui.lightbox;
|
2011-02-19 15:49:56 +00:00
|
|
|
const Main = imports.ui.main;
|
2012-08-17 20:07:42 +00:00
|
|
|
const PointerWatcher = imports.ui.pointerWatcher;
|
2011-02-11 19:43:01 +00:00
|
|
|
const PopupMenu = imports.ui.popupMenu;
|
2010-08-18 20:01:33 +00:00
|
|
|
const Params = imports.misc.params;
|
2011-02-19 15:49:56 +00:00
|
|
|
const Tweener = imports.ui.tweener;
|
2010-11-30 16:16:10 +00:00
|
|
|
const Util = imports.misc.util;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2012-07-14 11:07:24 +00:00
|
|
|
const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
|
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
const ANIMATION_TIME = 0.2;
|
|
|
|
const NOTIFICATION_TIMEOUT = 4;
|
2010-01-28 18:39:00 +00:00
|
|
|
const SUMMARY_TIMEOUT = 1;
|
2011-02-19 15:49:56 +00:00
|
|
|
const LONGER_SUMMARY_TIMEOUT = 4;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-02-25 19:42:18 +00:00
|
|
|
const HIDE_TIMEOUT = 0.2;
|
2010-08-31 19:50:18 +00:00
|
|
|
const LONGER_HIDE_TIMEOUT = 0.6;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-10-30 19:54:43 +00:00
|
|
|
// We delay hiding of the tray if the mouse is within MOUSE_LEFT_ACTOR_THRESHOLD
|
|
|
|
// range from the point where it left the tray.
|
|
|
|
const MOUSE_LEFT_ACTOR_THRESHOLD = 20;
|
|
|
|
|
2012-08-17 20:07:42 +00:00
|
|
|
// Time the user needs to leave the mouse on the bottom pixel row to open the tray
|
|
|
|
const TRAY_DWELL_TIME = 1000; // ms
|
|
|
|
// Time resolution when tracking the mouse to catch the open tray dwell
|
|
|
|
const TRAY_DWELL_CHECK_INTERVAL = 100; // ms
|
|
|
|
|
2012-08-10 21:54:33 +00:00
|
|
|
const IDLE_TIME = 1000;
|
|
|
|
|
[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
|
|
|
};
|
|
|
|
|
2011-01-27 23:26:53 +00:00
|
|
|
// These reasons are useful when we destroy the notifications received through
|
|
|
|
// the notification daemon. We use EXPIRED for transient notifications that the
|
|
|
|
// user did not interact with, DISMISSED for all other notifications that were
|
|
|
|
// destroyed as a result of a user action, and SOURCE_CLOSED for the notifications
|
|
|
|
// that were requested to be destroyed by the associated source.
|
|
|
|
const NotificationDestroyedReason = {
|
|
|
|
EXPIRED: 1,
|
|
|
|
DISMISSED: 2,
|
2011-01-28 18:31:16 +00:00
|
|
|
SOURCE_CLOSED: 3
|
2011-01-27 23:26:53 +00:00
|
|
|
};
|
|
|
|
|
2011-01-04 09:34:57 +00:00
|
|
|
// Message tray has its custom Urgency enumeration. LOW, NORMAL and CRITICAL
|
|
|
|
// urgency values map to the corresponding values for the notifications received
|
|
|
|
// through the notification daemon. HIGH urgency value is used for chats received
|
|
|
|
// through the Telepathy client.
|
|
|
|
const Urgency = {
|
|
|
|
LOW: 0,
|
|
|
|
NORMAL: 1,
|
|
|
|
HIGH: 2,
|
|
|
|
CRITICAL: 3
|
|
|
|
}
|
|
|
|
|
2010-11-23 23:31:55 +00:00
|
|
|
function _fixMarkup(text, allowMarkup) {
|
|
|
|
if (allowMarkup) {
|
|
|
|
// Support &, ", ', < and >, escape all other
|
|
|
|
// occurrences of '&'.
|
|
|
|
let _text = text.replace(/&(?!amp;|quot;|apos;|lt;|gt;)/g, '&');
|
2011-05-04 15:15:45 +00:00
|
|
|
|
2010-11-23 23:31:55 +00:00
|
|
|
// Support <b>, <i>, and <u>, escape anything else
|
|
|
|
// so it displays as raw markup.
|
2011-05-04 15:15:45 +00:00
|
|
|
_text = _text.replace(/<(?!\/?[biu]>)/g, '<');
|
|
|
|
|
|
|
|
try {
|
|
|
|
Pango.parse_markup(_text, -1, '');
|
|
|
|
return _text;
|
|
|
|
} catch (e) {}
|
2010-11-23 23:31:55 +00:00
|
|
|
}
|
2011-05-04 15:15:45 +00:00
|
|
|
|
|
|
|
// !allowMarkup, or invalid markup
|
|
|
|
return GLib.markup_escape_text(text, -1);
|
2010-02-01 20:23:49 +00:00
|
|
|
}
|
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const URLHighlighter = new Lang.Class({
|
|
|
|
Name: 'URLHighlighter',
|
2010-11-23 23:27:47 +00:00
|
|
|
|
2010-11-23 23:31:55 +00:00
|
|
|
_init: function(text, lineWrap, allowMarkup) {
|
2010-11-23 23:27:47 +00:00
|
|
|
if (!text)
|
|
|
|
text = '';
|
|
|
|
this.actor = new St.Label({ reactive: true, style_class: 'url-highlighter' });
|
|
|
|
this._linkColor = '#ccccff';
|
|
|
|
this.actor.connect('style-changed', Lang.bind(this, function() {
|
2011-02-14 14:20:22 +00:00
|
|
|
let [hasColor, color] = this.actor.get_theme_node().lookup_color('link-color', false);
|
2010-11-23 23:27:47 +00:00
|
|
|
if (hasColor) {
|
|
|
|
let linkColor = color.to_string().substr(0, 7);
|
|
|
|
if (linkColor != this._linkColor) {
|
|
|
|
this._linkColor = linkColor;
|
|
|
|
this._highlightUrls();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
if (lineWrap) {
|
|
|
|
this.actor.clutter_text.line_wrap = true;
|
|
|
|
this.actor.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
|
|
|
|
this.actor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
|
|
|
}
|
|
|
|
|
2010-11-23 23:31:55 +00:00
|
|
|
this.setMarkup(text, allowMarkup);
|
2011-03-27 15:58:23 +00:00
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, function(actor, event) {
|
2011-08-29 23:10:14 +00:00
|
|
|
// Don't try to URL highlight when invisible.
|
|
|
|
// The MessageTray doesn't actually hide us, so
|
|
|
|
// we need to check for paint opacities as well.
|
|
|
|
if (!actor.visible || actor.get_paint_opacity() == 0)
|
|
|
|
return false;
|
|
|
|
|
2011-03-23 20:05:38 +00:00
|
|
|
// Keep Notification.actor from seeing this and taking
|
|
|
|
// a pointer grab, which would block our button-release-event
|
2011-03-27 15:58:23 +00:00
|
|
|
// handler, if an URL is clicked
|
|
|
|
return this._findUrlAtPos(event) != -1;
|
|
|
|
}));
|
2010-11-23 23:27:47 +00:00
|
|
|
this.actor.connect('button-release-event', Lang.bind(this, function (actor, event) {
|
2011-08-29 23:10:14 +00:00
|
|
|
if (!actor.visible || actor.get_paint_opacity() == 0)
|
|
|
|
return false;
|
|
|
|
|
2010-11-23 23:27:47 +00:00
|
|
|
let urlId = this._findUrlAtPos(event);
|
|
|
|
if (urlId != -1) {
|
|
|
|
let url = this._urls[urlId].url;
|
|
|
|
if (url.indexOf(':') == -1)
|
|
|
|
url = 'http://' + url;
|
2012-10-16 16:20:39 +00:00
|
|
|
|
|
|
|
Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context());
|
|
|
|
return true;
|
2010-11-23 23:27:47 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}));
|
|
|
|
this.actor.connect('motion-event', Lang.bind(this, function(actor, event) {
|
2011-08-29 23:10:14 +00:00
|
|
|
if (!actor.visible || actor.get_paint_opacity() == 0)
|
|
|
|
return false;
|
|
|
|
|
2010-11-23 23:27:47 +00:00
|
|
|
let urlId = this._findUrlAtPos(event);
|
|
|
|
if (urlId != -1 && !this._cursorChanged) {
|
|
|
|
global.set_cursor(Shell.Cursor.POINTING_HAND);
|
|
|
|
this._cursorChanged = true;
|
|
|
|
} else if (urlId == -1) {
|
|
|
|
global.unset_cursor();
|
|
|
|
this._cursorChanged = false;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}));
|
|
|
|
this.actor.connect('leave-event', Lang.bind(this, function() {
|
2011-08-29 23:10:14 +00:00
|
|
|
if (!this.actor.visible || this.actor.get_paint_opacity() == 0)
|
|
|
|
return;
|
|
|
|
|
2010-11-23 23:27:47 +00:00
|
|
|
if (this._cursorChanged) {
|
|
|
|
this._cursorChanged = false;
|
|
|
|
global.unset_cursor();
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2010-11-23 23:31:55 +00:00
|
|
|
setMarkup: function(text, allowMarkup) {
|
|
|
|
text = text ? _fixMarkup(text, allowMarkup) : '';
|
2010-11-23 23:27:47 +00:00
|
|
|
this._text = text;
|
|
|
|
|
|
|
|
this.actor.clutter_text.set_markup(text);
|
|
|
|
/* clutter_text.text contain text without markup */
|
2010-11-30 16:16:10 +00:00
|
|
|
this._urls = Util.findUrls(this.actor.clutter_text.text);
|
2010-11-23 23:27:47 +00:00
|
|
|
this._highlightUrls();
|
|
|
|
},
|
|
|
|
|
|
|
|
_highlightUrls: function() {
|
|
|
|
// text here contain markup
|
2010-11-30 16:16:10 +00:00
|
|
|
let urls = Util.findUrls(this._text);
|
2010-11-23 23:27:47 +00:00
|
|
|
let markup = '';
|
|
|
|
let pos = 0;
|
|
|
|
for (let i = 0; i < urls.length; i++) {
|
|
|
|
let url = urls[i];
|
|
|
|
let str = this._text.substr(pos, url.pos - pos);
|
|
|
|
markup += str + '<span foreground="' + this._linkColor + '"><u>' + url.url + '</u></span>';
|
|
|
|
pos = url.pos + url.url.length;
|
|
|
|
}
|
|
|
|
markup += this._text.substr(pos);
|
|
|
|
this.actor.clutter_text.set_markup(markup);
|
|
|
|
},
|
|
|
|
|
|
|
|
_findUrlAtPos: function(event) {
|
|
|
|
let success;
|
|
|
|
let [x, y] = event.get_coords();
|
|
|
|
[success, x, y] = this.actor.transform_stage_point(x, y);
|
|
|
|
let find_pos = -1;
|
|
|
|
for (let i = 0; i < this.actor.clutter_text.text.length; i++) {
|
|
|
|
let [success, px, py, line_height] = this.actor.clutter_text.position_to_coords(i);
|
|
|
|
if (py > y || py + line_height < y || x < px)
|
|
|
|
continue;
|
|
|
|
find_pos = i;
|
|
|
|
}
|
|
|
|
if (find_pos != -1) {
|
|
|
|
for (let i = 0; i < this._urls.length; i++)
|
|
|
|
if (find_pos >= this._urls[i].pos &&
|
|
|
|
this._urls[i].pos + this._urls[i].url.length > find_pos)
|
|
|
|
return i;
|
|
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2010-11-23 23:27:47 +00:00
|
|
|
|
2012-08-20 19:51:52 +00:00
|
|
|
function makeCloseButton() {
|
|
|
|
let closeButton = new St.Button({ style_class: 'notification-close'});
|
|
|
|
|
|
|
|
// This is a bit tricky. St.Bin has its own x-align/y-align properties
|
|
|
|
// that compete with Clutter's properties. This should be fixed for
|
|
|
|
// Clutter 2.0. Since St.Bin doesn't define its own setters, the
|
|
|
|
// setters are a workaround to get Clutter's version.
|
|
|
|
closeButton.set_x_align(Clutter.ActorAlign.END);
|
|
|
|
closeButton.set_y_align(Clutter.ActorAlign.START);
|
|
|
|
|
|
|
|
// XXX Clutter 2.0 workaround: ClutterBinLayout needs expand
|
|
|
|
// to respect the alignments.
|
|
|
|
closeButton.set_x_expand(true);
|
|
|
|
closeButton.set_y_expand(true);
|
|
|
|
|
|
|
|
closeButton.connect('style-changed', function() {
|
|
|
|
let themeNode = closeButton.get_theme_node();
|
2012-08-20 20:44:28 +00:00
|
|
|
closeButton.translation_x = themeNode.get_length('-shell-close-overlap-x');
|
2012-10-16 17:02:48 +00:00
|
|
|
closeButton.translation_y = themeNode.get_length('-shell-close-overlap-y');
|
2012-08-20 19:51:52 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
return closeButton;
|
|
|
|
}
|
|
|
|
|
2013-01-19 17:50:49 +00:00
|
|
|
function strHasSuffix(string, suffix) {
|
|
|
|
return string.substr(-suffix.length) == suffix;
|
|
|
|
}
|
|
|
|
|
2012-10-16 15:08:54 +00:00
|
|
|
// NotificationPolicy:
|
|
|
|
// An object that holds all bits of configurable policy related to a notification
|
|
|
|
// source, such as whether to play sound or honour the critical bit.
|
|
|
|
//
|
|
|
|
// A notification without a policy object will inherit the default one.
|
|
|
|
const NotificationPolicy = new Lang.Class({
|
|
|
|
Name: 'NotificationPolicy',
|
|
|
|
|
|
|
|
_init: function(params) {
|
|
|
|
params = Params.parse(params, { enable: true,
|
|
|
|
enableSound: true,
|
|
|
|
showBanners: true,
|
|
|
|
forceExpanded: false,
|
|
|
|
showInLockScreen: true,
|
|
|
|
detailsInLockScreen: false
|
|
|
|
});
|
|
|
|
Lang.copyProperties(params, this);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Do nothing for the default policy. These methods are only useful for the
|
|
|
|
// GSettings policy.
|
|
|
|
store: function() { },
|
|
|
|
destroy: function() { }
|
|
|
|
});
|
|
|
|
Signals.addSignalMethods(NotificationPolicy.prototype);
|
|
|
|
|
2010-02-01 17:10:38 +00:00
|
|
|
// Notification:
|
|
|
|
// @source: the notification's Source
|
|
|
|
// @title: the title
|
|
|
|
// @banner: the banner text
|
2010-08-18 20:01:33 +00:00
|
|
|
// @params: optional additional params
|
2010-02-01 17:10:38 +00:00
|
|
|
//
|
2010-08-30 20:03:08 +00:00
|
|
|
// Creates a notification. In the banner mode, the notification
|
|
|
|
// will show an icon, @title (in bold) and @banner, all on a single
|
|
|
|
// line (with @banner ellipsized if necessary).
|
2010-02-09 16:25:10 +00:00
|
|
|
//
|
2010-08-30 20:03:08 +00:00
|
|
|
// The notification will be expandable if either it has additional
|
|
|
|
// elements that were added to it or if the @banner text did not
|
|
|
|
// fit fully in the banner mode. When the notification is expanded,
|
|
|
|
// the @banner text from the top line is always removed. The complete
|
|
|
|
// @banner text is added as the first element in the content section,
|
|
|
|
// unless 'customContent' parameter with the value 'true' is specified
|
|
|
|
// in @params.
|
2010-08-05 17:09:27 +00:00
|
|
|
//
|
2010-08-30 20:03:08 +00:00
|
|
|
// Additional notification content can be added with addActor() and
|
|
|
|
// addBody() methods. The notification content is put inside a
|
2010-02-22 19:23:36 +00:00
|
|
|
// scrollview, so if it gets too tall, the notification will scroll
|
2010-08-30 20:03:08 +00:00
|
|
|
// rather than continue 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. The action area can
|
|
|
|
// be set by calling setActionArea() method. There is also a
|
|
|
|
// convenience method addButton() for adding a button to the action
|
|
|
|
// area.
|
|
|
|
//
|
|
|
|
// @params can contain values for 'customContent', 'body', 'icon',
|
2010-11-23 23:31:55 +00:00
|
|
|
// 'titleMarkup', 'bannerMarkup', 'bodyMarkup', and 'clear'
|
|
|
|
// parameters.
|
2010-08-30 20:03:08 +00:00
|
|
|
//
|
|
|
|
// If @params contains a 'customContent' parameter with the value %true,
|
|
|
|
// then @banner will not be shown in the body of the notification when the
|
|
|
|
// notification is expanded and calls to update() will not clear the content
|
|
|
|
// unless 'clear' parameter with value %true is explicitly specified.
|
|
|
|
//
|
|
|
|
// If @params contains a 'body' parameter, then that text will be added to
|
|
|
|
// the content area (as with addBody()).
|
2010-02-09 16:25:10 +00:00
|
|
|
//
|
2012-09-14 14:33:52 +00:00
|
|
|
// By default, the icon shown is the same as the source's.
|
|
|
|
// However, if @params contains a 'gicon' parameter, the passed in gicon
|
|
|
|
// will be used.
|
|
|
|
//
|
|
|
|
// You can add a secondary icon to the banner with 'secondaryGIcon'. There
|
|
|
|
// is no fallback for this icon.
|
2010-08-30 20:03:08 +00:00
|
|
|
//
|
2010-11-23 23:31:55 +00:00
|
|
|
// If @params contains a 'titleMarkup', 'bannerMarkup', or
|
|
|
|
// 'bodyMarkup' parameter with the value %true, then the corresponding
|
|
|
|
// element is assumed to use pango markup. If the parameter is not
|
|
|
|
// present for an element, then anything that looks like markup in
|
|
|
|
// that element will appear literally in the output.
|
|
|
|
//
|
2010-08-30 20:03:08 +00:00
|
|
|
// If @params contains a 'clear' parameter with the value %true, then
|
|
|
|
// the content and the action area of the notification will be cleared.
|
|
|
|
// The content area is also always cleared if 'customContent' is false
|
|
|
|
// because it might contain the @banner that didn't fit in the banner mode.
|
2011-11-20 15:12:02 +00:00
|
|
|
const Notification = new Lang.Class({
|
|
|
|
Name: 'Notification',
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2012-09-14 14:33:52 +00:00
|
|
|
ICON_SIZE: 24,
|
|
|
|
|
2011-08-29 17:11:24 +00:00
|
|
|
IMAGE_SIZE: 125,
|
|
|
|
|
2010-08-18 20:01:33 +00:00
|
|
|
_init: function(source, title, banner, params) {
|
2010-01-28 18:39:00 +00:00
|
|
|
this.source = source;
|
2011-09-21 20:46:08 +00:00
|
|
|
this.title = title;
|
2011-01-04 09:34:57 +00:00
|
|
|
this.urgency = Urgency.NORMAL;
|
2010-12-16 20:49:47 +00:00
|
|
|
this.resident = false;
|
2010-12-15 21:30:50 +00:00
|
|
|
// 'transient' is a reserved keyword in JS, so we have to use an alternate variable name
|
|
|
|
this.isTransient = false;
|
2013-01-30 18:47:55 +00:00
|
|
|
this.isMusic = false;
|
2012-11-02 17:06:40 +00:00
|
|
|
this.forFeedback = false;
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this.expanded = false;
|
2012-09-05 16:59:50 +00:00
|
|
|
this.focused = false;
|
2012-08-06 15:28:55 +00:00
|
|
|
this.acknowledged = false;
|
2011-01-30 23:33:49 +00:00
|
|
|
this._destroyed = false;
|
2010-10-30 06:29:21 +00:00
|
|
|
this._useActionIcons = false;
|
2010-08-30 20:03:08 +00:00
|
|
|
this._customContent = false;
|
2013-01-30 18:47:55 +00:00
|
|
|
this.bannerBodyText = null;
|
|
|
|
this.bannerBodyMarkup = false;
|
2010-08-29 05:34:27 +00:00
|
|
|
this._titleFitsInBannerMode = true;
|
2012-02-14 01:37:28 +00:00
|
|
|
this._titleDirection = Clutter.TextDirection.DEFAULT;
|
2010-08-26 20:05:27 +00:00
|
|
|
this._spacing = 0;
|
2011-03-22 07:40:53 +00:00
|
|
|
this._scrollPolicy = Gtk.PolicyType.AUTOMATIC;
|
2011-08-29 17:11:24 +00:00
|
|
|
this._imageBin = null;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2011-01-30 23:08:20 +00:00
|
|
|
source.connect('destroy', Lang.bind(this,
|
2011-03-21 21:43:34 +00:00
|
|
|
function (source, reason) {
|
|
|
|
this.destroy(reason);
|
2011-01-30 23:08:20 +00:00
|
|
|
}));
|
2010-07-14 21:07:06 +00:00
|
|
|
|
2012-02-20 19:27:27 +00:00
|
|
|
this.actor = new St.Button({ accessible_role: Atk.Role.NOTIFICATION });
|
2012-08-26 18:27:06 +00:00
|
|
|
this.actor.add_style_class_name('notification-unexpanded');
|
2011-03-21 21:43:34 +00:00
|
|
|
this.actor._delegate = this;
|
2011-03-03 20:14:52 +00:00
|
|
|
this.actor.connect('clicked', Lang.bind(this, this._onClicked));
|
2011-03-03 00:47:58 +00:00
|
|
|
this.actor.connect('destroy', Lang.bind(this, this._onDestroy));
|
2010-06-13 14:17:35 +00:00
|
|
|
|
2011-10-12 22:49:04 +00:00
|
|
|
this._table = new St.Table({ style_class: 'notification',
|
2011-03-03 20:14:52 +00:00
|
|
|
reactive: true });
|
|
|
|
this._table.connect('style-changed', Lang.bind(this, this._styleChanged));
|
|
|
|
this.actor.set_child(this._table);
|
|
|
|
|
2010-07-28 15:02:32 +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));
|
2011-03-03 20:14:52 +00:00
|
|
|
this._table.add(this._bannerBox, { row: 0,
|
|
|
|
col: 1,
|
2011-08-29 17:11:24 +00:00
|
|
|
col_span: 2,
|
|
|
|
x_expand: false,
|
2011-03-03 20:14:52 +00:00
|
|
|
y_expand: false,
|
|
|
|
y_fill: false });
|
2010-07-28 15:02:32 +00:00
|
|
|
|
2011-08-29 17:11:24 +00:00
|
|
|
// This is an empty cell that overlaps with this._bannerBox cell to ensure
|
|
|
|
// that this._bannerBox cell expands horizontally, while not forcing the
|
|
|
|
// this._imageBin that is also in col: 2 to expand horizontally.
|
|
|
|
this._table.add(new St.Bin(), { row: 0,
|
|
|
|
col: 2,
|
|
|
|
y_expand: false,
|
|
|
|
y_fill: false });
|
|
|
|
|
2010-07-28 15:02:32 +00:00
|
|
|
this._titleLabel = new St.Label();
|
|
|
|
this._bannerBox.add_actor(this._titleLabel);
|
2010-11-23 23:27:47 +00:00
|
|
|
this._bannerUrlHighlighter = new URLHighlighter();
|
|
|
|
this._bannerLabel = this._bannerUrlHighlighter.actor;
|
2010-07-28 15:02:32 +00:00
|
|
|
this._bannerBox.add_actor(this._bannerLabel);
|
|
|
|
|
2010-08-18 20:01:33 +00:00
|
|
|
this.update(title, banner, params);
|
2010-02-09 16:25:10 +00:00
|
|
|
},
|
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-08-18 20:01:33 +00:00
|
|
|
// @params: as in the Notification constructor
|
2010-02-09 16:25:10 +00:00
|
|
|
//
|
|
|
|
// Updates the notification by regenerating its icon and updating
|
2010-08-18 20:01:33 +00:00
|
|
|
// the title/banner. If @params.clear is %true, it will also
|
|
|
|
// remove any additional actors/action buttons previously added.
|
|
|
|
update: function(title, banner, params) {
|
2010-08-30 20:03:08 +00:00
|
|
|
params = Params.parse(params, { customContent: false,
|
2010-08-18 20:01:33 +00:00
|
|
|
body: null,
|
2012-09-14 14:33:52 +00:00
|
|
|
gicon: null,
|
|
|
|
secondaryGIcon: null,
|
2010-11-23 23:31:55 +00:00
|
|
|
titleMarkup: false,
|
|
|
|
bannerMarkup: false,
|
|
|
|
bodyMarkup: false,
|
2010-08-18 20:01:33 +00:00
|
|
|
clear: false });
|
|
|
|
|
2010-08-30 20:03:08 +00:00
|
|
|
this._customContent = params.customContent;
|
|
|
|
|
2011-03-02 14:48:29 +00:00
|
|
|
let oldFocus = global.stage.key_focus;
|
|
|
|
|
2012-09-14 14:33:52 +00:00
|
|
|
if (this._icon && (params.gicon || params.clear)) {
|
2010-02-22 19:23:36 +00:00
|
|
|
this._icon.destroy();
|
2011-09-21 20:47:49 +00:00
|
|
|
this._icon = null;
|
|
|
|
}
|
|
|
|
|
2012-09-14 14:33:52 +00:00
|
|
|
if (this._secondaryIcon && (params.secondaryGIcon || params.clear)) {
|
2012-07-05 17:47:13 +00:00
|
|
|
this._secondaryIcon.destroy();
|
|
|
|
this._secondaryIcon = null;
|
|
|
|
}
|
|
|
|
|
2010-08-30 20:03:08 +00:00
|
|
|
// We always clear the content area if we don't have custom
|
|
|
|
// content because it might contain the @banner that didn't
|
|
|
|
// fit in the banner mode.
|
|
|
|
if (this._scrollArea && (!this._customContent || params.clear)) {
|
2011-03-02 14:48:29 +00:00
|
|
|
if (oldFocus && this._scrollArea.contains(oldFocus))
|
|
|
|
this.actor.grab_key_focus();
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this._scrollArea.destroy();
|
|
|
|
this._scrollArea = null;
|
|
|
|
this._contentArea = null;
|
2010-02-09 16:25:10 +00:00
|
|
|
}
|
2010-08-18 20:01:33 +00:00
|
|
|
if (this._actionArea && params.clear) {
|
2011-03-02 14:48:29 +00:00
|
|
|
if (oldFocus && this._actionArea.contains(oldFocus))
|
|
|
|
this.actor.grab_key_focus();
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this._actionArea.destroy();
|
|
|
|
this._actionArea = null;
|
|
|
|
this._buttonBox = null;
|
2010-02-09 16:25:10 +00:00
|
|
|
}
|
2013-01-19 17:49:55 +00:00
|
|
|
if (params.clear)
|
2011-08-29 17:11:24 +00:00
|
|
|
this.unsetImage();
|
|
|
|
|
|
|
|
if (!this._scrollArea && !this._actionArea && !this._imageBin)
|
2011-03-03 20:14:52 +00:00
|
|
|
this._table.remove_style_class_name('multi-line-notification');
|
2010-02-09 16:25:10 +00:00
|
|
|
|
2012-09-14 14:33:52 +00:00
|
|
|
if (params.gicon) {
|
|
|
|
this._icon = new St.Icon({ gicon: params.gicon,
|
|
|
|
icon_size: this.ICON_SIZE });
|
|
|
|
} else {
|
|
|
|
this._icon = this.source.createIcon(this.ICON_SIZE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this._icon) {
|
2011-09-21 20:47:49 +00:00
|
|
|
this._table.add(this._icon, { row: 0,
|
|
|
|
col: 0,
|
|
|
|
x_expand: false,
|
|
|
|
y_expand: false,
|
|
|
|
y_fill: false,
|
|
|
|
y_align: St.Align.START });
|
|
|
|
}
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2012-09-14 14:33:52 +00:00
|
|
|
if (params.secondaryGIcon) {
|
|
|
|
this._secondaryIcon = new St.Icon({ gicon: params.secondaryGIcon,
|
|
|
|
style_class: 'secondary-icon' });
|
|
|
|
this._bannerBox.add_actor(this._secondaryIcon);
|
2012-07-05 17:47:13 +00:00
|
|
|
}
|
|
|
|
|
2011-09-21 20:46:08 +00:00
|
|
|
this.title = title;
|
2010-11-23 23:31:55 +00:00
|
|
|
title = title ? _fixMarkup(title.replace(/\n/g, ' '), params.titleMarkup) : '';
|
2010-02-09 16:20:51 +00:00
|
|
|
this._titleLabel.clutter_text.set_markup('<b>' + title + '</b>');
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2011-04-06 16:23:49 +00:00
|
|
|
if (Pango.find_base_dir(title, -1) == Pango.Direction.RTL)
|
2012-02-14 01:37:28 +00:00
|
|
|
this._titleDirection = Clutter.TextDirection.RTL;
|
2011-04-06 16:23:49 +00:00
|
|
|
else
|
2012-02-14 01:37:28 +00:00
|
|
|
this._titleDirection = Clutter.TextDirection.LTR;
|
2011-04-06 16:23:49 +00:00
|
|
|
|
|
|
|
// Let the title's text direction control the overall direction
|
|
|
|
// of the notification - in case where different scripts are used
|
|
|
|
// in the notification, this is the right thing for the icon, and
|
|
|
|
// arguably for action buttons as well. Labels other than the title
|
|
|
|
// will be allocated at the available width, so that their alignment
|
|
|
|
// is done correctly automatically.
|
2012-02-14 01:37:28 +00:00
|
|
|
this._table.set_text_direction(this._titleDirection);
|
2011-04-06 16:23:49 +00:00
|
|
|
|
2013-01-30 18:47:55 +00:00
|
|
|
// Unless the notification has custom content, we save this.bannerBodyText
|
2010-08-30 20:03:08 +00:00
|
|
|
// to add it to the content of the notification if the notification is
|
|
|
|
// expandable due to other elements in its content area or due to the banner
|
|
|
|
// not fitting fully in the single-line mode.
|
2013-01-30 18:47:55 +00:00
|
|
|
this.bannerBodyText = this._customContent ? null : banner;
|
|
|
|
this.bannerBodyMarkup = params.bannerMarkup;
|
2010-02-09 16:25:10 +00:00
|
|
|
|
2010-11-23 23:27:47 +00:00
|
|
|
banner = banner ? banner.replace(/\n/g, ' ') : '';
|
|
|
|
|
2010-11-23 23:31:55 +00:00
|
|
|
this._bannerUrlHighlighter.setMarkup(banner, params.bannerMarkup);
|
2010-07-28 15:02:32 +00:00
|
|
|
this._bannerLabel.queue_relayout();
|
2010-04-29 14:54:05 +00:00
|
|
|
|
|
|
|
// Add the bannerBody now if we know for sure we'll need it
|
2013-01-30 18:47:55 +00:00
|
|
|
if (this.bannerBodyText && this.bannerBodyText.indexOf('\n') > -1)
|
2010-04-29 14:54:05 +00:00
|
|
|
this._addBannerBody();
|
2010-08-30 20:03:08 +00:00
|
|
|
|
|
|
|
if (params.body)
|
2010-11-23 23:31:55 +00:00
|
|
|
this.addBody(params.body, params.bodyMarkup);
|
2012-04-13 17:05:51 +00:00
|
|
|
this.updated();
|
2010-02-09 16:25:10 +00:00
|
|
|
},
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2011-03-21 21:43:34 +00:00
|
|
|
setIconVisible: function(visible) {
|
|
|
|
this._icon.visible = visible;
|
|
|
|
},
|
|
|
|
|
2011-03-22 07:40:53 +00:00
|
|
|
enableScrolling: function(enableScrolling) {
|
|
|
|
this._scrollPolicy = enableScrolling ? Gtk.PolicyType.AUTOMATIC : Gtk.PolicyType.NEVER;
|
2011-10-25 00:06:39 +00:00
|
|
|
if (this._scrollArea) {
|
2011-03-22 07:40:53 +00:00
|
|
|
this._scrollArea.vscrollbar_policy = this._scrollPolicy;
|
2011-10-25 00:06:39 +00:00
|
|
|
this._scrollArea.enable_mouse_scrolling = enableScrolling;
|
|
|
|
}
|
2011-03-22 07:40:53 +00:00
|
|
|
},
|
|
|
|
|
2011-03-09 12:56:35 +00:00
|
|
|
_createScrollArea: function() {
|
2011-03-03 20:14:52 +00:00
|
|
|
this._table.add_style_class_name('multi-line-notification');
|
2011-10-12 22:49:04 +00:00
|
|
|
this._scrollArea = new St.ScrollView({ style_class: 'notification-scrollview',
|
2011-03-22 07:40:53 +00:00
|
|
|
vscrollbar_policy: this._scrollPolicy,
|
2013-01-19 18:14:31 +00:00
|
|
|
hscrollbar_policy: Gtk.PolicyType.NEVER,
|
|
|
|
visible: this.expanded });
|
2011-08-29 17:11:24 +00:00
|
|
|
this._table.add(this._scrollArea, { row: 1,
|
|
|
|
col: 2 });
|
|
|
|
this._updateLastColumnSettings();
|
2011-10-12 22:49:04 +00:00
|
|
|
this._contentArea = new St.BoxLayout({ style_class: 'notification-body',
|
2011-03-09 12:56:35 +00:00
|
|
|
vertical: true });
|
|
|
|
this._scrollArea.add_actor(this._contentArea);
|
|
|
|
// If we know the notification will be expandable, we need to add
|
|
|
|
// the banner text to the body as the first element.
|
|
|
|
this._addBannerBody();
|
|
|
|
},
|
|
|
|
|
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
|
2010-11-25 14:29:41 +00:00
|
|
|
addActor: function(actor, style) {
|
2010-02-22 19:23:36 +00:00
|
|
|
if (!this._scrollArea) {
|
2011-03-09 12:56:35 +00:00
|
|
|
this._createScrollArea();
|
2010-02-01 17:10:38 +00:00
|
|
|
}
|
2010-02-01 20:23:49 +00:00
|
|
|
|
2010-11-25 14:29:41 +00:00
|
|
|
this._contentArea.add(actor, style ? style : {});
|
2012-04-13 17:05:51 +00:00
|
|
|
this.updated();
|
2010-02-01 20:41:22 +00:00
|
|
|
},
|
|
|
|
|
2010-02-09 16:25:10 +00:00
|
|
|
// addBody:
|
|
|
|
// @text: the text
|
2010-11-23 23:31:55 +00:00
|
|
|
// @markup: %true if @text contains pango markup
|
2010-11-25 14:29:41 +00:00
|
|
|
// @style: style to use when adding the actor containing the text
|
2010-02-09 16:25:10 +00:00
|
|
|
//
|
|
|
|
// Adds a multi-line label containing @text to the notification.
|
2010-02-22 19:23:36 +00:00
|
|
|
//
|
|
|
|
// Return value: the newly-added label
|
2010-11-25 14:29:41 +00:00
|
|
|
addBody: function(text, markup, style) {
|
2010-11-23 23:31:55 +00:00
|
|
|
let label = new URLHighlighter(text, true, markup);
|
2010-02-09 16:25:10 +00:00
|
|
|
|
2010-11-25 14:29:41 +00:00
|
|
|
this.addActor(label.actor, style);
|
2010-11-23 23:27:47 +00:00
|
|
|
return label.actor;
|
2010-02-09 16:25:10 +00:00
|
|
|
},
|
|
|
|
|
2010-02-12 20:15:03 +00:00
|
|
|
_addBannerBody: function() {
|
2013-01-30 18:47:55 +00:00
|
|
|
if (this.bannerBodyText) {
|
|
|
|
let text = this.bannerBodyText;
|
|
|
|
this.bannerBodyText = null;
|
|
|
|
this.addBody(text, this.bannerBodyMarkup);
|
2010-08-30 20:03:08 +00:00
|
|
|
}
|
2010-02-12 20:15:03 +00:00
|
|
|
},
|
|
|
|
|
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) {
|
|
|
|
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;
|
2010-10-06 21:25:15 +00:00
|
|
|
} else {
|
|
|
|
this._addBannerBody();
|
2010-02-22 19:23:36 +00:00
|
|
|
}
|
|
|
|
this._actionArea = actor;
|
2013-01-19 18:14:31 +00:00
|
|
|
this._actionArea.visible = this.expanded;
|
2010-02-22 19:23:36 +00:00
|
|
|
|
|
|
|
if (!props)
|
|
|
|
props = {};
|
|
|
|
props.row = 2;
|
2011-08-29 17:11:24 +00:00
|
|
|
props.col = 2;
|
2010-02-22 19:23:36 +00:00
|
|
|
|
2011-03-03 20:14:52 +00:00
|
|
|
this._table.add_style_class_name('multi-line-notification');
|
|
|
|
this._table.add(this._actionArea, props);
|
2011-08-29 17:11:24 +00:00
|
|
|
this._updateLastColumnSettings();
|
2012-04-13 17:05:51 +00:00
|
|
|
this.updated();
|
2010-02-22 19:23:36 +00:00
|
|
|
},
|
|
|
|
|
2011-08-29 17:11:24 +00:00
|
|
|
_updateLastColumnSettings: function() {
|
|
|
|
if (this._scrollArea)
|
|
|
|
this._table.child_set(this._scrollArea, { col: this._imageBin ? 2 : 1,
|
|
|
|
col_span: this._imageBin ? 1 : 2 });
|
|
|
|
if (this._actionArea)
|
|
|
|
this._table.child_set(this._actionArea, { col: this._imageBin ? 2 : 1,
|
|
|
|
col_span: this._imageBin ? 1 : 2 });
|
|
|
|
},
|
|
|
|
|
|
|
|
setImage: function(image) {
|
2013-01-19 17:49:55 +00:00
|
|
|
this.unsetImage();
|
|
|
|
|
2012-12-30 16:36:48 +00:00
|
|
|
if (!image)
|
|
|
|
return;
|
2013-01-19 17:49:55 +00:00
|
|
|
|
|
|
|
this._imageBin = new St.Bin({ opacity: 230,
|
2013-01-19 18:14:31 +00:00
|
|
|
child: image,
|
|
|
|
visible: this.expanded });
|
2013-01-19 17:49:55 +00:00
|
|
|
|
2011-08-29 19:18:52 +00:00
|
|
|
this._table.add_style_class_name('multi-line-notification');
|
2011-08-29 17:11:24 +00:00
|
|
|
this._table.add_style_class_name('notification-with-image');
|
2011-10-08 21:55:21 +00:00
|
|
|
this._addBannerBody();
|
2011-08-29 17:11:24 +00:00
|
|
|
this._updateLastColumnSettings();
|
|
|
|
this._table.add(this._imageBin, { row: 1,
|
|
|
|
col: 1,
|
|
|
|
row_span: 2,
|
|
|
|
x_expand: false,
|
|
|
|
y_expand: false,
|
|
|
|
x_fill: false,
|
|
|
|
y_fill: false });
|
|
|
|
},
|
|
|
|
|
|
|
|
unsetImage: function() {
|
|
|
|
if (this._imageBin) {
|
|
|
|
this._table.remove_style_class_name('notification-with-image');
|
|
|
|
this._table.remove_actor(this._imageBin);
|
|
|
|
this._imageBin = null;
|
|
|
|
this._updateLastColumnSettings();
|
2011-08-29 19:18:52 +00:00
|
|
|
if (!this._scrollArea && !this._actionArea)
|
|
|
|
this._table.remove_style_class_name('multi-line-notification');
|
2011-08-29 17:11:24 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
// 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
|
|
|
|
2011-10-12 22:49:04 +00:00
|
|
|
let box = new St.BoxLayout({ style_class: 'notification-actions' });
|
2010-02-22 19:23:36 +00:00
|
|
|
this.setActionArea(box, { x_expand: false,
|
2011-08-29 17:11:24 +00:00
|
|
|
y_expand: false,
|
2010-02-22 19:23:36 +00:00
|
|
|
x_fill: false,
|
2011-08-29 17:11:24 +00:00
|
|
|
y_fill: false,
|
2010-02-22 19:23:36 +00:00
|
|
|
x_align: St.Align.END });
|
|
|
|
this._buttonBox = box;
|
2010-02-01 20:41:22 +00:00
|
|
|
}
|
|
|
|
|
2011-01-20 05:16:47 +00:00
|
|
|
let button = new St.Button({ can_focus: true });
|
2012-05-17 19:05:36 +00:00
|
|
|
button._actionId = id;
|
2010-06-11 21:19:18 +00:00
|
|
|
|
2013-01-19 17:50:49 +00:00
|
|
|
let iconName = strHasSuffix(id, '-symbolic') ? id : id + '-symbolic';
|
|
|
|
if (this._useActionIcons && Gtk.IconTheme.get_default().has_icon(iconName)) {
|
2010-06-11 21:19:18 +00:00
|
|
|
button.add_style_class_name('notification-icon-button');
|
2013-01-19 17:50:49 +00:00
|
|
|
button.child = new St.Icon({ icon_name: iconName });
|
2010-06-11 21:19:18 +00:00
|
|
|
} else {
|
|
|
|
button.add_style_class_name('notification-button');
|
|
|
|
button.label = label;
|
|
|
|
}
|
|
|
|
|
2012-05-30 00:36:45 +00:00
|
|
|
if (this._buttonBox.get_n_children() > 0)
|
2012-08-15 16:22:01 +00:00
|
|
|
global.focus_manager.remove_group(this._buttonBox);
|
2011-01-20 05:16:47 +00:00
|
|
|
|
2010-02-22 19:23:36 +00:00
|
|
|
this._buttonBox.add(button);
|
2012-08-15 16:22:01 +00:00
|
|
|
global.focus_manager.add_group(this._buttonBox);
|
2010-12-16 20:49:47 +00:00
|
|
|
button.connect('clicked', Lang.bind(this, this._onActionInvoked, id));
|
2011-01-20 05:16:47 +00:00
|
|
|
|
2012-04-13 17:05:51 +00:00
|
|
|
this.updated();
|
2010-02-01 17:10:38 +00:00
|
|
|
},
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2012-05-17 19:05:36 +00:00
|
|
|
// setButtonSensitive:
|
|
|
|
// @id: the action ID
|
|
|
|
// @sensitive: whether the button should be sensitive
|
|
|
|
//
|
|
|
|
// If the notification contains a button with action ID @id,
|
|
|
|
// its sensitivity will be set to @sensitive. Insensitive
|
|
|
|
// buttons cannot be clicked.
|
|
|
|
setButtonSensitive: function(id, sensitive) {
|
|
|
|
if (!this._buttonBox)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let button = this._buttonBox.get_children().filter(function(b) {
|
|
|
|
return b._actionId == id;
|
|
|
|
})[0];
|
|
|
|
|
|
|
|
if (!button || button.reactive == sensitive)
|
|
|
|
return;
|
|
|
|
|
|
|
|
button.reactive = sensitive;
|
|
|
|
},
|
|
|
|
|
2011-01-04 09:34:57 +00:00
|
|
|
setUrgency: function(urgency) {
|
|
|
|
this.urgency = urgency;
|
2010-04-28 19:34:27 +00:00
|
|
|
},
|
|
|
|
|
2010-12-16 20:49:47 +00:00
|
|
|
setResident: function(resident) {
|
|
|
|
this.resident = resident;
|
|
|
|
},
|
|
|
|
|
2010-12-15 21:30:50 +00:00
|
|
|
setTransient: function(isTransient) {
|
|
|
|
this.isTransient = isTransient;
|
|
|
|
},
|
|
|
|
|
2012-11-02 17:06:40 +00:00
|
|
|
setForFeedback: function(forFeedback) {
|
|
|
|
this.forFeedback = forFeedback;
|
|
|
|
},
|
|
|
|
|
2010-10-30 06:29:21 +00:00
|
|
|
setUseActionIcons: function(useIcons) {
|
|
|
|
this._useActionIcons = useIcons;
|
|
|
|
},
|
|
|
|
|
2010-04-29 14:54:05 +00:00
|
|
|
_styleChanged: function() {
|
2011-03-03 20:14:52 +00:00
|
|
|
this._spacing = this._table.get_theme_node().get_length('spacing-columns');
|
2010-04-29 14:54:05 +00:00
|
|
|
},
|
|
|
|
|
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
|
|
|
|
2012-07-05 17:47:13 +00:00
|
|
|
if (this._secondaryIcon) {
|
|
|
|
let [secondaryIconMin, secondaryIconNat] = this._secondaryIcon.get_preferred_width(forHeight);
|
|
|
|
|
|
|
|
alloc.min_size = secondaryIconMin + this._spacing + titleMin;
|
|
|
|
alloc.natural_size = secondaryIconNat + this._spacing + titleNat + this._spacing + bannerNat;
|
|
|
|
} else {
|
|
|
|
alloc.min_size = titleMin;
|
|
|
|
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) {
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
|
2011-09-20 19:52:42 +00:00
|
|
|
let [titleMinW, titleNatW] = this._titleLabel.get_preferred_width(-1);
|
|
|
|
let [titleMinH, titleNatH] = this._titleLabel.get_preferred_height(availWidth);
|
|
|
|
let [bannerMinW, bannerNatW] = this._bannerLabel.get_preferred_width(availWidth);
|
|
|
|
|
2012-07-05 17:47:13 +00:00
|
|
|
let rtl = (this._titleDirection == Clutter.TextDirection.RTL);
|
|
|
|
let x = rtl ? availWidth : 0;
|
|
|
|
|
|
|
|
if (this._secondaryIcon) {
|
|
|
|
let [iconMinW, iconNatW] = this._secondaryIcon.get_preferred_width(-1);
|
|
|
|
let [iconMinH, iconNatH] = this._secondaryIcon.get_preferred_height(availWidth);
|
|
|
|
|
|
|
|
let secondaryIconBox = new Clutter.ActorBox();
|
|
|
|
let secondaryIconBoxW = Math.min(iconNatW, availWidth);
|
|
|
|
|
|
|
|
// allocate secondary icon box
|
|
|
|
if (rtl) {
|
|
|
|
secondaryIconBox.x1 = x - secondaryIconBoxW;
|
|
|
|
secondaryIconBox.x2 = x;
|
|
|
|
x = x - (secondaryIconBoxW + this._spacing);
|
|
|
|
} else {
|
|
|
|
secondaryIconBox.x1 = x;
|
|
|
|
secondaryIconBox.x2 = x + secondaryIconBoxW;
|
|
|
|
x = x + secondaryIconBoxW + this._spacing;
|
|
|
|
}
|
|
|
|
secondaryIconBox.y1 = 0;
|
|
|
|
// Using titleNatH ensures that the secondary icon is centered vertically
|
|
|
|
secondaryIconBox.y2 = titleNatH;
|
|
|
|
|
|
|
|
availWidth = availWidth - (secondaryIconBoxW + this._spacing);
|
|
|
|
this._secondaryIcon.allocate(secondaryIconBox, flags);
|
|
|
|
}
|
|
|
|
|
2010-02-01 17:10:38 +00:00
|
|
|
let titleBox = new Clutter.ActorBox();
|
2011-04-06 16:23:49 +00:00
|
|
|
let titleBoxW = Math.min(titleNatW, availWidth);
|
2012-07-05 17:47:13 +00:00
|
|
|
if (rtl) {
|
2011-04-06 16:23:49 +00:00
|
|
|
titleBox.x1 = availWidth - titleBoxW;
|
|
|
|
titleBox.x2 = availWidth;
|
|
|
|
} else {
|
2012-07-05 17:47:13 +00:00
|
|
|
titleBox.x1 = x;
|
|
|
|
titleBox.x2 = titleBox.x1 + titleBoxW;
|
2011-04-06 16:23:49 +00:00
|
|
|
}
|
|
|
|
titleBox.y1 = 0;
|
2010-02-01 17:10:38 +00:00
|
|
|
titleBox.y2 = titleNatH;
|
2010-02-09 16:20:51 +00:00
|
|
|
this._titleLabel.allocate(titleBox, flags);
|
2010-08-29 05:34:27 +00:00
|
|
|
this._titleFitsInBannerMode = (titleNatW <= availWidth);
|
2010-02-01 17:10:38 +00:00
|
|
|
|
2010-08-26 20:05:27 +00:00
|
|
|
let bannerFits = true;
|
2011-04-06 16:23:49 +00:00
|
|
|
if (titleBoxW + this._spacing > availWidth) {
|
2010-09-10 17:35:11 +00:00
|
|
|
this._bannerLabel.opacity = 0;
|
2010-08-26 20:05:27 +00:00
|
|
|
bannerFits = false;
|
2010-02-03 20:15:12 +00:00
|
|
|
} else {
|
|
|
|
let bannerBox = new Clutter.ActorBox();
|
2011-04-06 16:23:49 +00:00
|
|
|
|
2012-07-05 17:47:13 +00:00
|
|
|
if (rtl) {
|
2011-04-06 16:23:49 +00:00
|
|
|
bannerBox.x1 = 0;
|
|
|
|
bannerBox.x2 = titleBox.x1 - this._spacing;
|
|
|
|
|
|
|
|
bannerFits = (bannerBox.x2 - bannerNatW >= 0);
|
|
|
|
} else {
|
|
|
|
bannerBox.x1 = titleBox.x2 + this._spacing;
|
|
|
|
bannerBox.x2 = availWidth;
|
|
|
|
|
|
|
|
bannerFits = (bannerBox.x1 + bannerNatW <= availWidth);
|
|
|
|
}
|
2010-02-03 20:15:12 +00:00
|
|
|
bannerBox.y1 = 0;
|
|
|
|
bannerBox.y2 = titleNatH;
|
2010-02-09 16:20:51 +00:00
|
|
|
this._bannerLabel.allocate(bannerBox, flags);
|
2010-09-10 17:35:11 +00:00
|
|
|
|
|
|
|
// Make _bannerLabel visible if the entire notification
|
|
|
|
// fits on one line, or if the notification is currently
|
|
|
|
// unexpanded and only showing one line anyway.
|
2011-03-03 20:14:52 +00:00
|
|
|
if (!this.expanded || (bannerFits && this._table.row_count == 1))
|
2010-09-10 17:35:11 +00:00
|
|
|
this._bannerLabel.opacity = 255;
|
2010-02-09 16:25:10 +00:00
|
|
|
}
|
2010-08-26 20:05:27 +00:00
|
|
|
|
2010-08-30 20:03:08 +00:00
|
|
|
// If the banner doesn't fully fit in the banner box, we possibly need to add the
|
|
|
|
// banner to the body. We can't do that from here though since that will force a
|
|
|
|
// relayout, so we add it to the main loop.
|
2011-03-27 18:57:34 +00:00
|
|
|
if (!bannerFits && this._canExpandContent())
|
|
|
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
|
|
|
|
Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
if (this._canExpandContent()) {
|
2010-08-26 20:05:27 +00:00
|
|
|
this._addBannerBody();
|
2011-03-27 18:57:34 +00:00
|
|
|
this._table.add_style_class_name('multi-line-notification');
|
2012-04-13 17:05:51 +00:00
|
|
|
this.updated();
|
2011-03-27 18:57:34 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
|
|
|
_canExpandContent: function() {
|
2013-01-30 18:47:55 +00:00
|
|
|
return this.bannerBodyText ||
|
2011-03-27 18:57:34 +00:00
|
|
|
(!this._titleFitsInBannerMode && !this._table.has_style_class_name('multi-line-notification'));
|
2010-02-01 17:10:38 +00:00
|
|
|
},
|
|
|
|
|
2012-04-13 17:05:51 +00:00
|
|
|
updated: function() {
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
if (this.expanded)
|
|
|
|
this.expand(false);
|
|
|
|
},
|
|
|
|
|
|
|
|
expand: function(animate) {
|
|
|
|
this.expanded = true;
|
2012-08-26 18:27:06 +00:00
|
|
|
this.actor.remove_style_class_name('notification-unexpanded');
|
|
|
|
|
2013-01-19 18:14:31 +00:00
|
|
|
// Show additional content that we keep hidden in banner mode
|
|
|
|
if (this._imageBin)
|
|
|
|
this._imageBin.show();
|
|
|
|
if (this._actionArea)
|
|
|
|
this._actionArea.show();
|
|
|
|
if (this._scrollArea)
|
|
|
|
this._scrollArea.show();
|
|
|
|
|
2010-08-29 05:34:27 +00:00
|
|
|
// The banner is never shown when the title did not fit, so this
|
|
|
|
// can be an if-else statement.
|
|
|
|
if (!this._titleFitsInBannerMode) {
|
|
|
|
// Remove ellipsization from the title label and make it wrap so that
|
|
|
|
// we show the full title when the notification is expanded.
|
|
|
|
this._titleLabel.clutter_text.line_wrap = true;
|
|
|
|
this._titleLabel.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
|
|
|
|
this._titleLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
|
2011-03-03 20:14:52 +00:00
|
|
|
} else if (this._table.row_count > 1 && this._bannerLabel.opacity != 0) {
|
2010-08-29 05:34:27 +00:00
|
|
|
// We always hide the banner if the notification has additional content.
|
|
|
|
//
|
|
|
|
// We don't need to wrap the banner that doesn't fit the way we wrap the
|
|
|
|
// title that doesn't fit because we won't have a notification with
|
|
|
|
// row_count=1 that has a banner that doesn't fully fit. We'll either add
|
|
|
|
// that banner to the content of the notification in _bannerBoxAllocate()
|
|
|
|
// or the notification will have custom content.
|
|
|
|
if (animate)
|
|
|
|
Tweener.addTween(this._bannerLabel,
|
|
|
|
{ opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad' });
|
|
|
|
else
|
|
|
|
this._bannerLabel.opacity = 0;
|
|
|
|
}
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this.emit('expanded');
|
2010-02-01 17:10:38 +00:00
|
|
|
},
|
|
|
|
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
collapseCompleted: function() {
|
2011-03-03 00:47:58 +00:00
|
|
|
if (this._destroyed)
|
|
|
|
return;
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this.expanded = false;
|
2013-01-19 18:14:31 +00:00
|
|
|
|
|
|
|
// Hide additional content that we keep hidden in banner mode
|
|
|
|
if (this._imageBin)
|
|
|
|
this._imageBin.hide();
|
|
|
|
if (this._actionArea)
|
|
|
|
this._actionArea.hide();
|
|
|
|
if (this._scrollArea)
|
|
|
|
this._scrollArea.hide();
|
|
|
|
|
2010-08-29 05:34:27 +00:00
|
|
|
// Make sure we don't line wrap the title, and ellipsize it instead.
|
|
|
|
this._titleLabel.clutter_text.line_wrap = false;
|
|
|
|
this._titleLabel.clutter_text.ellipsize = Pango.EllipsizeMode.END;
|
2013-01-19 18:14:31 +00:00
|
|
|
|
2010-08-29 05:34:27 +00:00
|
|
|
// Restore banner opacity in case the notification is shown in the
|
|
|
|
// banner mode again on update.
|
2010-08-29 04:04:33 +00:00
|
|
|
this._bannerLabel.opacity = 255;
|
2013-01-19 18:14:31 +00:00
|
|
|
|
2012-08-26 18:27:06 +00:00
|
|
|
// Restore height requisition
|
|
|
|
this.actor.add_style_class_name('notification-unexpanded');
|
2011-05-27 09:08:32 +00:00
|
|
|
this.emit('collapsed');
|
2010-02-22 22:19:32 +00:00
|
|
|
},
|
|
|
|
|
2011-02-08 07:06:42 +00:00
|
|
|
_onActionInvoked: function(actor, mouseButtonClicked, id) {
|
2010-12-16 20:49:47 +00:00
|
|
|
this.emit('action-invoked', id);
|
|
|
|
if (!this.resident) {
|
|
|
|
// We don't hide a resident notification when the user invokes one of its actions,
|
|
|
|
// because it is common for such notifications to update themselves with new
|
|
|
|
// information based on the action. We'd like to display the updated information
|
|
|
|
// in place, rather than pop-up a new notification.
|
|
|
|
this.emit('done-displaying');
|
|
|
|
this.destroy();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onClicked: function() {
|
|
|
|
this.emit('clicked');
|
|
|
|
// We hide all types of notifications once the user clicks on them because the common
|
|
|
|
// outcome of clicking should be the relevant window being brought forward and the user's
|
|
|
|
// attention switching to the window.
|
|
|
|
this.emit('done-displaying');
|
|
|
|
if (!this.resident)
|
|
|
|
this.destroy();
|
|
|
|
},
|
|
|
|
|
2011-03-03 00:47:58 +00:00
|
|
|
_onDestroy: function() {
|
2011-01-30 23:33:49 +00:00
|
|
|
if (this._destroyed)
|
|
|
|
return;
|
|
|
|
this._destroyed = true;
|
2011-03-03 00:47:58 +00:00
|
|
|
if (!this._destroyedReason)
|
|
|
|
this._destroyedReason = NotificationDestroyedReason.DISMISSED;
|
|
|
|
this.emit('destroy', this._destroyedReason);
|
|
|
|
},
|
|
|
|
|
|
|
|
destroy: function(reason) {
|
|
|
|
this._destroyedReason = reason;
|
|
|
|
this.actor.destroy();
|
2011-03-21 21:43:34 +00:00
|
|
|
this.actor._delegate = null;
|
2010-01-13 20:05:20 +00:00
|
|
|
}
|
2011-11-20 15:12:02 +00:00
|
|
|
});
|
2010-02-01 20:41:22 +00:00
|
|
|
Signals.addSignalMethods(Notification.prototype);
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2012-07-19 13:05:17 +00:00
|
|
|
const SourceActor = new Lang.Class({
|
|
|
|
Name: 'SourceActor',
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2012-07-19 13:05:17 +00:00
|
|
|
_init: function(source, size) {
|
|
|
|
this._source = source;
|
2012-07-23 04:07:51 +00:00
|
|
|
this._size = size;
|
2011-06-24 19:46:55 +00:00
|
|
|
|
|
|
|
this.actor = new Shell.GenericContainer();
|
|
|
|
this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
|
|
this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
|
|
this.actor.connect('allocate', Lang.bind(this, this._allocate));
|
2012-07-19 13:05:17 +00:00
|
|
|
this.actor.connect('destroy', Lang.bind(this, function() {
|
|
|
|
this._actorDestroyed = true;
|
|
|
|
}));
|
2011-07-12 23:18:58 +00:00
|
|
|
this._actorDestroyed = false;
|
2011-06-24 19:46:55 +00:00
|
|
|
|
2012-09-10 15:41:30 +00:00
|
|
|
this._counterLabel = new St.Label( {x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
x_expand: true,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_expand: true });
|
|
|
|
|
2011-06-24 19:46:55 +00:00
|
|
|
this._counterBin = new St.Bin({ style_class: 'summary-source-counter',
|
2012-09-10 15:41:30 +00:00
|
|
|
child: this._counterLabel,
|
|
|
|
layout_manager: new Clutter.BinLayout() });
|
2011-06-24 19:46:55 +00:00
|
|
|
this._counterBin.hide();
|
|
|
|
|
2012-09-10 15:41:30 +00:00
|
|
|
this._counterBin.connect('style-changed', Lang.bind(this, function() {
|
|
|
|
let themeNode = this._counterBin.get_theme_node();
|
|
|
|
this._counterBin.translation_x = themeNode.get_length('-shell-counter-overlap-x');
|
|
|
|
this._counterBin.translation_y = themeNode.get_length('-shell-counter-overlap-y');
|
|
|
|
}));
|
|
|
|
|
2012-07-19 13:05:17 +00:00
|
|
|
this._iconBin = new St.Bin({ width: size,
|
|
|
|
height: size,
|
2011-03-19 17:51:34 +00:00
|
|
|
x_fill: true,
|
|
|
|
y_fill: true });
|
2011-06-24 19:46:55 +00:00
|
|
|
|
|
|
|
this.actor.add_actor(this._iconBin);
|
|
|
|
this.actor.add_actor(this._counterBin);
|
|
|
|
|
2012-08-06 15:28:55 +00:00
|
|
|
this._source.connect('count-updated', Lang.bind(this, this._updateCount));
|
2012-07-19 13:05:17 +00:00
|
|
|
this._updateCount();
|
2012-07-23 04:07:51 +00:00
|
|
|
|
|
|
|
this._source.connect('icon-updated', Lang.bind(this, this._updateIcon));
|
|
|
|
this._updateIcon();
|
2012-07-19 13:05:17 +00:00
|
|
|
},
|
2011-10-07 22:31:26 +00:00
|
|
|
|
2012-07-19 13:05:17 +00:00
|
|
|
setIcon: function(icon) {
|
|
|
|
this._iconBin.child = icon;
|
2012-07-23 04:07:51 +00:00
|
|
|
this._iconSet = true;
|
2010-12-15 21:30:50 +00:00
|
|
|
},
|
|
|
|
|
2011-06-24 19:46:55 +00:00
|
|
|
_getPreferredWidth: function (actor, forHeight, alloc) {
|
|
|
|
let [min, nat] = this._iconBin.get_preferred_width(forHeight);
|
|
|
|
alloc.min_size = min; alloc.nat_size = nat;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function (actor, forWidth, alloc) {
|
|
|
|
let [min, nat] = this._iconBin.get_preferred_height(forWidth);
|
|
|
|
alloc.min_size = min; alloc.nat_size = nat;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function(actor, box, flags) {
|
|
|
|
// the iconBin should fill our entire box
|
|
|
|
this._iconBin.allocate(box, flags);
|
|
|
|
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
|
|
|
|
let [minWidth, minHeight, naturalWidth, naturalHeight] = this._counterBin.get_preferred_size();
|
2012-02-14 01:37:28 +00:00
|
|
|
let direction = this.actor.get_text_direction();
|
2011-06-24 19:46:55 +00:00
|
|
|
|
2012-02-14 01:37:28 +00:00
|
|
|
if (direction == Clutter.TextDirection.LTR) {
|
2011-06-24 19:46:55 +00:00
|
|
|
// allocate on the right in LTR
|
|
|
|
childBox.x1 = box.x2 - naturalWidth;
|
|
|
|
childBox.x2 = box.x2;
|
|
|
|
} else {
|
|
|
|
// allocate on the left in RTL
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = naturalWidth;
|
|
|
|
}
|
|
|
|
|
|
|
|
childBox.y1 = box.y2 - naturalHeight;
|
|
|
|
childBox.y2 = box.y2;
|
|
|
|
|
|
|
|
this._counterBin.allocate(childBox, flags);
|
|
|
|
},
|
|
|
|
|
2012-07-23 04:07:51 +00:00
|
|
|
_updateIcon: function() {
|
|
|
|
if (this._actorDestroyed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (!this._iconSet)
|
|
|
|
this._iconBin.child = this._source.createIcon(this._size);
|
|
|
|
},
|
|
|
|
|
2012-07-19 13:05:17 +00:00
|
|
|
_updateCount: function() {
|
|
|
|
if (this._actorDestroyed)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._counterBin.visible = this._source.countVisible;
|
2012-09-10 15:41:30 +00:00
|
|
|
|
|
|
|
let text;
|
|
|
|
if (this._source.count < 100)
|
|
|
|
text = this._source.count.toString();
|
|
|
|
else
|
|
|
|
text = String.fromCharCode(0x22EF); // midline horizontal ellipsis
|
|
|
|
|
|
|
|
this._counterLabel.set_text(text);
|
2012-07-23 04:07:51 +00:00
|
|
|
}
|
2012-07-19 13:05:17 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
const Source = new Lang.Class({
|
|
|
|
Name: 'MessageTraySource',
|
|
|
|
|
2012-08-12 10:35:41 +00:00
|
|
|
SOURCE_ICON_SIZE: 48,
|
2012-07-19 13:05:17 +00:00
|
|
|
|
2012-05-30 13:58:37 +00:00
|
|
|
_init: function(title, iconName) {
|
2012-07-19 13:05:17 +00:00
|
|
|
this.title = title;
|
|
|
|
this.iconName = iconName;
|
|
|
|
|
|
|
|
this.isTransient = false;
|
|
|
|
this.isChat = false;
|
|
|
|
this.isMuted = false;
|
2012-09-21 15:21:25 +00:00
|
|
|
this.keepTrayOnSummaryClick = false;
|
2012-07-19 13:05:17 +00:00
|
|
|
|
|
|
|
this.notifications = [];
|
2012-10-16 15:08:54 +00:00
|
|
|
|
|
|
|
this.policy = this._createPolicy();
|
2012-07-19 13:05:17 +00:00
|
|
|
},
|
|
|
|
|
2012-08-06 15:28:55 +00:00
|
|
|
get count() {
|
|
|
|
return this.notifications.length;
|
|
|
|
},
|
2011-06-24 19:46:55 +00:00
|
|
|
|
2012-08-06 15:28:55 +00:00
|
|
|
get unseenCount() {
|
|
|
|
return this.notifications.filter(function(n) { return !n.acknowledged; }).length;
|
2011-06-24 19:46:55 +00:00
|
|
|
},
|
|
|
|
|
2012-08-06 15:28:55 +00:00
|
|
|
get countVisible() {
|
|
|
|
return this.count > 1;
|
|
|
|
},
|
|
|
|
|
|
|
|
countUpdated: function() {
|
|
|
|
this.emit('count-updated');
|
2011-06-24 19:47:24 +00:00
|
|
|
},
|
|
|
|
|
2012-10-16 15:08:54 +00:00
|
|
|
_createPolicy: function() {
|
|
|
|
return new NotificationPolicy();
|
|
|
|
},
|
|
|
|
|
2012-09-05 15:32:45 +00:00
|
|
|
buildRightClickMenu: function() {
|
|
|
|
let item;
|
|
|
|
let rightClickMenu = new St.BoxLayout({ name: 'summary-right-click-menu',
|
|
|
|
vertical: true });
|
|
|
|
|
|
|
|
item = new PopupMenu.PopupMenuItem(_("Open"));
|
|
|
|
item.connect('activate', Lang.bind(this, function() {
|
|
|
|
this.open();
|
2012-11-30 15:14:50 +00:00
|
|
|
this.emit('done-displaying-content', true);
|
2012-09-05 15:32:45 +00:00
|
|
|
}));
|
|
|
|
rightClickMenu.add(item.actor);
|
|
|
|
|
|
|
|
item = new PopupMenu.PopupMenuItem(_("Remove"));
|
|
|
|
item.connect('activate', Lang.bind(this, function() {
|
|
|
|
this.destroy();
|
2012-11-30 15:14:50 +00:00
|
|
|
this.emit('done-displaying-content', false);
|
2012-09-05 15:32:45 +00:00
|
|
|
}));
|
|
|
|
rightClickMenu.add(item.actor);
|
|
|
|
return rightClickMenu;
|
|
|
|
},
|
|
|
|
|
2010-12-15 21:30:50 +00:00
|
|
|
setTransient: function(isTransient) {
|
|
|
|
this.isTransient = isTransient;
|
2010-08-05 17:09:27 +00:00
|
|
|
},
|
|
|
|
|
2011-06-08 06:27:57 +00:00
|
|
|
setTitle: function(newTitle) {
|
|
|
|
this.title = newTitle;
|
|
|
|
this.emit('title-changed');
|
|
|
|
},
|
|
|
|
|
2011-11-03 14:48:09 +00:00
|
|
|
setMuted: function(muted) {
|
|
|
|
if (!this.isChat || this.isMuted == muted)
|
|
|
|
return;
|
|
|
|
this.isMuted = muted;
|
|
|
|
this.emit('muted-changed');
|
|
|
|
},
|
|
|
|
|
2012-07-19 13:05:17 +00:00
|
|
|
// Called to create a new icon actor.
|
2011-10-07 22:31:26 +00:00
|
|
|
// Provides a sane default implementation, override if you need
|
|
|
|
// something more fancy.
|
2012-07-19 13:05:17 +00:00
|
|
|
createIcon: function(size) {
|
2012-09-15 06:10:15 +00:00
|
|
|
return new St.Icon({ gicon: this.getIcon(),
|
2012-07-19 13:05:17 +00:00
|
|
|
icon_size: size });
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2012-09-15 06:10:15 +00:00
|
|
|
getIcon: function() {
|
|
|
|
return new Gio.ThemedIcon({ name: this.iconName });
|
|
|
|
},
|
|
|
|
|
2012-07-23 04:07:51 +00:00
|
|
|
_ensureMainIcon: function() {
|
2012-08-16 19:50:35 +00:00
|
|
|
if (this._mainIcon)
|
2012-07-23 04:07:51 +00:00
|
|
|
return;
|
2012-08-16 19:50:35 +00:00
|
|
|
|
2012-08-12 10:35:41 +00:00
|
|
|
this._mainIcon = new SourceActor(this, this.SOURCE_ICON_SIZE);
|
2012-08-16 19:50:35 +00:00
|
|
|
},
|
|
|
|
|
2012-07-19 13:05:17 +00:00
|
|
|
// Unlike createIcon, this always returns the same actor;
|
2010-08-05 17:09:27 +00:00
|
|
|
// there is only one summary icon actor for a Source.
|
|
|
|
getSummaryIcon: function() {
|
2012-08-16 19:50:35 +00:00
|
|
|
this._ensureMainIcon();
|
|
|
|
return this._mainIcon.actor;
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2011-02-28 18:13:32 +00:00
|
|
|
pushNotification: function(notification) {
|
2011-03-21 21:43:34 +00:00
|
|
|
if (this.notifications.indexOf(notification) < 0) {
|
|
|
|
this.notifications.push(notification);
|
|
|
|
this.emit('notification-added', notification);
|
MessageTray: untangle click notifications
Previously, when you clicked on a notification, it would call
this.source.clicked(), which would emit a 'clicked' signal on the
source, and then various other stuff would happen from there. This
used to make a little bit of sense, when clicking on a notification
was supposed to do the same thing as clicking on its source, but makes
less sense now, when clicking on the source itself *doesn't* call
source.clicked()...
Change it so that when you click on a notification, the notification
emits 'clicked' itself, and the source notices that and calls its
notificationClicked() method, and the various source subclasses do
what they need to do with that, and Source no longer has a clicked
method/signal.
https://bugzilla.gnome.org/show_bug.cgi?id=631042
2010-09-30 20:41:38 +00:00
|
|
|
}
|
2010-02-25 19:42:18 +00:00
|
|
|
|
2011-03-21 21:43:34 +00:00
|
|
|
notification.connect('clicked', Lang.bind(this, this.open));
|
|
|
|
notification.connect('destroy', Lang.bind(this,
|
2010-02-25 19:42:18 +00:00
|
|
|
function () {
|
2011-03-21 21:43:34 +00:00
|
|
|
let index = this.notifications.indexOf(notification);
|
|
|
|
if (index < 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this.notifications.splice(index, 1);
|
|
|
|
if (this.notifications.length == 0)
|
|
|
|
this._lastNotificationRemoved();
|
2011-06-24 19:47:24 +00:00
|
|
|
|
2012-08-06 15:28:55 +00:00
|
|
|
this.countUpdated();
|
2010-02-25 19:42:18 +00:00
|
|
|
}));
|
2011-06-24 19:47:24 +00:00
|
|
|
|
2012-08-06 15:28:55 +00:00
|
|
|
this.countUpdated();
|
2011-02-28 18:13:32 +00:00
|
|
|
},
|
2010-02-25 19:42:18 +00:00
|
|
|
|
2011-02-28 18:13:32 +00:00
|
|
|
notify: function(notification) {
|
2012-08-06 15:28:55 +00:00
|
|
|
notification.acknowledged = false;
|
2011-02-28 18:13:32 +00:00
|
|
|
this.pushNotification(notification);
|
2012-10-16 15:08:54 +00:00
|
|
|
|
|
|
|
if (!this.isMuted && this.policy.showBanners)
|
|
|
|
this.emit('notify', notification);
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2011-03-21 21:43:34 +00:00
|
|
|
destroy: function(reason) {
|
2012-10-16 15:08:54 +00:00
|
|
|
this.policy.destroy();
|
2011-03-21 21:43:34 +00:00
|
|
|
this.emit('destroy', reason);
|
2010-08-05 17:09:27 +00:00
|
|
|
},
|
|
|
|
|
2011-01-13 20:04:37 +00:00
|
|
|
// A subclass can redefine this to "steal" clicks from the
|
|
|
|
// summaryitem; Use Clutter.get_current_event() to get the
|
|
|
|
// details, return true to prevent the default handling from
|
|
|
|
// ocurring.
|
|
|
|
handleSummaryClick: function() {
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2012-07-23 04:07:51 +00:00
|
|
|
iconUpdated: function() {
|
|
|
|
this.emit('icon-updated');
|
|
|
|
},
|
|
|
|
|
2010-08-05 17:09:27 +00:00
|
|
|
//// Protected methods ////
|
|
|
|
_setSummaryIcon: function(icon) {
|
2012-07-23 04:07:51 +00:00
|
|
|
this._ensureMainIcon();
|
|
|
|
this._mainIcon.setIcon(icon);
|
|
|
|
this.iconUpdated();
|
MessageTray: untangle click notifications
Previously, when you clicked on a notification, it would call
this.source.clicked(), which would emit a 'clicked' signal on the
source, and then various other stuff would happen from there. This
used to make a little bit of sense, when clicking on a notification
was supposed to do the same thing as clicking on its source, but makes
less sense now, when clicking on the source itself *doesn't* call
source.clicked()...
Change it so that when you click on a notification, the notification
emits 'clicked' itself, and the source notices that and calls its
notificationClicked() method, and the various source subclasses do
what they need to do with that, and Source no longer has a clicked
method/signal.
https://bugzilla.gnome.org/show_bug.cgi?id=631042
2010-09-30 20:41:38 +00:00
|
|
|
},
|
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
open: function(notification) {
|
2011-10-07 22:31:26 +00:00
|
|
|
this.emit('opened', notification);
|
2010-12-16 20:49:47 +00:00
|
|
|
},
|
|
|
|
|
2011-03-21 21:43:34 +00:00
|
|
|
destroyNonResidentNotifications: function() {
|
|
|
|
for (let i = this.notifications.length - 1; i >= 0; i--)
|
|
|
|
if (!this.notifications[i].resident)
|
|
|
|
this.notifications[i].destroy();
|
2011-06-24 19:47:24 +00:00
|
|
|
|
2012-08-06 15:28:55 +00:00
|
|
|
this.countUpdated();
|
2011-03-21 21:43:34 +00:00
|
|
|
},
|
|
|
|
|
2010-12-16 20:49:47 +00:00
|
|
|
// Default implementation is to destroy this source, but subclasses can override
|
2011-03-21 21:43:34 +00:00
|
|
|
_lastNotificationRemoved: function() {
|
2010-12-16 20:49:47 +00:00
|
|
|
this.destroy();
|
2012-05-22 22:02:00 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
hasResidentNotification: function() {
|
|
|
|
return this.notifications.some(function(n) { return n.resident; });
|
2013-01-30 18:47:55 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getMusicNotification: function() {
|
|
|
|
for (let i = 0; i < this.notifications.length; i++) {
|
|
|
|
if (this.notifications[i].isMusic)
|
|
|
|
return this.notifications[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
},
|
2011-11-20 15:12:02 +00:00
|
|
|
});
|
2010-01-13 20:05:20 +00:00
|
|
|
Signals.addSignalMethods(Source.prototype);
|
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const SummaryItem = new Lang.Class({
|
|
|
|
Name: 'SummaryItem',
|
2010-06-23 19:20:39 +00:00
|
|
|
|
2010-09-24 20:04:56 +00:00
|
|
|
_init: function(source) {
|
2010-06-23 19:20:39 +00:00
|
|
|
this.source = source;
|
2011-03-21 21:43:34 +00:00
|
|
|
this.source.connect('notification-added', Lang.bind(this, this._notificationAddedToSource));
|
|
|
|
|
2010-06-23 19:20:39 +00:00
|
|
|
this.actor = new St.Button({ style_class: 'summary-source-button',
|
2011-03-22 01:22:02 +00:00
|
|
|
y_fill: true,
|
2010-06-23 19:20:39 +00:00
|
|
|
reactive: true,
|
2011-01-13 20:04:37 +00:00
|
|
|
button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO | St.ButtonMask.THREE,
|
2012-08-14 18:53:06 +00:00
|
|
|
can_focus: true,
|
2010-06-23 19:20:39 +00:00
|
|
|
track_hover: true });
|
2012-08-24 14:52:43 +00:00
|
|
|
this.actor.label_actor = new St.Label({ text: source.title });
|
2012-09-14 23:03:19 +00:00
|
|
|
this.actor.connect('key-press-event', Lang.bind(this, this._onKeyPress));
|
2010-09-24 20:04:56 +00:00
|
|
|
this._sourceBox = new St.BoxLayout({ style_class: 'summary-source' });
|
2010-06-23 19:20:39 +00:00
|
|
|
|
2010-08-05 17:09:27 +00:00
|
|
|
this._sourceIcon = source.getSummaryIcon();
|
2011-03-22 01:22:02 +00:00
|
|
|
this._sourceBox.add(this._sourceIcon, { y_fill: false });
|
2010-06-23 19:20:39 +00:00
|
|
|
this.actor.child = this._sourceBox;
|
2011-03-21 21:43:34 +00:00
|
|
|
|
2012-08-20 19:51:52 +00:00
|
|
|
this.notificationStackWidget = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
|
|
|
|
2012-08-06 18:44:07 +00:00
|
|
|
this.notificationStackView = new St.ScrollView({ style_class: source.isChat ? '' : 'summary-notification-stack-scrollview',
|
2011-03-22 07:40:53 +00:00
|
|
|
vscrollbar_policy: source.isChat ? Gtk.PolicyType.NEVER : Gtk.PolicyType.AUTOMATIC,
|
2012-08-06 18:44:07 +00:00
|
|
|
hscrollbar_policy: Gtk.PolicyType.NEVER });
|
|
|
|
this.notificationStackView.add_style_class_name('vfade');
|
|
|
|
this.notificationStack = new St.BoxLayout({ style_class: 'summary-notification-stack',
|
|
|
|
vertical: true });
|
2011-03-22 07:40:53 +00:00
|
|
|
this.notificationStackView.add_actor(this.notificationStack);
|
2012-08-18 23:34:49 +00:00
|
|
|
this.notificationStackWidget.add_actor(this.notificationStackView);
|
2012-09-19 03:36:05 +00:00
|
|
|
|
|
|
|
this.closeButton = makeCloseButton();
|
|
|
|
this.notificationStackWidget.add_actor(this.closeButton);
|
2011-09-08 04:40:58 +00:00
|
|
|
this._stackedNotifications = [];
|
2011-03-21 21:43:34 +00:00
|
|
|
|
2011-03-22 07:40:53 +00:00
|
|
|
this._oldMaxScrollAdjustment = 0;
|
|
|
|
|
|
|
|
this.notificationStackView.vscroll.adjustment.connect('changed', Lang.bind(this, function(adjustment) {
|
|
|
|
let currentValue = adjustment.value + adjustment.page_size;
|
|
|
|
if (currentValue == this._oldMaxScrollAdjustment)
|
|
|
|
this.scrollTo(St.Side.BOTTOM);
|
|
|
|
this._oldMaxScrollAdjustment = adjustment.upper;
|
|
|
|
}));
|
|
|
|
|
2012-09-05 15:32:45 +00:00
|
|
|
this.rightClickMenu = source.buildRightClickMenu();
|
|
|
|
if (this.rightClickMenu)
|
|
|
|
global.focus_manager.add_group(this.rightClickMenu);
|
2010-06-23 19:20:39 +00:00
|
|
|
},
|
|
|
|
|
2012-10-16 15:08:54 +00:00
|
|
|
destroy: function() {
|
|
|
|
// remove the actor from the summary item so it doesn't get destroyed
|
|
|
|
// with us
|
|
|
|
this._sourceBox.remove_actor(this._sourceIcon);
|
|
|
|
|
|
|
|
this.actor.destroy();
|
|
|
|
},
|
|
|
|
|
2012-09-14 23:03:19 +00:00
|
|
|
_onKeyPress: function(actor, event) {
|
|
|
|
if (event.get_key_symbol() == Clutter.KEY_Up) {
|
|
|
|
actor.emit('clicked', 1);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2011-03-21 21:43:34 +00:00
|
|
|
prepareNotificationStackForShowing: function() {
|
2012-05-30 00:36:45 +00:00
|
|
|
if (this.notificationStack.get_n_children() > 0)
|
2011-03-21 21:43:34 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
for (let i = 0; i < this.source.notifications.length; i++) {
|
|
|
|
this._appendNotificationToStack(this.source.notifications[i]);
|
|
|
|
}
|
2012-08-18 23:34:49 +00:00
|
|
|
|
|
|
|
this.scrollTo(St.Side.BOTTOM);
|
2011-03-21 21:43:34 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
doneShowingNotificationStack: function() {
|
2011-09-08 04:40:58 +00:00
|
|
|
for (let i = 0; i < this._stackedNotifications.length; i++) {
|
|
|
|
let stackedNotification = this._stackedNotifications[i];
|
|
|
|
let notification = stackedNotification.notification;
|
|
|
|
notification.collapseCompleted();
|
|
|
|
notification.disconnect(stackedNotification.notificationExpandedId);
|
|
|
|
notification.disconnect(stackedNotification.notificationDoneDisplayingId);
|
|
|
|
notification.disconnect(stackedNotification.notificationDestroyedId);
|
|
|
|
if (notification.actor.get_parent() == this.notificationStack)
|
|
|
|
this.notificationStack.remove_actor(notification.actor);
|
|
|
|
notification.setIconVisible(true);
|
|
|
|
notification.enableScrolling(true);
|
2011-03-21 21:43:34 +00:00
|
|
|
}
|
2011-09-08 04:40:58 +00:00
|
|
|
this._stackedNotifications = [];
|
2011-03-21 21:43:34 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_notificationAddedToSource: function(source, notification) {
|
|
|
|
if (this.notificationStack.mapped)
|
|
|
|
this._appendNotificationToStack(notification);
|
|
|
|
},
|
|
|
|
|
|
|
|
_appendNotificationToStack: function(notification) {
|
2011-09-08 04:40:58 +00:00
|
|
|
let stackedNotification = {};
|
|
|
|
stackedNotification.notification = notification;
|
|
|
|
stackedNotification.notificationExpandedId = notification.connect('expanded', Lang.bind(this, this._contentUpdated));
|
|
|
|
stackedNotification.notificationDoneDisplayingId = notification.connect('done-displaying', Lang.bind(this, this._notificationDoneDisplaying));
|
|
|
|
stackedNotification.notificationDestroyedId = notification.connect('destroy', Lang.bind(this, this._notificationDestroyed));
|
|
|
|
this._stackedNotifications.push(stackedNotification);
|
2011-03-22 07:40:53 +00:00
|
|
|
if (!this.source.isChat)
|
|
|
|
notification.enableScrolling(false);
|
2012-05-30 00:36:45 +00:00
|
|
|
if (this.notificationStack.get_n_children() > 0)
|
2011-03-21 21:43:34 +00:00
|
|
|
notification.setIconVisible(false);
|
|
|
|
this.notificationStack.add(notification.actor);
|
|
|
|
notification.expand(false);
|
|
|
|
},
|
|
|
|
|
2011-03-22 07:40:53 +00:00
|
|
|
// scrollTo:
|
|
|
|
// @side: St.Side.TOP or St.Side.BOTTOM
|
|
|
|
//
|
|
|
|
// Scrolls the notifiction stack to the indicated edge
|
|
|
|
scrollTo: function(side) {
|
|
|
|
let adjustment = this.notificationStackView.vscroll.adjustment;
|
|
|
|
if (side == St.Side.TOP)
|
|
|
|
adjustment.value = adjustment.lower;
|
|
|
|
else if (side == St.Side.BOTTOM)
|
|
|
|
adjustment.value = adjustment.upper;
|
|
|
|
},
|
|
|
|
|
2011-03-21 21:43:34 +00:00
|
|
|
_contentUpdated: function() {
|
|
|
|
this.emit('content-updated');
|
|
|
|
},
|
|
|
|
|
|
|
|
_notificationDoneDisplaying: function() {
|
2012-11-30 15:14:50 +00:00
|
|
|
this.source.emit('done-displaying-content', true);
|
2011-03-21 21:43:34 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_notificationDestroyed: function(notification) {
|
2011-09-08 04:40:58 +00:00
|
|
|
for (let i = 0; i < this._stackedNotifications.length; i++) {
|
|
|
|
if (this._stackedNotifications[i].notification == notification) {
|
|
|
|
let stackedNotification = this._stackedNotifications[i];
|
|
|
|
notification.disconnect(stackedNotification.notificationExpandedId);
|
|
|
|
notification.disconnect(stackedNotification.notificationDoneDisplayingId);
|
|
|
|
notification.disconnect(stackedNotification.notificationDestroyedId);
|
|
|
|
this._stackedNotifications.splice(i, 1);
|
2012-08-19 21:32:37 +00:00
|
|
|
if (notification.actor.get_parent() == this.notificationStack)
|
|
|
|
this.notificationStack.remove_actor(notification.actor);
|
2011-09-08 04:40:58 +00:00
|
|
|
this._contentUpdated();
|
|
|
|
break;
|
|
|
|
}
|
2011-03-21 21:43:34 +00:00
|
|
|
}
|
2011-09-08 04:40:58 +00:00
|
|
|
|
2012-02-16 22:23:16 +00:00
|
|
|
let firstNotification = this._stackedNotifications[0];
|
|
|
|
if (firstNotification)
|
|
|
|
firstNotification.notification.setIconVisible(true);
|
2010-06-23 19:20:39 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2011-02-11 19:43:01 +00:00
|
|
|
Signals.addSignalMethods(SummaryItem.prototype);
|
2010-06-23 19:20:39 +00:00
|
|
|
|
2013-02-10 10:48:13 +00:00
|
|
|
const MessageTrayContextMenu = new Lang.Class({
|
|
|
|
Name: 'MessageTrayContextMenu',
|
|
|
|
Extends: PopupMenu.PopupMenu,
|
|
|
|
|
|
|
|
_init: function(tray) {
|
|
|
|
this._dummy = new St.Bin({ opacity: 0 });
|
|
|
|
Main.uiGroup.add_actor(this._dummy);
|
|
|
|
|
|
|
|
this.parent(this._dummy, 0, St.Side.BOTTOM);
|
|
|
|
|
|
|
|
this.addAction(_("Clear"), function() {
|
|
|
|
let toDestroy = [];
|
|
|
|
let sources = tray.getSources();
|
|
|
|
for (let i = 0; i < sources.length; i++) {
|
|
|
|
// We exclude trayIcons, chat and resident sources
|
|
|
|
if (sources[i].trayIcon ||
|
|
|
|
sources[i].isChat ||
|
|
|
|
sources[i].resident)
|
|
|
|
continue;
|
|
|
|
toDestroy.push(sources[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let i = 0; i < toDestroy.length; i++) {
|
|
|
|
toDestroy[i].destroy();
|
|
|
|
}
|
|
|
|
|
|
|
|
toDestroy = null;
|
|
|
|
tray.close();
|
|
|
|
});
|
|
|
|
|
|
|
|
let separator = new PopupMenu.PopupSeparatorMenuItem();
|
|
|
|
this.addMenuItem(separator);
|
|
|
|
|
|
|
|
let settingsItem = this.addSettingsAction(_("Notification Settings"), 'gnome-notifications-panel.desktop');
|
|
|
|
settingsItem.connect('activate', function() { tray.close(); });
|
|
|
|
},
|
|
|
|
|
|
|
|
setPosition: function(x, y) {
|
|
|
|
this._dummy.set_position(x, y);
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const MessageTray = new Lang.Class({
|
|
|
|
Name: 'MessageTray',
|
2010-01-13 20:05:20 +00:00
|
|
|
|
|
|
|
_init: function() {
|
2011-08-16 12:24:39 +00:00
|
|
|
this._presence = new GnomeSession.Presence(Lang.bind(this, function(proxy, error) {
|
|
|
|
this._onStatusChanged(proxy.status);
|
|
|
|
}));
|
2011-02-19 15:49:56 +00:00
|
|
|
this._busy = false;
|
2011-08-16 12:24:39 +00:00
|
|
|
this._presence.connectSignal('StatusChanged', Lang.bind(this, function(proxy, senderName, [status]) {
|
|
|
|
this._onStatusChanged(status);
|
|
|
|
}));
|
2011-02-19 15:49:56 +00:00
|
|
|
|
2012-02-14 16:33:26 +00:00
|
|
|
this.actor = new St.Widget({ name: 'message-tray',
|
|
|
|
reactive: true,
|
2012-09-05 00:26:09 +00:00
|
|
|
track_hover: true,
|
|
|
|
layout_manager: new Clutter.BinLayout(),
|
|
|
|
x_expand: true,
|
|
|
|
y_expand: true,
|
|
|
|
y_align: Clutter.ActorAlign.START,
|
|
|
|
});
|
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
|
|
|
|
2012-08-20 20:32:10 +00:00
|
|
|
this._notificationWidget = new St.Widget({ name: 'notification-container',
|
2012-09-08 16:54:50 +00:00
|
|
|
y_align: Clutter.ActorAlign.START,
|
2012-09-05 00:26:09 +00:00
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
2012-09-08 16:54:50 +00:00
|
|
|
y_expand: true,
|
2012-09-05 00:26:09 +00:00
|
|
|
x_expand: true,
|
2012-08-20 20:32:10 +00:00
|
|
|
layout_manager: new Clutter.BinLayout() });
|
2012-11-08 14:38:57 +00:00
|
|
|
this._notificationWidget.connect('key-release-event', Lang.bind(this, this._onNotificationKeyRelease));
|
|
|
|
|
2012-08-20 19:51:52 +00:00
|
|
|
this.actor.add_actor(this._notificationWidget);
|
|
|
|
|
2012-09-05 00:26:09 +00:00
|
|
|
this._notificationBin = new St.Bin({ y_expand: true });
|
|
|
|
this._notificationBin.set_y_align(Clutter.ActorAlign.START);
|
2012-08-20 19:51:52 +00:00
|
|
|
this._notificationWidget.add_actor(this._notificationBin);
|
|
|
|
this._notificationWidget.hide();
|
2010-01-13 20:05:20 +00:00
|
|
|
this._notificationQueue = [];
|
2010-02-01 20:23:49 +00:00
|
|
|
this._notification = null;
|
2010-12-16 20:49:47 +00:00
|
|
|
this._notificationClickedId = 0;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2012-09-05 00:26:09 +00:00
|
|
|
this.actor.connect('button-release-event', Lang.bind(this, function(actor, event) {
|
2012-08-16 17:53:07 +00:00
|
|
|
this._setClickedSummaryItem(null);
|
|
|
|
this._updateState();
|
|
|
|
actor.grab_key_focus();
|
|
|
|
}));
|
2012-09-15 18:16:01 +00:00
|
|
|
global.focus_manager.add_group(this.actor);
|
2012-12-13 22:47:16 +00:00
|
|
|
this._summary = new St.BoxLayout({ style_class: 'message-tray-summary',
|
2010-03-15 16:20:10 +00:00
|
|
|
reactive: true,
|
2012-09-05 00:26:09 +00:00
|
|
|
track_hover: true,
|
2012-09-19 10:40:32 +00:00
|
|
|
x_align: Clutter.ActorAlign.END,
|
2012-09-05 00:26:09 +00:00
|
|
|
x_expand: true,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_expand: true });
|
2010-03-15 16:20:10 +00:00
|
|
|
this._summary.connect('notify::hover', Lang.bind(this, this._onSummaryHoverChanged));
|
2012-09-05 00:26:09 +00:00
|
|
|
this.actor.add_actor(this._summary);
|
|
|
|
this._summary.opacity = 0;
|
2010-02-09 18:31:39 +00:00
|
|
|
|
2010-10-19 19:17:36 +00:00
|
|
|
this._summaryMotionId = 0;
|
2011-09-09 13:45:39 +00:00
|
|
|
this._trayMotionId = 0;
|
2010-10-19 19:17:36 +00:00
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
this._summaryBoxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM,
|
2011-07-27 14:03:30 +00:00
|
|
|
{ reactive: true,
|
|
|
|
track_hover: true });
|
2012-09-15 16:56:01 +00:00
|
|
|
this._summaryBoxPointer.actor.connect('key-press-event',
|
|
|
|
Lang.bind(this, this._onSummaryBoxPointerKeyPress));
|
2011-02-11 19:43:01 +00:00
|
|
|
this._summaryBoxPointer.actor.style_class = 'summary-boxpointer';
|
|
|
|
this._summaryBoxPointer.actor.hide();
|
2012-05-22 20:16:31 +00:00
|
|
|
Main.layoutManager.addChrome(this._summaryBoxPointer.actor);
|
2010-10-19 19:17:36 +00:00
|
|
|
|
2011-03-21 21:43:34 +00:00
|
|
|
this._summaryBoxPointerItem = null;
|
|
|
|
this._summaryBoxPointerContentUpdatedId = 0;
|
|
|
|
this._summaryBoxPointerDoneDisplayingId = 0;
|
2010-06-23 19:20:39 +00:00
|
|
|
this._clickedSummaryItem = null;
|
2011-02-11 19:43:01 +00:00
|
|
|
this._clickedSummaryItemMouseButton = -1;
|
2010-10-19 19:17:36 +00:00
|
|
|
this._clickedSummaryItemAllocationChangedId = 0;
|
2010-09-24 20:04:56 +00:00
|
|
|
|
2012-08-20 19:51:52 +00:00
|
|
|
this._closeButton = makeCloseButton();
|
2012-08-18 23:34:49 +00:00
|
|
|
this._closeButton.hide();
|
2012-11-08 14:38:57 +00:00
|
|
|
this._closeButton.connect('clicked', Lang.bind(this, this._closeNotification));
|
2012-08-20 19:51:52 +00:00
|
|
|
this._notificationWidget.add_actor(this._closeButton);
|
2012-08-18 23:34:49 +00:00
|
|
|
|
2012-08-20 04:06:50 +00:00
|
|
|
this._idleMonitorBecameActiveId = 0;
|
2012-07-30 23:14:49 +00:00
|
|
|
this._userActiveWhileNotificationShown = false;
|
2012-03-15 02:50:58 +00:00
|
|
|
|
2012-08-20 04:06:50 +00:00
|
|
|
this.idleMonitor = new GnomeDesktop.IdleMonitor();
|
2012-03-15 02:50:58 +00:00
|
|
|
|
2012-08-10 18:35:59 +00:00
|
|
|
this._grabHelper = new GrabHelper.GrabHelper(this.actor,
|
2012-12-12 13:14:13 +00:00
|
|
|
{ keybindingMode: Shell.KeyBindingMode.MESSAGE_TRAY });
|
2012-02-28 20:04:00 +00:00
|
|
|
this._grabHelper.addActor(this._summaryBoxPointer.actor);
|
|
|
|
this._grabHelper.addActor(this.actor);
|
2012-09-16 20:36:21 +00:00
|
|
|
if (Main.panel.statusArea.activities)
|
|
|
|
this._grabHelper.addActor(Main.panel.statusArea.activities.hotCorner.actor);
|
2011-02-10 03:45:50 +00:00
|
|
|
|
2011-09-09 14:56:51 +00:00
|
|
|
Main.layoutManager.keyboardBox.connect('notify::hover', Lang.bind(this, this._onKeyboardHoverChanged));
|
2012-09-08 18:01:30 +00:00
|
|
|
Main.layoutManager.connect('keyboard-visible-changed', Lang.bind(this, this._onKeyboardVisibleChanged));
|
2011-09-09 14:56:51 +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;
|
2010-07-21 04:42:37 +00:00
|
|
|
this._locked = false;
|
2011-08-29 15:11:22 +00:00
|
|
|
this._traySummoned = false;
|
2010-08-31 19:50:18 +00:00
|
|
|
this._useLongerTrayLeftTimeout = 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._trayLeftTimeoutId = 0;
|
|
|
|
this._pointerInTray = false;
|
2011-09-09 14:56:51 +00:00
|
|
|
this._pointerInKeyboard = false;
|
2012-09-08 18:01:30 +00:00
|
|
|
this._keyboardVisible = false;
|
2012-10-14 16:57:45 +00:00
|
|
|
this._keyboardUnderMessageTray = 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._summaryState = State.HIDDEN;
|
|
|
|
this._pointerInSummary = false;
|
2012-09-06 09:45:30 +00:00
|
|
|
this._notificationClosed = 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._notificationState = State.HIDDEN;
|
|
|
|
this._notificationTimeoutId = 0;
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._notificationExpandedId = 0;
|
2011-02-11 19:43:01 +00:00
|
|
|
this._summaryBoxPointerState = State.HIDDEN;
|
2011-03-21 21:43:34 +00:00
|
|
|
this._summaryBoxPointerTimeoutId = 0;
|
2012-09-08 18:01:30 +00:00
|
|
|
this._desktopCloneState = State.HIDDEN;
|
2010-07-21 05:19:25 +00:00
|
|
|
this._overviewVisible = Main.overview.visible;
|
2010-02-22 22:19:32 +00:00
|
|
|
this._notificationRemoved = false;
|
2011-03-21 21:43:34 +00:00
|
|
|
this._reNotifyAfterHideNotification = null;
|
2012-06-06 22:27:52 +00:00
|
|
|
this._inFullscreen = false;
|
2012-08-08 20:48:17 +00:00
|
|
|
this._desktopClone = null;
|
2012-12-07 11:09:42 +00:00
|
|
|
this._inCtrlAltTab = false;
|
2010-02-09 18:31:39 +00:00
|
|
|
|
2012-08-08 17:51:31 +00:00
|
|
|
this._lightbox = new Lightbox.Lightbox(global.window_group,
|
|
|
|
{ inhibitEvents: true,
|
|
|
|
fadeInTime: ANIMATION_TIME,
|
2012-08-20 18:32:05 +00:00
|
|
|
fadeOutTime: ANIMATION_TIME,
|
|
|
|
fadeFactor: 0.2
|
2012-08-08 17:51:31 +00:00
|
|
|
});
|
|
|
|
|
2011-07-25 18:25:51 +00:00
|
|
|
Main.layoutManager.trayBox.add_actor(this.actor);
|
2012-07-08 12:08:56 +00:00
|
|
|
this.actor.y = 0;
|
2011-07-25 18:25:51 +00:00
|
|
|
Main.layoutManager.trackChrome(this.actor);
|
2012-08-20 19:51:52 +00:00
|
|
|
Main.layoutManager.trackChrome(this._notificationWidget);
|
2012-08-18 23:34:49 +00:00
|
|
|
Main.layoutManager.trackChrome(this._closeButton);
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2012-06-06 22:27:52 +00:00
|
|
|
Main.layoutManager.connect('primary-fullscreen-changed', Lang.bind(this, this._onFullscreenChanged));
|
2010-06-03 15:14:58 +00:00
|
|
|
|
2010-02-19 00:42:09 +00:00
|
|
|
Main.overview.connect('showing', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this._overviewVisible = true;
|
2012-09-15 16:49:41 +00:00
|
|
|
this._grabHelper.ungrab(); // drop modal grab if necessary
|
2012-07-31 14:10:24 +00:00
|
|
|
this.actor.add_style_pseudo_class('overview');
|
2012-02-28 20:04:00 +00:00
|
|
|
this._updateState();
|
2010-02-19 00:42:09 +00:00
|
|
|
}));
|
|
|
|
Main.overview.connect('hiding', Lang.bind(this,
|
|
|
|
function() {
|
|
|
|
this._overviewVisible = false;
|
2012-08-20 00:47:52 +00:00
|
|
|
this._escapeTray();
|
2012-02-28 20:04:00 +00:00
|
|
|
this._updateState();
|
2010-02-19 00:42:09 +00:00
|
|
|
}));
|
2010-02-12 17:33:00 +00:00
|
|
|
|
2012-10-10 20:17:39 +00:00
|
|
|
Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
|
2012-05-22 22:02:00 +00:00
|
|
|
|
2012-08-20 20:16:21 +00:00
|
|
|
Main.wm.addKeybinding('toggle-message-tray',
|
|
|
|
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
|
|
|
|
Meta.KeyBindingFlags.NONE,
|
2012-12-12 13:14:13 +00:00
|
|
|
Shell.KeyBindingMode.NORMAL |
|
|
|
|
Shell.KeyBindingMode.MESSAGE_TRAY |
|
|
|
|
Shell.KeyBindingMode.OVERVIEW,
|
2012-08-20 20:16:21 +00:00
|
|
|
Lang.bind(this, this.toggleAndNavigate));
|
|
|
|
Main.wm.addKeybinding('focus-active-notification',
|
|
|
|
new Gio.Settings({ schema: SHELL_KEYBINDINGS_SCHEMA }),
|
|
|
|
Meta.KeyBindingFlags.NONE,
|
2012-12-12 13:14:13 +00:00
|
|
|
Shell.KeyBindingMode.NORMAL |
|
|
|
|
Shell.KeyBindingMode.MESSAGE_TRAY |
|
|
|
|
Shell.KeyBindingMode.OVERVIEW,
|
2012-08-20 20:16:21 +00:00
|
|
|
Lang.bind(this, this._expandActiveNotification));
|
2012-07-14 11:07:24 +00:00
|
|
|
|
2012-11-04 17:49:14 +00:00
|
|
|
this._sources = new Hash.Map();
|
2010-12-30 21:09:56 +00:00
|
|
|
this._chatSummaryItemsCount = 0;
|
2012-08-17 20:07:42 +00:00
|
|
|
|
2013-01-29 20:19:19 +00:00
|
|
|
this._setupTrayDwellIfNeeded();
|
2012-10-10 20:17:39 +00:00
|
|
|
this._sessionUpdated();
|
2012-11-04 17:49:14 +00:00
|
|
|
|
|
|
|
this._noMessages = new St.Label({ text: _("No Messages"),
|
|
|
|
style_class: 'no-messages-label',
|
|
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
|
|
x_expand: true,
|
|
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
|
|
y_expand: true });
|
|
|
|
this.actor.add_actor(this._noMessages);
|
2012-11-13 01:44:01 +00:00
|
|
|
this._updateNoMessagesLabel();
|
2013-02-10 10:48:13 +00:00
|
|
|
|
|
|
|
this._contextMenu = new MessageTrayContextMenu(this);
|
|
|
|
this._grabHelper.addActor(this._contextMenu.actor);
|
|
|
|
this.actor.connect('button-press-event', Lang.bind(this, function(actor, event) {
|
|
|
|
let button = event.get_button();
|
|
|
|
if (button == 3) {
|
|
|
|
let [stageX, stageY] = event.get_coords();
|
|
|
|
this._lock();
|
|
|
|
this._contextMenu.setPosition(Math.round(stageX), Math.round(stageY));
|
|
|
|
this._grabHelper.grab({ actor: this._contextMenu.actor,
|
|
|
|
grabFocus: true,
|
|
|
|
onUngrab: Lang.bind(this, function () {
|
|
|
|
this._unlock();
|
|
|
|
this._contextMenu.close();
|
|
|
|
})
|
|
|
|
});
|
|
|
|
this._contextMenu.open();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this._grabHelper.ungrab({ actor: this._contextMenu.actor });
|
|
|
|
}
|
|
|
|
}));
|
|
|
|
|
|
|
|
this._contextMenu.actor.hide();
|
|
|
|
Main.layoutManager.addChrome(this._contextMenu.actor);
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
close: function() {
|
|
|
|
this._escapeTray();
|
2012-11-13 01:44:01 +00:00
|
|
|
},
|
|
|
|
|
2013-01-29 20:19:19 +00:00
|
|
|
_setupTrayDwellIfNeeded: function() {
|
|
|
|
// If we don't have extended barrier features, then we need
|
|
|
|
// to support the old tray dwelling mechanism.
|
|
|
|
if (!global.display.supports_extended_barriers()) {
|
|
|
|
let pointerWatcher = PointerWatcher.getPointerWatcher();
|
|
|
|
pointerWatcher.addWatch(TRAY_DWELL_CHECK_INTERVAL, Lang.bind(this, this._checkTrayDwell));
|
|
|
|
this._trayDwellTimeoutId = 0;
|
|
|
|
this._trayDwelling = false;
|
|
|
|
this._trayDwellUserTime = 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-13 01:44:01 +00:00
|
|
|
_updateNoMessagesLabel: function() {
|
2012-11-04 17:49:14 +00:00
|
|
|
this._noMessages.visible = this._sources.size() == 0;
|
2012-10-10 20:17:39 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_sessionUpdated: function() {
|
2012-12-07 11:09:42 +00:00
|
|
|
if ((Main.sessionMode.isLocked || Main.sessionMode.isGreeter) && this._inCtrlAltTab) {
|
2012-10-10 20:17:39 +00:00
|
|
|
Main.ctrlAltTabManager.removeGroup(this._summary);
|
2012-12-07 11:09:42 +00:00
|
|
|
this._inCtrlAltTab = false;
|
|
|
|
} else if (!this._inCtrlAltTab) {
|
2012-11-20 10:51:56 +00:00
|
|
|
Main.ctrlAltTabManager.addGroup(this._summary, _("Message Tray"), 'user-available-symbolic',
|
2012-10-10 20:17:39 +00:00
|
|
|
{ focusCallback: Lang.bind(this, this.toggleAndNavigate),
|
|
|
|
sortGroup: CtrlAltTab.SortGroup.BOTTOM });
|
2012-12-07 11:09:42 +00:00
|
|
|
this._inCtrlAltTab = true;
|
|
|
|
}
|
2012-10-10 20:17:39 +00:00
|
|
|
this._updateState();
|
2012-08-17 20:07:42 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_checkTrayDwell: function(x, y) {
|
2012-08-30 16:14:24 +00:00
|
|
|
let monitor = Main.layoutManager.bottomMonitor;
|
|
|
|
let shouldDwell = (x >= monitor.x && x <= monitor.x + monitor.width &&
|
|
|
|
y == monitor.y + monitor.height - 1);
|
|
|
|
if (shouldDwell) {
|
2012-08-21 23:40:11 +00:00
|
|
|
// We only set up dwell timeout when the user is not hovering over the tray
|
|
|
|
// (!this.actor.hover). This avoids bringing up the message tray after the
|
|
|
|
// user clicks on a notification with the pointer on the bottom pixel
|
|
|
|
// of the monitor. The _trayDwelling variable is used so that we only try to
|
|
|
|
// fire off one tray dwell - if it fails (because, say, the user has the mouse down),
|
|
|
|
// we don't try again until the user moves the mouse up and down again.
|
2012-09-06 16:02:26 +00:00
|
|
|
if (!this._trayDwelling && !this.actor.hover && this._trayDwellTimeoutId == 0) {
|
|
|
|
// Save the interaction timestamp so we can detect user input
|
|
|
|
let focusWindow = global.display.focus_window;
|
|
|
|
this._trayDwellUserTime = focusWindow ? focusWindow.user_time : 0;
|
|
|
|
|
2012-08-17 20:07:42 +00:00
|
|
|
this._trayDwellTimeoutId = Mainloop.timeout_add(TRAY_DWELL_TIME,
|
2012-08-21 23:40:11 +00:00
|
|
|
Lang.bind(this, this._trayDwellTimeout));
|
2012-09-06 16:02:26 +00:00
|
|
|
}
|
2012-08-21 23:40:11 +00:00
|
|
|
this._trayDwelling = true;
|
2012-08-17 20:07:42 +00:00
|
|
|
} else {
|
|
|
|
this._cancelTrayDwell();
|
2012-08-21 23:40:11 +00:00
|
|
|
this._trayDwelling = false;
|
2012-08-17 20:07:42 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_cancelTrayDwell: function() {
|
|
|
|
if (this._trayDwellTimeoutId != 0) {
|
|
|
|
Mainloop.source_remove(this._trayDwellTimeoutId);
|
|
|
|
this._trayDwellTimeoutId = 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_trayDwellTimeout: function() {
|
2012-11-20 18:53:22 +00:00
|
|
|
this._trayDwellTimeoutId = 0;
|
|
|
|
|
2012-09-20 22:36:52 +00:00
|
|
|
if (Main.modalCount > 0)
|
|
|
|
return false;
|
|
|
|
|
2012-09-06 16:02:26 +00:00
|
|
|
// If the user interacted with the focus window since we started the tray
|
|
|
|
// dwell (by clicking or typing), don't activate the message tray
|
|
|
|
let focusWindow = global.display.focus_window;
|
|
|
|
let currentUserTime = focusWindow ? focusWindow.user_time : 0;
|
|
|
|
if (currentUserTime != this._trayDwellUserTime)
|
|
|
|
return false;
|
|
|
|
|
2012-07-30 19:25:07 +00:00
|
|
|
this.openTray();
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
openTray: function() {
|
2012-08-17 20:07:42 +00:00
|
|
|
this._traySummoned = true;
|
|
|
|
this._updateState();
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2012-11-08 14:38:57 +00:00
|
|
|
_onNotificationKeyRelease: function(actor, event) {
|
2012-12-23 03:21:47 +00:00
|
|
|
if (event.get_key_symbol() == Clutter.KEY_Escape && event.get_state() == 0) {
|
2012-11-08 14:38:57 +00:00
|
|
|
this._closeNotification();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
_closeNotification: function() {
|
2012-08-18 23:34:49 +00:00
|
|
|
if (this._notificationState == State.SHOWN) {
|
2012-10-25 20:07:12 +00:00
|
|
|
this._closeButton.hide();
|
2012-09-06 09:45:30 +00:00
|
|
|
this._notificationClosed = true;
|
2012-09-19 03:40:53 +00:00
|
|
|
this._updateState();
|
2012-09-06 09:45:30 +00:00
|
|
|
this._notificationClosed = false;
|
2012-08-18 23:34:49 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2010-01-13 20:05:20 +00:00
|
|
|
contains: function(source) {
|
2012-11-04 17:49:14 +00:00
|
|
|
return this._sources.has(source);
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
add: function(source) {
|
|
|
|
if (this.contains(source)) {
|
2010-08-09 17:18:15 +00:00
|
|
|
log('Trying to re-add source ' + source.title);
|
2010-01-13 20:05:20 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-16 15:08:54 +00:00
|
|
|
// Register that we got a notification for this source
|
|
|
|
source.policy.store();
|
|
|
|
|
|
|
|
source.policy.connect('enable-changed', Lang.bind(this, this._onSourceEnableChanged, source));
|
|
|
|
source.policy.connect('policy-changed', Lang.bind(this, this._updateState));
|
|
|
|
this._onSourceEnableChanged(source.policy, source);
|
2012-11-04 17:49:14 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_addSource: function(source) {
|
|
|
|
let obj = {
|
|
|
|
source: source,
|
|
|
|
summaryItem: new SummaryItem(source),
|
|
|
|
notifyId: 0,
|
|
|
|
destroyId: 0,
|
|
|
|
mutedChangedId: 0
|
|
|
|
};
|
|
|
|
let summaryItem = obj.summaryItem;
|
2010-06-23 19:20:39 +00:00
|
|
|
|
2010-12-30 21:09:56 +00:00
|
|
|
if (source.isChat) {
|
2012-02-13 20:27:16 +00:00
|
|
|
this._summary.insert_child_at_index(summaryItem.actor, 0);
|
2010-12-30 21:09:56 +00:00
|
|
|
this._chatSummaryItemsCount++;
|
|
|
|
} else {
|
2012-02-13 20:27:16 +00:00
|
|
|
this._summary.insert_child_at_index(summaryItem.actor, this._chatSummaryItemsCount);
|
2010-12-30 21:09:56 +00:00
|
|
|
}
|
2010-06-23 19:20:39 +00:00
|
|
|
|
2012-11-04 17:49:14 +00:00
|
|
|
this._sources.set(source, obj);
|
2010-01-28 18:12:03 +00:00
|
|
|
|
2012-11-04 17:49:14 +00:00
|
|
|
obj.notifyId = source.connect('notify', Lang.bind(this, this._onNotify));
|
|
|
|
obj.destroyId = source.connect('destroy', Lang.bind(this, this._onSourceDestroy));
|
|
|
|
obj.mutedChangedId = source.connect('muted-changed', Lang.bind(this,
|
2011-11-03 14:48:09 +00:00
|
|
|
function () {
|
|
|
|
if (source.isMuted)
|
|
|
|
this._notificationQueue = this._notificationQueue.filter(function(notification) {
|
|
|
|
return source != notification.source;
|
|
|
|
});
|
|
|
|
}));
|
|
|
|
|
2011-01-13 20:04:37 +00:00
|
|
|
summaryItem.actor.connect('clicked', Lang.bind(this,
|
2012-08-22 14:48:55 +00:00
|
|
|
function(actor, button) {
|
2012-08-09 00:30:13 +00:00
|
|
|
actor.grab_key_focus();
|
2011-01-13 20:04:37 +00:00
|
|
|
this._onSummaryItemClicked(summaryItem, button);
|
2010-01-13 20:05:20 +00:00
|
|
|
}));
|
2012-08-22 14:48:55 +00:00
|
|
|
summaryItem.actor.connect('popup-menu', Lang.bind(this,
|
|
|
|
function(actor, button) {
|
|
|
|
actor.grab_key_focus();
|
|
|
|
this._onSummaryItemClicked(summaryItem, 3);
|
|
|
|
}));
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-09-09 20:18:07 +00:00
|
|
|
// We need to display the newly-added summary item, but if the
|
|
|
|
// caller is about to post a notification, we want to show that
|
|
|
|
// *first* and not show the summary item until after it hides.
|
|
|
|
// So postpone calling _updateState() a tiny bit.
|
|
|
|
Meta.later_add(Meta.LaterType.BEFORE_REDRAW, Lang.bind(this, function() { this._updateState(); return false; }));
|
2012-05-22 22:02:00 +00:00
|
|
|
|
2013-01-30 18:47:55 +00:00
|
|
|
this.emit('source-added', source);
|
2012-11-13 01:44:01 +00:00
|
|
|
|
|
|
|
this._updateNoMessagesLabel();
|
2012-05-22 22:02:00 +00:00
|
|
|
},
|
|
|
|
|
2012-11-04 17:49:14 +00:00
|
|
|
_removeSource: function(source) {
|
|
|
|
let [, obj] = this._sources.delete(source);
|
|
|
|
let summaryItem = obj.summaryItem;
|
2010-12-30 21:09:56 +00:00
|
|
|
|
|
|
|
if (source.isChat)
|
|
|
|
this._chatSummaryItemsCount--;
|
|
|
|
|
2012-11-04 17:49:14 +00:00
|
|
|
source.disconnect(obj.notifyId);
|
|
|
|
source.disconnect(obj.destroyId);
|
|
|
|
source.disconnect(obj.mutedChangedId);
|
|
|
|
|
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) {
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._updateNotificationTimeout(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
|
|
|
}
|
2012-11-04 17:49:14 +00:00
|
|
|
if (this._clickedSummaryItem == summaryItem) {
|
2012-02-28 20:04:00 +00:00
|
|
|
this._setClickedSummaryItem(null);
|
2010-02-25 19:42:18 +00:00
|
|
|
needUpdate = true;
|
|
|
|
}
|
|
|
|
|
2012-11-04 17:49:14 +00:00
|
|
|
summaryItem.destroy();
|
2011-03-06 21:29:11 +00:00
|
|
|
|
2013-02-05 18:06:19 +00:00
|
|
|
this.emit('source-removed', source);
|
|
|
|
|
2012-11-13 01:44:01 +00:00
|
|
|
this._updateNoMessagesLabel();
|
|
|
|
|
2011-10-07 20:22:08 +00:00
|
|
|
if (needUpdate)
|
2010-02-25 19:42:18 +00:00
|
|
|
this._updateState();
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2013-01-30 18:47:55 +00:00
|
|
|
getSources: function() {
|
|
|
|
return this._sources.keys();
|
2012-11-04 17:49:14 +00:00
|
|
|
},
|
|
|
|
|
2012-10-16 15:08:54 +00:00
|
|
|
_onSourceEnableChanged: function(policy, source) {
|
|
|
|
let wasEnabled = this.contains(source);
|
|
|
|
let shouldBeEnabled = policy.enable;
|
|
|
|
|
|
|
|
if (wasEnabled != shouldBeEnabled) {
|
|
|
|
if (shouldBeEnabled)
|
|
|
|
this._addSource(source);
|
|
|
|
else
|
|
|
|
this._removeSource(source);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-11-04 17:49:14 +00:00
|
|
|
_onSourceDestroy: function(source) {
|
|
|
|
this._removeSource(source);
|
|
|
|
},
|
|
|
|
|
2011-03-03 00:47:58 +00:00
|
|
|
_onNotificationDestroy: function(notification) {
|
2010-02-22 22:19:32 +00:00
|
|
|
if (this._notification == notification && (this._notificationState == State.SHOWN || this._notificationState == State.SHOWING)) {
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._updateNotificationTimeout(0);
|
2010-02-22 22:19:32 +00:00
|
|
|
this._notificationRemoved = true;
|
|
|
|
this._updateState();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let index = this._notificationQueue.indexOf(notification);
|
2011-03-03 00:47:58 +00:00
|
|
|
notification.destroy();
|
2010-02-22 22:19:32 +00:00
|
|
|
if (index != -1)
|
|
|
|
this._notificationQueue.splice(index, 1);
|
|
|
|
},
|
|
|
|
|
2011-02-10 03:45:50 +00:00
|
|
|
_lock: function() {
|
2010-02-22 19:23:36 +00:00
|
|
|
this._locked = true;
|
|
|
|
},
|
|
|
|
|
2011-02-10 03:45:50 +00:00
|
|
|
_unlock: function() {
|
2010-07-21 05:19:25 +00:00
|
|
|
if (!this._locked)
|
|
|
|
return;
|
2010-02-22 19:23:36 +00:00
|
|
|
this._locked = false;
|
2011-07-27 14:03:30 +00:00
|
|
|
this._pointerInTray = this.actor.hover;
|
2010-02-22 19:23:36 +00:00
|
|
|
this._updateState();
|
|
|
|
},
|
|
|
|
|
2011-08-29 15:11:22 +00:00
|
|
|
toggle: function() {
|
|
|
|
this._traySummoned = !this._traySummoned;
|
|
|
|
this._updateState();
|
|
|
|
},
|
|
|
|
|
2012-08-15 17:42:31 +00:00
|
|
|
toggleAndNavigate: function() {
|
|
|
|
this.toggle();
|
2012-09-15 21:01:56 +00:00
|
|
|
this._summary.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
|
2012-08-15 17:42:31 +00:00
|
|
|
},
|
|
|
|
|
2011-08-29 15:11:22 +00:00
|
|
|
hide: function() {
|
|
|
|
this._traySummoned = false;
|
|
|
|
this.actor.set_hover(false);
|
|
|
|
this._summary.set_hover(false);
|
|
|
|
this._updateState();
|
|
|
|
},
|
|
|
|
|
2010-02-01 20:23:49 +00:00
|
|
|
_onNotify: function(source, notification) {
|
2011-03-21 21:43:34 +00:00
|
|
|
if (this._summaryBoxPointerItem && this._summaryBoxPointerItem.source == source) {
|
2012-08-06 15:28:55 +00:00
|
|
|
if (this._summaryBoxPointerState == State.HIDING) {
|
2011-03-21 21:43:34 +00:00
|
|
|
// We are in the process of hiding the summary box pointer.
|
|
|
|
// If there is an update for one of the notifications or
|
|
|
|
// a new notification to be added to the notification stack
|
|
|
|
// while it is in the process of being hidden, we show it as
|
|
|
|
// a new notification. However, we first wait till the hide
|
|
|
|
// is complete. This is especially important if one of the
|
|
|
|
// notifications in the stack was updated because we will
|
|
|
|
// need to be able to re-parent its actor to a different
|
|
|
|
// part of the stage.
|
|
|
|
this._reNotifyAfterHideNotification = notification;
|
2012-08-06 15:28:55 +00:00
|
|
|
} else {
|
|
|
|
// The summary box pointer is showing or shown (otherwise,
|
|
|
|
// this._summaryBoxPointerItem would be null)
|
|
|
|
// Immediately mark the notification as acknowledged, as it's
|
|
|
|
// not going into the queue
|
|
|
|
notification.acknowledged = true;
|
|
|
|
}
|
|
|
|
|
2010-04-07 19:32:47 +00:00
|
|
|
return;
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
}
|
2010-04-07 19:32:47 +00:00
|
|
|
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
if (this._notification == notification) {
|
|
|
|
// If a notification that is being shown is updated, we update
|
|
|
|
// how it is shown and extend the time until it auto-hides.
|
|
|
|
// If a new notification is updated while it is being hidden,
|
|
|
|
// we stop hiding it and show it again.
|
|
|
|
this._updateShowingNotification();
|
|
|
|
} else if (this._notificationQueue.indexOf(notification) < 0) {
|
2010-02-22 22:19:32 +00:00
|
|
|
notification.connect('destroy',
|
2011-03-03 00:47:58 +00:00
|
|
|
Lang.bind(this, this._onNotificationDestroy));
|
2011-01-04 09:34:57 +00:00
|
|
|
this._notificationQueue.push(notification);
|
|
|
|
this._notificationQueue.sort(function(notification1, notification2) {
|
|
|
|
return (notification2.urgency - notification1.urgency);
|
|
|
|
});
|
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
|
|
|
},
|
|
|
|
|
2011-01-13 20:04:37 +00:00
|
|
|
_onSummaryItemClicked: function(summaryItem, button) {
|
2012-11-02 15:09:04 +00:00
|
|
|
if (summaryItem.source.handleSummaryClick(button)) {
|
2012-09-21 15:21:25 +00:00
|
|
|
if (summaryItem.source.keepTrayOnSummaryClick)
|
|
|
|
this._setClickedSummaryItem(null);
|
|
|
|
else
|
|
|
|
this._escapeTray();
|
2011-02-11 19:43:01 +00:00
|
|
|
} else {
|
2012-08-16 17:54:03 +00:00
|
|
|
if (!this._setClickedSummaryItem(summaryItem, button))
|
|
|
|
this._setClickedSummaryItem(null);
|
2011-02-11 19:43:01 +00:00
|
|
|
}
|
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) {
|
2012-08-17 20:07:42 +00:00
|
|
|
// No dwell inside notifications at the bottom of the screen
|
|
|
|
this._cancelTrayDwell();
|
|
|
|
|
2010-08-31 19:50:18 +00:00
|
|
|
// Don't do anything if the one pixel area at the bottom is hovered over while the tray is hidden.
|
2012-07-08 12:08:56 +00:00
|
|
|
if (this._trayState == State.HIDDEN && this._notificationState == State.HIDDEN)
|
2010-08-31 19:50:18 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Don't do anything if this._useLongerTrayLeftTimeout is true, meaning the notification originally
|
|
|
|
// popped up under the pointer, but this._trayLeftTimeoutId is 0, meaning the pointer didn't leave
|
|
|
|
// the tray yet. We need to check for this case because sometimes _onTrayHoverChanged() gets called
|
|
|
|
// multiple times while this.actor.hover is true.
|
|
|
|
if (this._useLongerTrayLeftTimeout && !this._trayLeftTimeoutId)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._useLongerTrayLeftTimeout = false;
|
2010-03-15 16:20:10 +00:00
|
|
|
if (this._trayLeftTimeoutId) {
|
|
|
|
Mainloop.source_remove(this._trayLeftTimeoutId);
|
|
|
|
this._trayLeftTimeoutId = 0;
|
2010-10-30 19:54:43 +00:00
|
|
|
this._trayLeftMouseX = -1;
|
|
|
|
this._trayLeftMouseY = -1;
|
2010-03-15 16:20:10 +00:00
|
|
|
return;
|
|
|
|
}
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-08-31 19:50:18 +00:00
|
|
|
if (this._showNotificationMouseX >= 0) {
|
|
|
|
let actorAtShowNotificationPosition =
|
|
|
|
global.stage.get_actor_at_pos(Clutter.PickMode.ALL, this._showNotificationMouseX, this._showNotificationMouseY);
|
|
|
|
this._showNotificationMouseX = -1;
|
|
|
|
this._showNotificationMouseY = -1;
|
|
|
|
// Don't set this._pointerInTray to true if the pointer was initially in the area where the notification
|
|
|
|
// popped up. That way we will not be expanding notifications that happen to pop up over the pointer
|
|
|
|
// automatically. Instead, the user is able to expand the notification by mousing away from it and then
|
|
|
|
// mousing back in. Because this is an expected action, we set the boolean flag that indicates that a longer
|
|
|
|
// timeout should be used before popping down the notification.
|
2011-11-02 18:41:46 +00:00
|
|
|
if (this.actor.contains(actorAtShowNotificationPosition)) {
|
2010-08-31 19:50:18 +00:00
|
|
|
this._useLongerTrayLeftTimeout = true;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2010-03-15 16:20:10 +00:00
|
|
|
this._pointerInTray = true;
|
|
|
|
this._updateState();
|
|
|
|
} else {
|
2010-10-30 19:54:43 +00:00
|
|
|
// We record the position of the mouse the moment it leaves the tray. These coordinates are used in
|
|
|
|
// this._onTrayLeftTimeout() to determine if the mouse has moved far enough during the initial timeout for us
|
|
|
|
// to consider that the user intended to leave the tray and therefore hide the tray. If the mouse is still
|
|
|
|
// close to its previous position, we extend the timeout once.
|
|
|
|
let [x, y, mods] = global.get_pointer();
|
|
|
|
this._trayLeftMouseX = x;
|
|
|
|
this._trayLeftMouseY = y;
|
|
|
|
|
2010-08-31 19:50:18 +00:00
|
|
|
// We wait just a little before hiding the message tray in case the user quickly moves the mouse back into it.
|
|
|
|
// We wait for a longer period if the notification popped up where the mouse pointer was already positioned.
|
|
|
|
// That gives the user more time to mouse away from the notification and mouse back in in order to expand it.
|
2012-09-03 14:16:40 +00:00
|
|
|
let timeout = this._useLongerTrayLeftTimeout ? LONGER_HIDE_TIMEOUT * 1000 : 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
|
|
|
},
|
|
|
|
|
2011-09-09 14:56:51 +00:00
|
|
|
_onKeyboardHoverChanged: function(keyboard) {
|
|
|
|
this._pointerInKeyboard = keyboard.hover;
|
|
|
|
|
|
|
|
if (!keyboard.hover) {
|
|
|
|
let event = Clutter.get_current_event();
|
|
|
|
if (event && event.type() == Clutter.EventType.LEAVE) {
|
|
|
|
let into = event.get_related();
|
|
|
|
if (into && this.actor.contains(into)) {
|
|
|
|
// Don't call _updateState, because pointerInTray is
|
|
|
|
// still false
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._updateState();
|
|
|
|
},
|
|
|
|
|
2012-09-08 18:01:30 +00:00
|
|
|
_onKeyboardVisibleChanged: function(layoutManager, keyboardVisible) {
|
2012-10-14 16:57:45 +00:00
|
|
|
let keyboardUnderMessageTray = layoutManager.keyboardIndex == layoutManager.bottomIndex;
|
|
|
|
if (this._keyboardVisible == keyboardVisible &&
|
|
|
|
this._keyboardUnderMesssageTray == keyboardUnderMessageTray)
|
2012-09-22 00:30:58 +00:00
|
|
|
return;
|
|
|
|
|
2012-09-08 18:01:30 +00:00
|
|
|
this._keyboardVisible = keyboardVisible;
|
2012-10-14 16:57:45 +00:00
|
|
|
this._keyboardUnderMessageTray = keyboardUnderMessageTray;
|
2012-09-08 18:01:30 +00:00
|
|
|
|
2012-10-14 16:57:45 +00:00
|
|
|
if (keyboardVisible && keyboardUnderMessageTray)
|
2012-09-08 18:01:30 +00:00
|
|
|
this.actor.add_style_pseudo_class('keyboard');
|
|
|
|
else
|
|
|
|
this.actor.remove_style_pseudo_class('keyboard');
|
|
|
|
|
|
|
|
this._updateState();
|
|
|
|
},
|
|
|
|
|
2012-06-06 22:27:52 +00:00
|
|
|
_onFullscreenChanged: function(obj, state) {
|
|
|
|
this._inFullscreen = state;
|
|
|
|
this._updateState();
|
|
|
|
},
|
|
|
|
|
2011-08-16 12:24:39 +00:00
|
|
|
_onStatusChanged: function(status) {
|
2011-02-19 15:49:56 +00:00
|
|
|
if (status == GnomeSession.PresenceStatus.BUSY) {
|
|
|
|
// remove notification and allow the summary to be closed now
|
|
|
|
this._updateNotificationTimeout(0);
|
|
|
|
this._busy = true;
|
|
|
|
} else if (status != GnomeSession.PresenceStatus.IDLE) {
|
|
|
|
// We preserve the previous value of this._busy if the status turns to IDLE
|
|
|
|
// so that we don't start showing notifications queued during the BUSY state
|
|
|
|
// as the screensaver gets activated.
|
|
|
|
this._busy = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
_onTrayLeftTimeout: function() {
|
2010-10-30 19:54:43 +00:00
|
|
|
let [x, y, mods] = global.get_pointer();
|
|
|
|
// We extend the timeout once if the mouse moved no further than MOUSE_LEFT_ACTOR_THRESHOLD to either side or up.
|
|
|
|
// We don't check how far down the mouse moved because any point above the tray, but below the exit coordinate,
|
|
|
|
// is close to the tray.
|
|
|
|
if (this._trayLeftMouseX > -1 &&
|
|
|
|
y > this._trayLeftMouseY - MOUSE_LEFT_ACTOR_THRESHOLD &&
|
|
|
|
x < this._trayLeftMouseX + MOUSE_LEFT_ACTOR_THRESHOLD &&
|
|
|
|
x > this._trayLeftMouseX - MOUSE_LEFT_ACTOR_THRESHOLD) {
|
|
|
|
this._trayLeftMouseX = -1;
|
|
|
|
this._trayLeftTimeoutId = Mainloop.timeout_add(LONGER_HIDE_TIMEOUT * 1000,
|
|
|
|
Lang.bind(this, this._onTrayLeftTimeout));
|
|
|
|
} else {
|
|
|
|
this._trayLeftTimeoutId = 0;
|
|
|
|
this._useLongerTrayLeftTimeout = false;
|
|
|
|
this._pointerInTray = false;
|
|
|
|
this._pointerInSummary = false;
|
|
|
|
this._updateNotificationTimeout(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
|
|
|
|
2011-02-10 03:45:50 +00:00
|
|
|
_escapeTray: function() {
|
|
|
|
this._unlock();
|
2010-07-21 05:26:25 +00:00
|
|
|
this._pointerInTray = false;
|
|
|
|
this._pointerInSummary = false;
|
2012-02-28 20:04:00 +00:00
|
|
|
this._traySummoned = false;
|
|
|
|
this._setClickedSummaryItem(null);
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._updateNotificationTimeout(0);
|
2010-07-21 05:26:25 +00:00
|
|
|
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
|
|
|
// 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
|
2012-08-27 12:42:22 +00:00
|
|
|
let notificationQueue = this._notificationQueue;
|
2012-05-22 22:02:00 +00:00
|
|
|
let notificationUrgent = notificationQueue.length > 0 && notificationQueue[0].urgency == Urgency.CRITICAL;
|
2012-11-02 17:06:40 +00:00
|
|
|
let notificationForFeedback = notificationQueue.length > 0 && notificationQueue[0].forFeedback;
|
2012-08-27 12:42:22 +00:00
|
|
|
let notificationsLimited = this._busy || this._inFullscreen;
|
2012-11-02 17:06:40 +00:00
|
|
|
let notificationsPending = notificationQueue.length > 0 && (!notificationsLimited || notificationUrgent || notificationForFeedback) && Main.sessionMode.hasNotifications;
|
2012-05-22 22:02:00 +00:00
|
|
|
let nextNotification = notificationQueue.length > 0 ? notificationQueue[0] : null;
|
2010-02-22 22:19:32 +00:00
|
|
|
let notificationPinned = this._pointerInTray && !this._pointerInSummary && !this._notificationRemoved;
|
2012-08-10 20:31:09 +00:00
|
|
|
let notificationExpanded = this._notification && this._notification.expanded;
|
2012-05-22 22:02:00 +00:00
|
|
|
let notificationExpired = this._notificationTimeoutId == 0 &&
|
|
|
|
!(this._notification && this._notification.urgency == Urgency.CRITICAL) &&
|
2012-09-05 16:59:50 +00:00
|
|
|
!(this._notification && this._notification.focused) &&
|
2012-05-22 22:02:00 +00:00
|
|
|
!this._pointerInTray &&
|
|
|
|
!this._locked &&
|
|
|
|
!(this._pointerInKeyboard && notificationExpanded);
|
2012-08-27 12:42:22 +00:00
|
|
|
let notificationLockedOut = !Main.sessionMode.hasNotifications && this._notification;
|
2012-09-06 09:45:30 +00:00
|
|
|
let notificationMustClose = this._notificationRemoved || notificationLockedOut || (notificationExpired && this._userActiveWhileNotificationShown) || this._notificationClosed;
|
2011-02-28 10:09:40 +00:00
|
|
|
let canShowNotification = notificationsPending && this._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
|
|
|
|
|
|
|
if (this._notificationState == State.HIDDEN) {
|
2012-05-22 22:02:00 +00:00
|
|
|
if (canShowNotification) {
|
2012-08-27 12:42:22 +00:00
|
|
|
this._showNotification();
|
2012-05-22 22:02: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
|
|
|
} else if (this._notificationState == State.SHOWN) {
|
2012-05-22 22:02:00 +00:00
|
|
|
if (notificationMustClose)
|
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)
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._expandNotification(false);
|
2010-07-21 05:19:25 +00:00
|
|
|
else if (notificationPinned)
|
|
|
|
this._ensureNotificationFocused();
|
[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
|
|
|
}
|
|
|
|
|
|
|
|
// Summary
|
2011-08-29 15:11:22 +00:00
|
|
|
let summarySummoned = this._pointerInSummary || this._overviewVisible || this._traySummoned;
|
2012-07-30 23:36:20 +00:00
|
|
|
let summaryPinned = this._pointerInTray || summarySummoned || this._locked;
|
2011-02-05 00:13:10 +00:00
|
|
|
let summaryHovered = this._pointerInTray || this._pointerInSummary;
|
2010-02-22 22:19:32 +00:00
|
|
|
|
2012-11-18 01:01:00 +00:00
|
|
|
let notificationsVisible = this._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
|
|
|
let notificationsDone = !notificationsVisible && !notificationsPending;
|
|
|
|
|
2011-02-28 10:09:40 +00:00
|
|
|
let summaryOptionalInOverview = this._overviewVisible && !this._locked && !summaryHovered;
|
|
|
|
let mustHideSummary = (notificationsPending && (notificationUrgent || summaryOptionalInOverview))
|
2012-08-26 14:53:08 +00:00
|
|
|
|| notificationsVisible || !Main.sessionMode.hasNotifications;
|
2011-02-28 10:09:40 +00:00
|
|
|
|
2012-07-30 23:36:20 +00:00
|
|
|
if (this._summaryState == State.HIDDEN && !mustHideSummary && summarySummoned)
|
|
|
|
this._showSummary();
|
|
|
|
else if (this._summaryState == State.SHOWN && (!summaryPinned || mustHideSummary))
|
2012-08-10 18:24:22 +00:00
|
|
|
this._hideSummary();
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2010-02-25 19:42:18 +00:00
|
|
|
// Summary notification
|
2011-02-11 19:43:01 +00:00
|
|
|
let haveClickedSummaryItem = this._clickedSummaryItem != null;
|
2011-03-07 19:03:14 +00:00
|
|
|
let summarySourceIsMainNotificationSource = (haveClickedSummaryItem && this._notification &&
|
|
|
|
this._clickedSummaryItem.source == this._notification.source);
|
2011-02-11 19:43:01 +00:00
|
|
|
let canShowSummaryBoxPointer = this._summaryState == State.SHOWN;
|
2011-03-21 21:43:34 +00:00
|
|
|
// We only have sources with empty notification stacks for legacy tray icons. Currently, we never attempt
|
|
|
|
// to show notifications for legacy tray icons, but this would be necessary if we did.
|
|
|
|
let requestedNotificationStackIsEmpty = (this._clickedSummaryItemMouseButton == 1 && this._clickedSummaryItem.source.notifications.length == 0);
|
|
|
|
let wrongSummaryNotificationStack = (this._clickedSummaryItemMouseButton == 1 &&
|
2012-08-18 23:34:49 +00:00
|
|
|
this._summaryBoxPointer.bin.child != this._clickedSummaryItem.notificationStackWidget);
|
2011-02-11 19:43:01 +00:00
|
|
|
let wrongSummaryRightClickMenu = (this._clickedSummaryItemMouseButton == 3 &&
|
|
|
|
this._summaryBoxPointer.bin.child != this._clickedSummaryItem.rightClickMenu);
|
|
|
|
let wrongSummaryBoxPointer = (haveClickedSummaryItem &&
|
2011-03-21 21:43:34 +00:00
|
|
|
(wrongSummaryNotificationStack || wrongSummaryRightClickMenu));
|
2011-02-11 19:43:01 +00:00
|
|
|
|
|
|
|
if (this._summaryBoxPointerState == State.HIDDEN) {
|
2011-03-21 21:43:34 +00:00
|
|
|
if (haveClickedSummaryItem && !summarySourceIsMainNotificationSource && canShowSummaryBoxPointer && !requestedNotificationStackIsEmpty)
|
2011-02-11 19:43:01 +00:00
|
|
|
this._showSummaryBoxPointer();
|
|
|
|
} else if (this._summaryBoxPointerState == State.SHOWN) {
|
2011-12-14 20:21:04 +00:00
|
|
|
if (!haveClickedSummaryItem || !canShowSummaryBoxPointer || wrongSummaryBoxPointer || mustHideSummary) {
|
2011-02-11 19:43:01 +00:00
|
|
|
this._hideSummaryBoxPointer();
|
2011-12-14 20:21:04 +00:00
|
|
|
if (wrongSummaryBoxPointer)
|
|
|
|
this._showSummaryBoxPointer();
|
|
|
|
}
|
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
|
|
|
// Tray itself
|
|
|
|
let trayIsVisible = (this._trayState == State.SHOWING ||
|
|
|
|
this._trayState == State.SHOWN);
|
2012-07-08 12:08:56 +00:00
|
|
|
let trayShouldBeVisible = (this._summaryState == State.SHOWING ||
|
[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._summaryState == State.SHOWN);
|
|
|
|
if (!trayIsVisible && trayShouldBeVisible)
|
2012-10-02 23:24:56 +00:00
|
|
|
trayShouldBeVisible = this._showTray();
|
[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 (trayIsVisible && !trayShouldBeVisible)
|
|
|
|
this._hideTray();
|
2012-09-08 18:01:30 +00:00
|
|
|
|
|
|
|
// Desktop clone
|
|
|
|
let desktopCloneIsVisible = (this._desktopCloneState == State.SHOWING ||
|
|
|
|
this._desktopCloneState == State.SHOWN);
|
|
|
|
let desktopCloneShouldBeVisible = (trayShouldBeVisible &&
|
|
|
|
!this._overviewVisible &&
|
2012-10-14 16:57:45 +00:00
|
|
|
(!this._keyboardVisible || !this._keyboardUnderMessageTray));
|
2012-09-08 18:01:30 +00:00
|
|
|
|
|
|
|
if (!desktopCloneIsVisible && desktopCloneShouldBeVisible) {
|
|
|
|
this._showDesktopClone();
|
|
|
|
} else if (desktopCloneIsVisible && !desktopCloneShouldBeVisible) {
|
2012-10-14 16:57:45 +00:00
|
|
|
this._hideDesktopClone (this._keyboardVisible && this._keyboardUnderMessageTray);
|
2012-09-08 18:01:30 +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
|
|
|
},
|
|
|
|
|
|
|
|
_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];
|
|
|
|
|
2012-11-25 22:51:44 +00:00
|
|
|
// Remove other tweens that could mess with the state machine
|
|
|
|
Tweener.removeTweens(actor);
|
[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
|
|
|
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() {
|
2012-08-21 23:40:11 +00:00
|
|
|
// Don't actually take a modal grab in the overview.
|
|
|
|
// Just add something to the grab stack that we can
|
|
|
|
// pop later.
|
2012-09-20 22:31:24 +00:00
|
|
|
let modal = !this._overviewVisible;
|
|
|
|
|
2012-08-21 23:40:11 +00:00
|
|
|
if (!this._grabHelper.grab({ actor: this.actor,
|
2012-09-20 22:31:24 +00:00
|
|
|
modal: modal,
|
2012-08-21 23:40:11 +00:00
|
|
|
onUngrab: Lang.bind(this, this._escapeTray) })) {
|
|
|
|
this._traySummoned = false;
|
2012-10-02 23:24:56 +00:00
|
|
|
return false;
|
2012-08-21 23:40:11 +00:00
|
|
|
}
|
|
|
|
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this.actor, '_trayState', State.SHOWN,
|
2011-07-25 18:25:51 +00:00
|
|
|
{ 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 20:31:12 +00:00
|
|
|
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
|
|
|
});
|
2012-08-08 20:48:17 +00:00
|
|
|
|
2012-10-23 20:40:09 +00:00
|
|
|
if (!this._overviewVisible)
|
2012-09-08 18:01:30 +00:00
|
|
|
this._lightbox.show();
|
2012-10-02 23:24:56 +00:00
|
|
|
|
|
|
|
return true;
|
2012-09-08 18:01:30 +00:00
|
|
|
},
|
2012-08-08 20:48:17 +00:00
|
|
|
|
2012-10-02 22:14:14 +00:00
|
|
|
_updateDesktopCloneClip: function() {
|
|
|
|
let geometry = this._bottomMonitorGeometry;
|
|
|
|
let progress = -Math.round(this._desktopClone.y);
|
|
|
|
this._desktopClone.set_clip(geometry.x,
|
|
|
|
geometry.y + progress,
|
|
|
|
geometry.width,
|
|
|
|
geometry.height - progress);
|
|
|
|
},
|
|
|
|
|
2012-09-08 18:01:30 +00:00
|
|
|
_showDesktopClone: function() {
|
2012-08-08 20:48:17 +00:00
|
|
|
let bottomMonitor = Main.layoutManager.bottomMonitor;
|
2012-10-02 22:14:14 +00:00
|
|
|
this._bottomMonitorGeometry = { x: bottomMonitor.x,
|
|
|
|
y: bottomMonitor.y,
|
|
|
|
width: bottomMonitor.width,
|
|
|
|
height: bottomMonitor.height };
|
|
|
|
|
2012-09-08 18:01:30 +00:00
|
|
|
if (this._desktopClone)
|
|
|
|
this._desktopClone.destroy();
|
2012-10-02 22:14:14 +00:00
|
|
|
this._desktopClone = new Clutter.Clone({ source: global.window_group, clip: new Clutter.Geometry(this._bottomMonitorGeometry) });
|
2012-08-08 20:48:17 +00:00
|
|
|
Main.uiGroup.insert_child_above(this._desktopClone, global.window_group);
|
|
|
|
this._desktopClone.x = 0;
|
|
|
|
this._desktopClone.y = 0;
|
|
|
|
this._desktopClone.show();
|
|
|
|
|
2012-09-08 18:01:30 +00:00
|
|
|
this._tween(this._desktopClone, '_desktopCloneState', State.SHOWN,
|
|
|
|
{ y: -this.actor.height,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad',
|
2012-10-02 22:14:14 +00:00
|
|
|
onUpdate: Lang.bind(this, this._updateDesktopCloneClip)
|
2012-09-08 18:01:30 +00:00
|
|
|
});
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2010-01-28 18:39:00 +00:00
|
|
|
_hideTray: function() {
|
2012-10-25 20:29:04 +00:00
|
|
|
// Having the summary item animate out while sliding down the tray
|
|
|
|
// is distracting, so hide it immediately in case it was visible.
|
|
|
|
this._summaryBoxPointer.actor.hide();
|
|
|
|
|
2010-05-13 19:46:04 +00:00
|
|
|
this._tween(this.actor, '_trayState', State.HIDDEN,
|
2012-07-08 12:08:56 +00:00
|
|
|
{ y: 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
|
|
|
time: ANIMATION_TIME,
|
2012-11-26 16:52:42 +00:00
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: this._onTrayHidden,
|
|
|
|
onCompleteScope: this
|
[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
|
|
|
});
|
2012-08-08 20:48:17 +00:00
|
|
|
|
2012-09-14 17:23:21 +00:00
|
|
|
// Note that we might have entered here without a grab,
|
|
|
|
// which would happen if GrabHelper ungrabbed for us.
|
|
|
|
// This is a no-op in that case.
|
|
|
|
this._grabHelper.ungrab({ actor: this.actor });
|
2012-09-22 18:24:08 +00:00
|
|
|
this._lightbox.hide();
|
2012-09-08 18:01:30 +00:00
|
|
|
},
|
|
|
|
|
2012-11-26 16:52:42 +00:00
|
|
|
_onTrayHidden: function() {
|
|
|
|
if (!this._overviewVisible)
|
|
|
|
this.actor.remove_style_pseudo_class('overview');
|
|
|
|
},
|
|
|
|
|
2012-09-08 18:01:30 +00:00
|
|
|
_hideDesktopClone: function(now) {
|
|
|
|
this._tween(this._desktopClone, '_desktopCloneState', State.HIDDEN,
|
|
|
|
{ y: 0,
|
2012-10-02 22:17:16 +00:00
|
|
|
time: now ? 0 : ANIMATION_TIME,
|
2012-09-08 18:01:30 +00:00
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: Lang.bind(this, function() {
|
|
|
|
this._desktopClone.destroy();
|
|
|
|
this._desktopClone = null;
|
2012-10-02 22:14:14 +00:00
|
|
|
this._bottomMonitorGeometry = null;
|
2012-09-08 18:01:30 +00:00
|
|
|
}),
|
2012-10-02 22:14:14 +00:00
|
|
|
onUpdate: Lang.bind(this, this._updateDesktopCloneClip)
|
2012-09-08 18:01:30 +00:00
|
|
|
});
|
2010-01-13 20:05:20 +00:00
|
|
|
},
|
|
|
|
|
2012-08-20 04:06:50 +00:00
|
|
|
_onIdleMonitorBecameActive: function() {
|
|
|
|
this.idleMonitor.disconnect(this._idleMonitorBecameActiveId);
|
|
|
|
this._idleMonitorBecameActiveId = 0;
|
|
|
|
|
2012-07-30 23:14:49 +00:00
|
|
|
this._userActiveWhileNotificationShown = true;
|
2012-08-20 04:06:50 +00:00
|
|
|
this._updateNotificationTimeout(2000);
|
2012-07-30 23:14:49 +00:00
|
|
|
this._updateState();
|
2012-03-15 02:50:58 +00:00
|
|
|
},
|
|
|
|
|
2012-08-27 12:42:22 +00:00
|
|
|
_showNotification: function() {
|
|
|
|
this._notification = this._notificationQueue.shift();
|
2012-08-20 04:06:50 +00:00
|
|
|
|
|
|
|
let userIdle = this.idleMonitor.get_idletime() > IDLE_TIME;
|
|
|
|
if (userIdle) {
|
|
|
|
this._userActiveWhileNotificationShown = false;
|
|
|
|
this._idleMonitorBecameActiveId = this.idleMonitor.connect('became-active', Lang.bind(this, this._onIdleMonitorBecameActive));
|
|
|
|
} else {
|
|
|
|
this._userActiveWhileNotificationShown = true;
|
|
|
|
}
|
|
|
|
|
2010-12-16 20:49:47 +00:00
|
|
|
this._notificationClickedId = this._notification.connect('done-displaying',
|
2011-02-10 03:45:50 +00:00
|
|
|
Lang.bind(this, this._escapeTray));
|
2012-12-22 15:02:19 +00:00
|
|
|
this._notificationUnfocusedId = this._notification.connect('unfocused', Lang.bind(this, function() {
|
2012-09-05 16:59:50 +00:00
|
|
|
this._updateState();
|
|
|
|
}));
|
2010-02-01 20:23:49 +00:00
|
|
|
this._notificationBin.child = this._notification.actor;
|
2010-01-13 20:05:20 +00:00
|
|
|
|
2012-08-20 19:51:52 +00:00
|
|
|
this._notificationWidget.opacity = 0;
|
|
|
|
this._notificationWidget.y = 0;
|
|
|
|
this._notificationWidget.show();
|
2010-01-13 20:05:20 +00:00
|
|
|
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._updateShowingNotification();
|
2010-04-29 19:06:51 +00:00
|
|
|
|
|
|
|
let [x, y, mods] = global.get_pointer();
|
2010-08-31 19:50:18 +00:00
|
|
|
// We save the position of the mouse at the time when we started showing the notification
|
|
|
|
// in order to determine if the notification popped up under it. We make that check if
|
|
|
|
// the user starts moving the mouse and _onTrayHoverChanged() gets called. We don't
|
|
|
|
// expand the notification if it just happened to pop up under the mouse unless the user
|
|
|
|
// explicitly mouses away from it and then mouses back in.
|
|
|
|
this._showNotificationMouseX = x;
|
|
|
|
this._showNotificationMouseY = y;
|
2012-09-03 16:29:39 +00:00
|
|
|
// We save the coordinates of the mouse at the time when we started showing the notification
|
|
|
|
// and then we update it in _notificationTimeout(). We don't pop down the notification if
|
|
|
|
// the mouse is moving towards it or within it.
|
|
|
|
this._lastSeenMouseX = x;
|
2010-04-29 19:06:51 +00:00
|
|
|
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
|
|
|
},
|
|
|
|
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
_updateShowingNotification: function() {
|
2012-08-06 15:28:55 +00:00
|
|
|
this._notification.acknowledged = true;
|
|
|
|
|
2012-10-16 15:08:54 +00:00
|
|
|
// We auto-expand notifications with CRITICAL urgency, or for which the relevant setting
|
|
|
|
// is on in the control center.
|
|
|
|
if (this._notification.urgency == Urgency.CRITICAL ||
|
|
|
|
this._notification.source.policy.forceExpanded)
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._expandNotification(true);
|
2011-01-13 01:05:36 +00:00
|
|
|
|
|
|
|
// We tween all notifications to full opacity. This ensures that both new notifications and
|
|
|
|
// notifications that might have been in the process of hiding get full opacity.
|
|
|
|
//
|
2012-11-30 15:05:38 +00:00
|
|
|
// We tween any notification showing in the banner mode to the appropriate height
|
|
|
|
// (which is banner height or expanded height, depending on the notification state)
|
2011-01-13 01:05:36 +00:00
|
|
|
// This ensures that both new notifications and notifications in the banner mode that might
|
2012-11-30 15:05:38 +00:00
|
|
|
// have been in the process of hiding are shown with the correct height.
|
2011-01-13 01:05:36 +00:00
|
|
|
//
|
|
|
|
// We use this._showNotificationCompleted() onComplete callback to extend the time the updated
|
|
|
|
// notification is being shown.
|
2012-11-30 15:05:38 +00:00
|
|
|
|
2011-01-13 01:05:36 +00:00
|
|
|
let tweenParams = { opacity: 255,
|
2012-11-30 15:05:38 +00:00
|
|
|
y: -this._notificationWidget.height,
|
2011-01-13 01:05:36 +00:00
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: this._showNotificationCompleted,
|
|
|
|
onCompleteScope: this
|
|
|
|
};
|
|
|
|
|
2012-08-20 19:51:52 +00:00
|
|
|
this._tween(this._notificationWidget, '_notificationState', State.SHOWN, tweenParams);
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +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
|
|
|
_showNotificationCompleted: function() {
|
2011-01-11 21:33:03 +00:00
|
|
|
if (this._notification.urgency != Urgency.CRITICAL)
|
|
|
|
this._updateNotificationTimeout(NOTIFICATION_TIMEOUT * 1000);
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_updateNotificationTimeout: function(timeout) {
|
|
|
|
if (this._notificationTimeoutId) {
|
|
|
|
Mainloop.source_remove(this._notificationTimeoutId);
|
|
|
|
this._notificationTimeoutId = 0;
|
|
|
|
}
|
|
|
|
if (timeout > 0)
|
|
|
|
this._notificationTimeoutId =
|
|
|
|
Mainloop.timeout_add(timeout,
|
|
|
|
Lang.bind(this, this._notificationTimeout));
|
[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
|
|
|
},
|
|
|
|
|
|
|
|
_notificationTimeout: function() {
|
2010-04-29 19:06:51 +00:00
|
|
|
let [x, y, mods] = global.get_pointer();
|
2011-07-25 18:25:51 +00:00
|
|
|
if (y > this._lastSeenMouseY + 10 && !this.actor.hover) {
|
2010-04-29 19:06:51 +00:00
|
|
|
// 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.)
|
2012-09-03 16:29:39 +00:00
|
|
|
this._updateNotificationTimeout(1000);
|
|
|
|
} else if (this._useLongerTrayLeftTimeout && !this._trayLeftTimeoutId &&
|
|
|
|
(x != this._lastSeenMouseX || y != this._lastSeenMouseY)) {
|
|
|
|
// Refresh the timeout if the notification originally
|
|
|
|
// popped up under the pointer, and the pointer is hovering
|
|
|
|
// inside it.
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._updateNotificationTimeout(1000);
|
2010-04-29 19:06:51 +00:00
|
|
|
} else {
|
|
|
|
this._notificationTimeoutId = 0;
|
|
|
|
this._updateState();
|
|
|
|
}
|
|
|
|
|
2012-09-03 16:29:39 +00:00
|
|
|
this._lastSeenMouseX = x;
|
|
|
|
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
|
|
|
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() {
|
2012-09-10 22:44:28 +00:00
|
|
|
// HACK!
|
|
|
|
// There seems to be a reentrancy issue in calling .ungrab() here,
|
|
|
|
// which causes _updateState to be called before _notificationState
|
|
|
|
// becomes HIDING. That hides the notification again, nullifying the
|
|
|
|
// object but not setting _notificationState (and that's the weird part)
|
|
|
|
// As then _notificationState is stuck into SHOWN but _notification
|
|
|
|
// is null, every new _updateState fails and the message tray is
|
|
|
|
// lost forever.
|
|
|
|
//
|
|
|
|
// See more at https://bugzilla.gnome.org/show_bug.cgi?id=683986
|
|
|
|
this._notificationState = State.HIDING;
|
|
|
|
|
2012-02-28 20:04:00 +00:00
|
|
|
this._grabHelper.ungrab({ actor: this._notification.actor });
|
|
|
|
|
2012-08-20 04:06:50 +00:00
|
|
|
if (this._idleMonitorBecameActiveId) {
|
|
|
|
this.idleMonitor.disconnect(this._idleMonitorBecameActiveId);
|
|
|
|
this._idleMonitorBecameActiveId = 0;
|
|
|
|
}
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
if (this._notificationExpandedId) {
|
|
|
|
this._notification.disconnect(this._notificationExpandedId);
|
|
|
|
this._notificationExpandedId = 0;
|
2010-02-22 19:23:36 +00:00
|
|
|
}
|
2013-01-28 11:09:17 +00:00
|
|
|
if (this._notificationClickedId) {
|
|
|
|
this._notification.disconnect(this._notificationClickedId);
|
|
|
|
this._notificationClickedId = 0;
|
|
|
|
}
|
|
|
|
if (this._notificationUnfocusedId) {
|
|
|
|
this._notification.disconnect(this._notificationUnfocusedId);
|
|
|
|
this._notificationUnfocusedId = 0;
|
|
|
|
}
|
2010-02-22 19:23:36 +00:00
|
|
|
|
2012-08-19 22:21:50 +00:00
|
|
|
if (this._notificationRemoved) {
|
2012-11-22 20:28:54 +00:00
|
|
|
Tweener.removeTweens(this._notificationWidget);
|
2012-08-20 19:51:52 +00:00
|
|
|
this._notificationWidget.y = this.actor.height;
|
|
|
|
this._notificationWidget.opacity = 0;
|
2012-08-19 22:21:50 +00:00
|
|
|
this._notificationState = State.HIDDEN;
|
|
|
|
this._hideNotificationCompleted();
|
|
|
|
} else {
|
2012-08-20 19:51:52 +00:00
|
|
|
this._tween(this._notificationWidget, '_notificationState', State.HIDDEN,
|
2012-08-19 22:21:50 +00:00
|
|
|
{ y: this.actor.height,
|
|
|
|
opacity: 0,
|
|
|
|
time: ANIMATION_TIME,
|
|
|
|
transition: 'easeOutQuad',
|
|
|
|
onComplete: this._hideNotificationCompleted,
|
|
|
|
onCompleteScope: this
|
|
|
|
});
|
2012-08-18 23:34:49 +00:00
|
|
|
|
2012-08-19 22:21:50 +00:00
|
|
|
}
|
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() {
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
this._notification.collapseCompleted();
|
2013-01-28 11:09:17 +00:00
|
|
|
|
2010-12-15 21:30:50 +00:00
|
|
|
let notification = this._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 20:31:12 +00:00
|
|
|
this._notification = null;
|
2010-12-15 21:30:50 +00:00
|
|
|
if (notification.isTransient)
|
2011-01-27 23:26:53 +00:00
|
|
|
notification.destroy(NotificationDestroyedReason.EXPIRED);
|
2012-12-22 15:02:19 +00:00
|
|
|
|
|
|
|
this._notificationRemoved = false;
|
|
|
|
this._closeButton.hide();
|
|
|
|
this._pointerInTray = false;
|
|
|
|
this.actor.hover = false; // Clutter doesn't emit notify::hover when actors move
|
|
|
|
this._notificationBin.child = null;
|
|
|
|
this._notificationWidget.hide();
|
[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
|
|
|
},
|
|
|
|
|
2012-11-08 14:24:40 +00:00
|
|
|
_expandActiveNotification: function() {
|
|
|
|
if (!this._notification)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._expandNotification(false);
|
|
|
|
},
|
|
|
|
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
_expandNotification: function(autoExpanding) {
|
|
|
|
// Don't grab focus in notifications that are auto-expanded.
|
|
|
|
if (!autoExpanding)
|
2012-02-28 20:04:00 +00:00
|
|
|
this._grabHelper.grab({ actor: this._notification.actor,
|
|
|
|
grabFocus: true });
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
|
|
|
|
if (!this._notificationExpandedId)
|
|
|
|
this._notificationExpandedId =
|
|
|
|
this._notification.connect('expanded',
|
|
|
|
Lang.bind(this, this._onNotificationExpanded));
|
|
|
|
// Don't animate changes in notifications that are auto-expanding.
|
|
|
|
this._notification.expand(!autoExpanding);
|
|
|
|
},
|
|
|
|
|
2011-05-23 21:15:08 +00:00
|
|
|
_onNotificationExpanded: function() {
|
2012-08-20 19:51:52 +00:00
|
|
|
let expandedY = - this._notificationWidget.height;
|
2012-08-18 23:34:49 +00:00
|
|
|
this._closeButton.show();
|
2011-05-23 21:15:08 +00:00
|
|
|
|
|
|
|
// Don't animate the notification to its new position if it has shrunk:
|
|
|
|
// there will be a very visible "gap" that breaks the illusion.
|
2012-08-20 19:51:52 +00:00
|
|
|
if (this._notificationWidget.y < expandedY) {
|
|
|
|
this._notificationWidget.y = expandedY;
|
2012-08-18 23:34:49 +00:00
|
|
|
} else if (this._notification.y != expandedY) {
|
2012-11-19 17:59:39 +00:00
|
|
|
// Tween also opacity here, to override a possible tween that's
|
2012-11-22 20:28:54 +00:00
|
|
|
// currently hiding the notification.
|
2012-08-20 19:51:52 +00:00
|
|
|
this._tween(this._notificationWidget, '_notificationState', State.SHOWN,
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
{ y: expandedY,
|
2012-11-19 17:59:39 +00:00
|
|
|
opacity: 255,
|
[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
|
|
|
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
|
|
|
});
|
2012-08-18 23:34:49 +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
|
|
|
|
2010-07-21 05:19:25 +00:00
|
|
|
// We use this function to grab focus when the user moves the pointer
|
2011-01-04 09:34:57 +00:00
|
|
|
// to a notification with CRITICAL urgency that was already auto-expanded.
|
2010-07-21 05:19:25 +00:00
|
|
|
_ensureNotificationFocused: function() {
|
2012-02-28 20:04:00 +00:00
|
|
|
this._grabHelper.grab({ actor: this._notification.actor,
|
|
|
|
grabFocus: true });
|
2010-07-21 05:19:25 +00:00
|
|
|
},
|
|
|
|
|
2012-07-30 23:36:20 +00:00
|
|
|
_showSummary: function() {
|
2012-09-05 00:26:09 +00:00
|
|
|
this._summary.opacity = 0;
|
|
|
|
this._tween(this._summary, '_summaryState', State.SHOWN,
|
|
|
|
{ opacity: 255,
|
[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
|
|
|
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-28 18:39:00 +00:00
|
|
|
_hideSummary: function() {
|
2012-09-05 00:26:09 +00:00
|
|
|
this._tween(this._summary, '_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,
|
2011-02-05 00:13:10 +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-25 19:42:18 +00:00
|
|
|
},
|
|
|
|
|
2012-11-30 15:14:50 +00:00
|
|
|
_onSourceDoneDisplayingContent: function(source, closeTray) {
|
|
|
|
if (closeTray) {
|
|
|
|
this._escapeTray();
|
|
|
|
} else {
|
|
|
|
this._setClickedSummaryItem(null);
|
|
|
|
this._updateState();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
_showSummaryBoxPointer: function() {
|
2011-03-21 21:43:34 +00:00
|
|
|
this._summaryBoxPointerItem = this._clickedSummaryItem;
|
|
|
|
this._summaryBoxPointerContentUpdatedId = this._summaryBoxPointerItem.connect('content-updated',
|
2011-09-08 05:15:09 +00:00
|
|
|
Lang.bind(this, this._onSummaryBoxPointerContentUpdated));
|
2012-09-05 15:32:45 +00:00
|
|
|
this._sourceDoneDisplayingId = this._summaryBoxPointerItem.source.connect('done-displaying-content',
|
2012-11-30 15:14:50 +00:00
|
|
|
Lang.bind(this, this._onSourceDoneDisplayingContent));
|
2012-09-05 15:34:01 +00:00
|
|
|
|
|
|
|
let hasRightClickMenu = this._summaryBoxPointerItem.rightClickMenu != null;
|
|
|
|
if (this._clickedSummaryItemMouseButton == 1 || !hasRightClickMenu) {
|
2012-08-06 15:28:55 +00:00
|
|
|
let newQueue = [];
|
|
|
|
for (let i = 0; i < this._notificationQueue.length; i++) {
|
|
|
|
let notification = this._notificationQueue[i];
|
|
|
|
let sameSource = this._summaryBoxPointerItem.source == notification.source;
|
|
|
|
if (sameSource)
|
|
|
|
notification.acknowledged = true;
|
|
|
|
else
|
|
|
|
newQueue.push(notification);
|
|
|
|
}
|
|
|
|
this._notificationQueue = newQueue;
|
|
|
|
|
2012-08-18 23:34:49 +00:00
|
|
|
this._summaryBoxPointer.bin.child = this._summaryBoxPointerItem.notificationStackWidget;
|
2012-09-19 03:36:05 +00:00
|
|
|
|
|
|
|
let closeButton = this._summaryBoxPointerItem.closeButton;
|
|
|
|
closeButton.show();
|
2012-09-19 03:37:24 +00:00
|
|
|
this._summaryBoxPointerCloseClickedId = closeButton.connect('clicked', Lang.bind(this, this._hideSummaryBoxPointer));
|
2011-03-21 21:43:34 +00:00
|
|
|
this._summaryBoxPointerItem.prepareNotificationStackForShowing();
|
2011-02-11 19:43:01 +00:00
|
|
|
} else if (this._clickedSummaryItemMouseButton == 3) {
|
|
|
|
this._summaryBoxPointer.bin.child = this._clickedSummaryItem.rightClickMenu;
|
2012-11-21 00:29:45 +00:00
|
|
|
this._summaryBoxPointerCloseClickedId = 0;
|
2011-02-11 19:43:01 +00:00
|
|
|
}
|
2010-02-25 19:42:18 +00:00
|
|
|
|
2012-02-28 20:04:00 +00:00
|
|
|
this._grabHelper.grab({ actor: this._summaryBoxPointer.bin.child,
|
|
|
|
grabFocus: true,
|
|
|
|
onUngrab: Lang.bind(this, this._onSummaryBoxPointerUngrabbed) });
|
|
|
|
this._lock();
|
2010-10-19 19:17:36 +00:00
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
this._summaryBoxPointer.actor.opacity = 0;
|
|
|
|
this._summaryBoxPointer.actor.show();
|
|
|
|
this._adjustSummaryBoxPointerPosition();
|
2010-10-19 19:17:36 +00:00
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
this._summaryBoxPointerState = State.SHOWING;
|
2011-03-22 01:22:02 +00:00
|
|
|
this._clickedSummaryItem.actor.add_style_pseudo_class('selected');
|
2012-06-15 17:16:10 +00:00
|
|
|
this._summaryBoxPointer.show(BoxPointer.PopupAnimation.FULL, Lang.bind(this, function() {
|
2011-02-11 19:43:01 +00:00
|
|
|
this._summaryBoxPointerState = State.SHOWN;
|
2010-10-19 19:17:36 +00:00
|
|
|
}));
|
|
|
|
},
|
|
|
|
|
2011-09-08 05:15:09 +00:00
|
|
|
_onSummaryBoxPointerContentUpdated: function() {
|
2012-05-30 00:36:45 +00:00
|
|
|
if (this._summaryBoxPointerItem.notificationStack.get_n_children() == 0)
|
2011-09-08 05:15:09 +00:00
|
|
|
this._hideSummaryBoxPointer();
|
|
|
|
this._adjustSummaryBoxPointerPosition();
|
|
|
|
},
|
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
_adjustSummaryBoxPointerPosition: function() {
|
2010-10-19 19:17:36 +00:00
|
|
|
if (!this._clickedSummaryItem)
|
|
|
|
return;
|
|
|
|
|
2012-06-14 17:46:21 +00:00
|
|
|
this._summaryBoxPointer.setPosition(this._clickedSummaryItem.actor, 0);
|
2010-10-19 19:17:36 +00:00
|
|
|
},
|
|
|
|
|
2012-02-28 20:04:00 +00:00
|
|
|
_setClickedSummaryItem: function(item, button) {
|
|
|
|
if (item == this._clickedSummaryItem &&
|
|
|
|
button == this._clickedSummaryItemMouseButton)
|
2012-08-16 17:54:03 +00:00
|
|
|
return false;
|
2012-02-28 20:04:00 +00:00
|
|
|
|
|
|
|
if (this._clickedSummaryItem) {
|
|
|
|
this._clickedSummaryItem.actor.remove_style_pseudo_class('selected');
|
2010-10-19 19:17:36 +00:00
|
|
|
this._clickedSummaryItem.actor.disconnect(this._clickedSummaryItemAllocationChangedId);
|
|
|
|
this._summary.disconnect(this._summaryMotionId);
|
2011-09-09 13:45:39 +00:00
|
|
|
Main.layoutManager.trayBox.disconnect(this._trayMotionId);
|
2010-10-19 19:17:36 +00:00
|
|
|
this._clickedSummaryItemAllocationChangedId = 0;
|
|
|
|
this._summaryMotionId = 0;
|
2011-09-09 13:45:39 +00:00
|
|
|
this._trayMotionId = 0;
|
2010-10-19 19:17:36 +00:00
|
|
|
}
|
|
|
|
|
2012-02-28 20:04:00 +00:00
|
|
|
this._clickedSummaryItem = item;
|
|
|
|
this._clickedSummaryItemMouseButton = button;
|
|
|
|
|
|
|
|
if (this._clickedSummaryItem) {
|
|
|
|
this._clickedSummaryItem.source.emit('summary-item-clicked', button);
|
|
|
|
this._clickedSummaryItem.actor.add_style_pseudo_class('selected');
|
|
|
|
this._clickedSummaryItemAllocationChangedId =
|
|
|
|
this._clickedSummaryItem.actor.connect('allocation-changed',
|
|
|
|
Lang.bind(this, this._adjustSummaryBoxPointerPosition));
|
|
|
|
// _clickedSummaryItem.actor can change absolute position without changing allocation
|
|
|
|
this._summaryMotionId = this._summary.connect('allocation-changed',
|
|
|
|
Lang.bind(this, this._adjustSummaryBoxPointerPosition));
|
|
|
|
this._trayMotionId = Main.layoutManager.trayBox.connect('notify::anchor-y',
|
|
|
|
Lang.bind(this, this._adjustSummaryBoxPointerPosition));
|
2011-09-08 05:15:09 +00:00
|
|
|
}
|
2012-08-16 17:54:03 +00:00
|
|
|
|
|
|
|
return true;
|
2012-02-28 20:04:00 +00:00
|
|
|
},
|
2011-09-08 05:15:09 +00:00
|
|
|
|
2012-09-15 16:56:01 +00:00
|
|
|
_onSummaryBoxPointerKeyPress: function(actor, event) {
|
|
|
|
switch (event.get_key_symbol()) {
|
|
|
|
case Clutter.KEY_Down:
|
|
|
|
case Clutter.KEY_Escape:
|
|
|
|
this._setClickedSummaryItem(null);
|
|
|
|
this._updateState();
|
|
|
|
return true;
|
|
|
|
case Clutter.KEY_Delete:
|
|
|
|
this._clickedSummaryItem.source.destroy();
|
|
|
|
this._escapeTray();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
2012-02-28 20:04:00 +00:00
|
|
|
_onSummaryBoxPointerUngrabbed: function() {
|
2011-03-26 07:25:25 +00:00
|
|
|
this._summaryBoxPointerState = State.HIDING;
|
2013-01-28 11:01:36 +00:00
|
|
|
|
|
|
|
if (this._summaryBoxPointerContentUpdatedId) {
|
|
|
|
this._summaryBoxPointerItem.disconnect(this._summaryBoxPointerContentUpdatedId);
|
|
|
|
this._summaryBoxPointerContentUpdatedId = 0;
|
|
|
|
}
|
|
|
|
if (this._summaryBoxPointerCloseClickedId != 0) {
|
|
|
|
this._summaryBoxPointerItem.closeButton.disconnect(this._summaryBoxPointerCloseClickedId);
|
|
|
|
this._summaryBoxPointerCloseClickedId = 0;
|
|
|
|
}
|
|
|
|
if (this._sourceDoneDisplayingId) {
|
|
|
|
this._summaryBoxPointerItem.source.disconnect(this._sourceDoneDisplayingId);
|
|
|
|
this._sourceDoneDisplayingId = 0;
|
|
|
|
}
|
|
|
|
|
2012-02-28 20:04:00 +00:00
|
|
|
this._unlock();
|
2010-02-25 19:42:18 +00:00
|
|
|
|
2011-03-26 07:25:25 +00:00
|
|
|
if (this._summaryBoxPointerItem.source.notifications.length == 0) {
|
|
|
|
this._summaryBoxPointer.actor.hide();
|
|
|
|
this._hideSummaryBoxPointerCompleted();
|
|
|
|
} else {
|
2012-10-04 16:09:01 +00:00
|
|
|
if (global.stage.key_focus &&
|
|
|
|
!this.actor.contains(global.stage.key_focus))
|
|
|
|
this._setClickedSummaryItem(null);
|
2012-06-15 17:16:10 +00:00
|
|
|
this._summaryBoxPointer.hide(BoxPointer.PopupAnimation.FULL, Lang.bind(this, this._hideSummaryBoxPointerCompleted));
|
2011-03-26 07:25:25 +00:00
|
|
|
}
|
2011-02-11 19:43:01 +00:00
|
|
|
},
|
|
|
|
|
2012-02-28 20:04:00 +00:00
|
|
|
_hideSummaryBoxPointer: function() {
|
|
|
|
this._grabHelper.ungrab({ actor: this._summaryBoxPointer.bin.child });
|
|
|
|
},
|
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
_hideSummaryBoxPointerCompleted: function() {
|
2012-08-18 23:34:49 +00:00
|
|
|
let doneShowingNotificationStack = (this._summaryBoxPointer.bin.child == this._summaryBoxPointerItem.notificationStackWidget);
|
2011-03-21 21:43:34 +00:00
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
this._summaryBoxPointerState = State.HIDDEN;
|
|
|
|
this._summaryBoxPointer.bin.child = null;
|
2011-03-21 21:43:34 +00:00
|
|
|
|
|
|
|
let sourceNotificationStackDoneShowing = null;
|
|
|
|
if (doneShowingNotificationStack) {
|
|
|
|
this._summaryBoxPointerItem.doneShowingNotificationStack();
|
|
|
|
sourceNotificationStackDoneShowing = this._summaryBoxPointerItem.source;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._summaryBoxPointerItem = null;
|
|
|
|
|
|
|
|
if (sourceNotificationStackDoneShowing) {
|
|
|
|
if (sourceNotificationStackDoneShowing.isTransient && !this._reNotifyAfterHideNotification)
|
|
|
|
sourceNotificationStackDoneShowing.destroy(NotificationDestroyedReason.EXPIRED);
|
|
|
|
if (this._reNotifyAfterHideNotification) {
|
|
|
|
this._onNotify(this._reNotifyAfterHideNotification.source, this._reNotifyAfterHideNotification);
|
|
|
|
this._reNotifyAfterHideNotification = null;
|
2011-02-11 19:43:01 +00:00
|
|
|
}
|
Fix various details of how notifications are shown
This patch ensures the following notifications behavior:
- Urgent notifications that have long title or banner text are auto-expanded
correctly.
- Single-line notifications that have _expandNotification() called (e.g.
because the user mouses over to them), are treated as expanded, which means
they get fully expanded if they are updated with more content and the user
can escape them.
- The position of expanded notifications is updated when they are updated.
- Notification banner is shown again on the first line if it can fully fit
there after a notification is updated, even if it was previously hidden
because the notification was expanded and the old banner did not fully fit.
- New notifications are immediately hidden if the user mouses away from them.
- If a new notification is updated while it is shown, we extend the time it
will be shown.
- If a new notification is updated while it is hiding, we stop hiding it and
show it again.
- If a summary notification is updated while it is hiding, we let it finish
hiding and show a new notification with the updated information.
Implementation details:
- Single-line notifications now have 4px bottom padding instead of 8px, which
means that their height matches the tray height, they are fully shown in the
banner mode, and don't pop out by 4px when the notification is expanded.
- Notification keeps a flag that indicates whether it is expanded, updates
its expanded look when it is updated, and emits an 'expanded' signal
indicating that its layout has possibly changed. The message tray connects
to this 'expanded' signal when it is showing a notification in the expanded
state and updates the position of the notification accordingly when this
signal is received so that the notification is fully shown. This is better
than connecting to 'notify::height' signal on the notification bin, since
it results in fewer callbacks.
https://bugzilla.gnome.org/show_bug.cgi?id=617209
2010-09-10 19:47:12 +00:00
|
|
|
}
|
2011-03-21 21:43:34 +00:00
|
|
|
|
2011-02-15 23:37:33 +00:00
|
|
|
if (this._clickedSummaryItem)
|
|
|
|
this._updateState();
|
2010-01-28 18:39:00 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|
2012-05-22 22:02:00 +00:00
|
|
|
Signals.addSignalMethods(MessageTray.prototype);
|
2010-11-30 15:47:28 +00:00
|
|
|
|
2011-11-20 15:12:02 +00:00
|
|
|
const SystemNotificationSource = new Lang.Class({
|
|
|
|
Name: 'SystemNotificationSource',
|
|
|
|
Extends: Source,
|
2010-11-30 15:47:28 +00:00
|
|
|
|
|
|
|
_init: function() {
|
2012-05-30 13:58:37 +00:00
|
|
|
this.parent(_("System Information"), 'dialog-information-symbolic');
|
2012-05-22 22:02:00 +00:00
|
|
|
this.setTransient(true);
|
2010-11-30 15:47:28 +00:00
|
|
|
},
|
|
|
|
|
2011-02-11 19:43:01 +00:00
|
|
|
open: function() {
|
2010-11-30 15:47:28 +00:00
|
|
|
this.destroy();
|
|
|
|
}
|
2011-11-20 15:12:02 +00:00
|
|
|
});
|