Merge remote branch 'origin/master' into zeitgeist

This commit is contained in:
Federico Mena Quintero
2011-03-29 17:00:54 -04:00
40 changed files with 8020 additions and 3363 deletions

View File

@ -2,6 +2,7 @@
const Clutter = imports.gi.Clutter;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const St = imports.gi.St;
const Shell = imports.gi.Shell;
@ -40,80 +41,80 @@ BoxPointer.prototype = {
this._border.connect('repaint', Lang.bind(this, this._drawBorder));
this._container.add_actor(this._border);
this.bin.raise(this._border);
this._xOffset = 0;
this._yOffset = 0;
this._xPosition = 0;
this._yPosition = 0;
},
show: function(animate, onComplete) {
let x = this.actor.x;
let y = this.actor.y;
let themeNode = this.actor.get_theme_node();
let rise = themeNode.get_length('-arrow-rise');
this.actor.opacity = 0;
this.opacity = 0;
this.actor.show();
if (animate) {
switch (this._arrowSide) {
case St.Side.TOP:
this.actor.y -= rise;
this.yOffset = -rise;
break;
case St.Side.BOTTOM:
this.actor.y += rise;
this.yOffset = rise;
break;
case St.Side.LEFT:
this.actor.x -= rise;
this.xOffset = -rise;
break;
case St.Side.RIGHT:
this.actor.x += rise;
this.xOffset = rise;
break;
}
}
Tweener.addTween(this.actor, { opacity: 255,
x: x,
y: y,
transition: "linear",
onComplete: onComplete,
time: POPUP_ANIMATION_TIME });
Tweener.addTween(this, { opacity: 255,
xOffset: 0,
yOffset: 0,
transition: "linear",
onComplete: onComplete,
time: POPUP_ANIMATION_TIME });
},
hide: function(animate, onComplete) {
let x = this.actor.x;
let y = this.actor.y;
let originalX = this.actor.x;
let originalY = this.actor.y;
let xOffset = 0;
let yOffset = 0;
let themeNode = this.actor.get_theme_node();
let rise = themeNode.get_length('-arrow-rise');
if (animate) {
switch (this._arrowSide) {
case St.Side.TOP:
y += rise;
yOffset = rise;
break;
case St.Side.BOTTOM:
y -= rise;
yOffset = -rise;
break;
case St.Side.LEFT:
x += rise;
xOffset = rise;
break;
case St.Side.RIGHT:
x -= rise;
xOffset = -rise;
break;
}
}
Tweener.addTween(this.actor, { opacity: 0,
x: x,
y: y,
transition: "linear",
time: POPUP_ANIMATION_TIME,
onComplete: Lang.bind(this, function () {
this.actor.hide();
this.actor.x = originalX;
this.actor.y = originalY;
if (onComplete)
onComplete();
})
});
Tweener.addTween(this, { opacity: 0,
xOffset: xOffset,
yOffset: yOffset,
transition: "linear",
time: POPUP_ANIMATION_TIME,
onComplete: Lang.bind(this, function () {
this.actor.hide();
this.xOffset = 0;
this.yOffset = 0;
if (onComplete)
onComplete();
})
});
},
_adjustAllocationForArrow: function(isWidth, alloc) {
@ -176,6 +177,9 @@ BoxPointer.prototype = {
break;
}
this.bin.allocate(childBox, flags);
if (this._sourceActor && this._sourceActor.mapped)
this._reposition(this._sourceActor, this._gap, this._alignment);
},
_drawBorder: function(area) {
@ -306,13 +310,20 @@ BoxPointer.prototype = {
// so that we can query the correct size.
this.actor.show();
this._sourceActor = sourceActor;
this._gap = gap;
this._alignment = alignment;
this._reposition(sourceActor, gap, alignment);
},
_reposition: function(sourceActor, gap, alignment) {
// Position correctly relative to the sourceActor
let sourceNode = sourceActor.get_theme_node();
let sourceContentBox = sourceNode.get_content_box(sourceActor.get_allocation_box());
let [sourceX, sourceY] = sourceActor.get_transformed_position();
let [sourceWidth, sourceHeight] = sourceActor.get_transformed_size();
let sourceCenterX = sourceX + sourceContentBox.x1 + (sourceContentBox.x2 - sourceContentBox.x1) / 2;
let sourceCenterY = sourceY + sourceContentBox.y1 + (sourceContentBox.y2 - sourceContentBox.y1) / 2;
let sourceAllocation = Shell.util_get_transformed_allocation(sourceActor);
let sourceCenterX = sourceAllocation.x1 + sourceContentBox.x1 + (sourceContentBox.x2 - sourceContentBox.x1) / 2;
let sourceCenterY = sourceAllocation.y1 + sourceContentBox.y1 + (sourceContentBox.y2 - sourceContentBox.y1) / 2;
let [minWidth, minHeight, natWidth, natHeight] = this.actor.get_preferred_size();
// We also want to keep it onscreen, and separated from the
@ -330,16 +341,16 @@ BoxPointer.prototype = {
switch (this._arrowSide) {
case St.Side.TOP:
resY = sourceY + sourceHeight + gap;
resY = sourceAllocation.y2 + gap;
break;
case St.Side.BOTTOM:
resY = sourceY - natHeight - gap;
resY = sourceAllocation.y1 - natHeight - gap;
break;
case St.Side.LEFT:
resX = sourceX + sourceWidth + gap;
resX = sourceAllocation.x2 + gap;
break;
case St.Side.RIGHT:
resX = sourceX - natWidth - gap;
resX = sourceAllocation.x1 - natWidth - gap;
break;
}
@ -373,9 +384,9 @@ BoxPointer.prototype = {
parent = parent.get_parent();
}
// Actually set the position
this.actor.x = Math.floor(x);
this.actor.y = Math.floor(y);
this._xPosition = Math.floor(x);
this._yPosition = Math.floor(y);
this._shiftActor();
},
// @origin: Coordinate specifying middle of the arrow, along
@ -386,5 +397,42 @@ BoxPointer.prototype = {
this._arrowOrigin = origin;
this._border.queue_repaint();
}
},
_shiftActor : function() {
// Since the position of the BoxPointer depends on the allocated size
// of the BoxPointer and the position of the source actor, trying
// to position the BoxPoiner via the x/y properties will result in
// allocation loops and warnings. Instead we do the positioning via
// the anchor point, which is independent of allocation, and leave
// x == y == 0.
this.actor.set_anchor_point(-(this._xPosition + this._xOffset),
-(this._yPosition + this._yOffset));
},
set xOffset(offset) {
this._xOffset = offset;
this._shiftActor();
},
get xOffset() {
return this._xOffset;
},
set yOffset(offset) {
this._yOffset = offset;
this._shiftActor();
},
get yOffset() {
return this._yOffset;
},
set opacity(opacity) {
this.actor.opacity = opacity;
},
get opacity() {
return this.actor.opacity;
}
};

