Merge remote branch 'origin/master' into zeitgeist
This commit is contained in:
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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)
|
||||
|
@ -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() {
|
||||
|
@ -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()...
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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 });
|
||||
|
||||
|
Reference in New Issue
Block a user