View File

@ -539,7 +539,7 @@ function _relayout() {
if (!isPrimary && !haveTopLeftCorner)
continue;
let corner = new Panel.HotCorner();
let corner = new Panel.HotCorner(isPrimary ? panel.button : null);
hotCorners.push(corner);
corner.actor.set_position(cornerX, cornerY);
if (isPrimary)

View File

@ -108,6 +108,12 @@ URLHighlighter.prototype = {
}
this.setMarkup(text, allowMarkup);
this.actor.connect('button-press-event', Lang.bind(this, function(actor, event) {
// Keep Notification.actor from seeing this and taking
// a pointer grab, which would block our button-release-event
// handler, if an URL is clicked
return this._findUrlAtPos(event) != -1;
}));
this.actor.connect('button-release-event', Lang.bind(this, function (actor, event) {
let urlId = this._findUrlAtPos(event);
if (urlId != -1) {
@ -734,15 +740,22 @@ Notification.prototype = {
// 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.
if (!bannerFits)
Mainloop.idle_add(Lang.bind(this,
function() {
if (!bannerFits && this._canExpandContent())
Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
Lang.bind(this,
function() {
if (this._canExpandContent()) {
this._addBannerBody();
if (!this._titleFitsInBannerMode)
this._table.add_style_class_name('multi-line-notification');
this._table.add_style_class_name('multi-line-notification');
this._updated();
return false;
}));
}
return false;
}));
},
_canExpandContent: function() {
return this._bannerBodyText ||
(!this._titleFitsInBannerMode && !this._table.has_style_class_name('multi-line-notification'));
},
_updated: function() {
@ -1404,6 +1417,7 @@ MessageTray.prototype = {
if (!this._locked)
return;
this._locked = false;
this._pointerInTray = this.actor.hover && !this._summaryBoxPointer.bin.hover;
this._updateState();
},
@ -2093,13 +2107,18 @@ MessageTray.prototype = {
},
_hideSummaryBoxPointer: function() {
this._summaryBoxPointerState = State.HIDING;
// Unset this._clickedSummaryItem if we are no longer showing the summary
if (this._summaryState != State.SHOWN)
this._unsetClickedSummaryItem();
this._focusGrabber.ungrabFocus();
this._summaryBoxPointerState = State.HIDING;
this._summaryBoxPointer.hide(true, Lang.bind(this, this._hideSummaryBoxPointerCompleted));
if (this._summaryBoxPointerItem.source.notifications.length == 0) {
this._summaryBoxPointer.actor.hide();
this._hideSummaryBoxPointerCompleted();
} else {
this._summaryBoxPointer.hide(true, Lang.bind(this, this._hideSummaryBoxPointerCompleted));
}
},
_hideSummaryBoxPointerCompleted: function() {

View File

@ -465,6 +465,14 @@ Source.prototype = {
if (event.type() != Clutter.EventType.BUTTON_RELEASE)
return false;
// Left clicks are passed through only where there aren't unacknowledged
// notifications, so it possible to open them in summary mode; right
// clicks are always forwarded, as the right click menu is not useful for
// tray icons
if (event.get_button() == 1 &&
this.notifications.length > 0)
return false;
if (Main.overview.visible) {
// We can't just connect to Main.overview's 'hidden' signal,
// because it's emitted *before* it calls popModal()...

View File

@ -300,6 +300,10 @@ AppMenuButton.prototype = {
this._visible = true;
this.actor.show();
if (!this._targetIsCurrent)
return;
Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor,
{ opacity: 255,
@ -312,6 +316,11 @@ AppMenuButton.prototype = {
return;
this._visible = false;
if (!this._targetIsCurrent) {
this.actor.hide();
return;
}
Tweener.removeTweens(this.actor);
Tweener.addTween(this.actor,
{ opacity: 0,
@ -622,12 +631,17 @@ PanelCorner.prototype = {
* This class manages the "hot corner" that can toggle switching to
* overview.
*/
function HotCorner() {
this._init();
function HotCorner(button) {
this._init(button);
}
HotCorner.prototype = {
_init : function() {
_init : function(button) {
// This is the activities button associated with this hot corner,
// if this is on the primary monitor (or null with the corner is
// on a different monitor)
this._button = button;
// We use this flag to mark the case where the user has entered the
// hot corner and has not left both the hot corner and a surrounding
// guard area (the "environs"). This avoids triggering the hot corner
@ -654,6 +668,8 @@ HotCorner.prototype = {
this._activationTime = 0;
this.actor.connect('enter-event',
Lang.bind(this, this._onEnvironsEntered));
this.actor.connect('leave-event',
Lang.bind(this, this._onEnvironsLeft));
// Clicking on the hot corner environs should result in the same bahavior
@ -730,6 +746,11 @@ HotCorner.prototype = {
this._addRipple(0.35, 1.0, 0.0, 0.3, 1, 0.0);
},
_onEnvironsEntered : function() {
if (this._button)
this._button.hover = true;
},
_onCornerEntered : function() {
if (!this._entered) {
this._entered = true;
@ -757,6 +778,9 @@ HotCorner.prototype = {
},
_onEnvironsLeft : function(actor, event) {
if (this._button)
this._button.hover = false;
if (event.get_related() != this._corner)
this._entered = false;
return false;

View File

@ -257,6 +257,10 @@ AuthenticationDialog.prototype = {
* show "Sorry, that didn't work. Please try again."
*/
if (!this._errorMessageLabel.visible && !this._wasDismissed) {
/* Translators: "that didn't work" refers to the fact that the
* requested authentication was not gained; this can happen
* because of an authentication error (like invalid password),
* for instance. */
this._errorMessageLabel.set_text(_("Sorry, that didn\'t work. Please try again."));
this._errorMessageLabel.show();
this._infoMessageLabel.hide();

View File

@ -61,7 +61,7 @@ PopupBaseMenuItem.prototype = {
},
_onStyleChanged: function (actor) {
this._spacing = actor.get_theme_node().get_length('spacing');
this._spacing = Math.round(actor.get_theme_node().get_length('spacing'));
},
_onButtonReleaseEvent: function (actor, event) {
@ -765,6 +765,7 @@ PopupMenuBase.prototype = {
} else {
this.box = new St.BoxLayout({ vertical: true });
}
this.box.connect_after('queue-relayout', Lang.bind(this, this._menuQueueRelayout));
this.isOpen = false;
@ -893,6 +894,16 @@ PopupMenuBase.prototype = {
}
},
// Because of the above column-width funniness, we need to do a
// queue-relayout on every item whenever the menu itself changes
// size, to force clutter to drop its cached size requests. (The
// menuitems will in turn call queue_relayout on their parent, the
// menu, but that call will be a no-op since the menu already
// has a relayout queued, so we won't get stuck in a loop.
_menuQueueRelayout: function() {
this.box.get_children().map(function (actor) { actor.queue_relayout(); });
},
addActor: function(actor) {
this.box.add(actor);
},

View File

@ -92,16 +92,32 @@ function NMNetworkMenuItem() {
}
NMNetworkMenuItem.prototype = {
__proto__: PopupMenu.PopupImageMenuItem.prototype,
__proto__: PopupMenu.PopupBaseMenuItem.prototype,
_init: function(accessPoints, title, params) {
PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params);
accessPoints = sortAccessPoints(accessPoints);
this.bestAP = accessPoints[0];
let ssid = this.bestAP.get_ssid();
title = title || NetworkManager.utils_ssid_to_utf8(ssid) || _("<unknown>");
PopupMenu.PopupImageMenuItem.prototype._init.call(this, title, this._getIcon(), params);
this._label = new St.Label({ text: title });
this.addActor(this._label);
this._icons = new St.BoxLayout({ style_class: 'nm-menu-item-icons' });
this.addActor(this._icons, { align: St.Align.END });
this._signalIcon = new St.Icon({ icon_name: this._getIcon(),
style_class: 'popup-menu-icon' });
this._icons.add_actor(this._signalIcon);
if (this.bestAP._secType != NMAccessPointSecurity.UNKNOWN &&
this.bestAP._secType != NMAccessPointSecurity.NONE) {
this._secureIcon = new St.Icon({ icon_name: 'network-wireless-encrypted',
style_class: 'popup-menu-icon' });
this._icons.add_actor(this._secureIcon);
}
this._accessPoints = [ ];
for (let i = 0; i < accessPoints.length; i++) {
@ -120,7 +136,7 @@ NMNetworkMenuItem.prototype = {
if (strength > this.bestAP.strength)
this.bestAP = ap;
this.setIcon(this._getIcon());
this._signalIcon.icon_name = this._getIcon();
},
_getIcon: function() {
@ -379,7 +395,7 @@ NMDevice.prototype = {
// pick the most recently used connection and connect to that
// or if no connections ever set, create an automatic one
if (this._connections.length > 0) {
this._client.activate_connection(this._connections[0].connection.path, this.device, null, null);
this._client.activate_connection(this._connections[0].connection, this.device, null, null);
} else if (this._autoConnectionName) {
let connection = this._createAutomaticConnection();
this._client.add_and_activate_connection(connection, this.device, null, null);
@ -566,10 +582,11 @@ NMDevice.prototype = {
},
_createConnectionItem: function(obj) {
let path = obj.connection.path;
let connection = obj.connection;
let item = new PopupMenu.PopupMenuItem(obj.name);
item.connect('activate', Lang.bind(this, function() {
this._client.activate_connection(path, this.device, null, null);
this._client.activate_connection(connection, this.device, null, null);
}));
return item;
},
@ -1045,7 +1062,7 @@ NMDeviceWireless.prototype = {
for (let i = 0; i < bestApObj.accessPoints.length; i++) {
let ap = bestApObj.accessPoints[i];
if (this._connectionValidForAP(best, ap)) {
this._client.activate_connection(best.path, this.device, ap.dbus_path, null);
this._client.activate_connection(best, this.device, ap.dbus_path, null);
break;
}
}
@ -1180,7 +1197,7 @@ NMDeviceWireless.prototype = {
let accessPoints = sortAccessPoints(accessPointObj.accessPoints);
for (let i = 0; i < accessPoints.length; i++) {
if (this._connectionValidForAP(connection, accessPoints[i])) {
this._client.activate_connection(connection.path, this.device, accessPoints[i].dbus_path, null);
this._client.activate_connection(connection, this.device, accessPoints[i].dbus_path, null);
break;
}
}
@ -1797,7 +1814,7 @@ NMApplet.prototype = {
}
}
} else
a._primaryDevice = this._vpnDevice;
a._primaryDevice = this._devices.vpn.device
if (a._primaryDevice)
a._primaryDevice.setActiveConnection(a);

View File

@ -19,7 +19,8 @@ const STANDARD_TRAY_ICON_IMPLEMENTATIONS = {
'a11y-keyboard': 'a11y',
'kbd-scrolllock': 'keyboard',
'kbd-numlock': 'keyboard',
'kbd-capslock': 'keyboard'
'kbd-capslock': 'keyboard',
'ibus-ui-gtk': 'input-method'
};
function StatusIconDispatcher() {

View File

@ -11,6 +11,7 @@ const Tpl = imports.gi.TelepathyLogger;
const Tp = imports.gi.TelepathyGLib;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const C_ = Gettext.pgettext;
const History = imports.misc.history;
const Main = imports.ui.main;
@ -245,36 +246,38 @@ Source.prototype = {
let [success, events] = logManager.get_filtered_events_finish(result);
let logMessages = events.map(makeMessageFromTplEvent);
let pendingTpMessages = this._channel.get_pending_messages();
let pendingMessages = pendingTpMessages.map(function (tpMessage) { return makeMessageFromTpMessage(tpMessage, NotificationDirection.RECEIVED); });
let showTimestamp = false;
for (let i = 0; i < logMessages.length; i++) {
this._notification.appendMessage(logMessages[i], true);
}
let logMessage = logMessages[i];
let isPending = false;
let pendingMessages = this._channel.get_pending_messages();
let hasPendingMessage = false;
for (let i = 0; i < pendingMessages.length; i++) {
let message = makeMessageFromTpMessage(pendingMessages[i], NotificationDirection.RECEIVED);
// Skip any pending messages that are in the logs.
let inLog = false;
for (let j = 0; j < logMessages.length; j++) {
let logMessage = logMessages[j];
if (logMessage.timestamp == message.timestamp && logMessage.text == message.body) {
inLog = true;
// Skip any log messages that are also in pendingMessages
for (let j = 0; j < pendingMessages.length; j++) {
let pending = pendingMessages[j];
if (logMessage.timestamp == pending.timestamp && logMessage.text == pending.text) {
isPending = true;
break;
}
}
if (inLog)
continue;
this._notification.appendMessage(message, true);
hasPendingMessage = true;
if (!isPending) {
showTimestamp = true;
this._notification.appendMessage(logMessage, true, ['chat-log-message']);
}
}
// Only show the timestamp if we have at least one message.
if (hasPendingMessage || logMessages.length > 0)
if (showTimestamp)
this._notification.appendTimestamp();
if (hasPendingMessage)
for (let i = 0; i < pendingMessages.length; i++)
this._notification.appendMessage(pendingMessages[i], true);
if (pendingMessages.length > 0)
this.notify();
},
@ -400,10 +403,12 @@ Notification.prototype = {
* @noTimestamp: Whether to add a timestamp. If %true, no timestamp
* will be added, regardless of the difference since the
* last timestamp
* @styles: A list of CSS class names.
*/
appendMessage: function(message, noTimestamp) {
appendMessage: function(message, noTimestamp, styles) {
let messageBody = GLib.markup_escape_text(message.text, -1);
let styles = [message.direction];
styles = styles || [];
styles.push(message.direction);
if (message.messageType == Tp.ChannelTextMessageType.ACTION) {
let senderAlias = GLib.markup_escape_text(message.sender, -1);
@ -463,15 +468,39 @@ Notification.prototype = {
}
},
_formatTimestamp: function(date) {
let now = new Date();
var daysAgo = (now.getTime() - date.getTime()) / (24 * 60 * 60 * 1000);
// Show a week day and time if date is in the last week
if (daysAgo < 1 || (daysAgo < 7 && now.getDay() != date.getDay())) {
/* Translators: this is a time format string followed by a date.
If applicable, replace %X with a strftime format valid for your
locale, without seconds. */
// xgettext:no-c-format
format = _("Sent at %X on %A");
// FIXME: The next two are stolen from calendar.js with the comment to avoid
// a string-freeze break. They should be replaced with better strings
// with 'Sent at', appropriate context and appropriate translator comment.
} else if (date.getYear() == now.getYear()) {
/* Translators: Shown on calendar heading when selected day occurs on current year */
format = C_("calendar heading", "%A, %B %d");
} else {
/* Translators: Shown on calendar heading when selected day occurs on different year */
format = C_("calendar heading", "%A, %B %d, %Y");
}
return date.toLocaleFormat(format);
},
appendTimestamp: function() {
let lastMessageTime = this._history[0].time;
let lastMessageDate = new Date(lastMessageTime * 1000);
/* Translators: this is a time format string followed by a date.
If applicable, replace %X with a strftime format valid for your
locale, without seconds. */
// xgettext:no-c-format
let timeLabel = this.addBody(lastMessageDate.toLocaleFormat(_("Sent at %X on %A")), false, { expand: true, x_fill: false, x_align: St.Align.END });
let timeLabel = this.addBody(this._formatTimestamp(lastMessageDate), false, { expand: true, x_fill: false, x_align: St.Align.END });
timeLabel.add_style_class_name('chat-meta-message');
this._history.unshift({ actor: timeLabel, time: lastMessageTime, realMessage: false });