2011-09-28 13:16:26 +00:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2010-07-21 02:22:19 +00:00
|
|
|
|
|
|
|
const Clutter = imports.gi.Clutter;
|
|
|
|
const Shell = imports.gi.Shell;
|
|
|
|
const St = imports.gi.St;
|
|
|
|
|
|
|
|
const Lang = imports.lang;
|
|
|
|
const Params = imports.misc.params;
|
|
|
|
|
|
|
|
const ICON_SIZE = 48;
|
|
|
|
|
|
|
|
|
2011-11-20 16:07:14 +00:00
|
|
|
const BaseIcon = new Lang.Class({
|
|
|
|
Name: 'BaseIcon',
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2010-08-30 12:59:45 +00:00
|
|
|
_init : function(label, params) {
|
|
|
|
params = Params.parse(params, { createIcon: null,
|
2011-01-20 14:25:25 +00:00
|
|
|
setSizeManually: false,
|
|
|
|
showLabel: true });
|
2010-07-21 02:22:19 +00:00
|
|
|
this.actor = new St.Bin({ style_class: 'overview-icon',
|
|
|
|
x_fill: true,
|
|
|
|
y_fill: true });
|
|
|
|
this.actor._delegate = this;
|
|
|
|
this.actor.connect('style-changed',
|
|
|
|
Lang.bind(this, this._onStyleChanged));
|
2012-12-01 01:35:04 +00:00
|
|
|
this.actor.connect('destroy',
|
|
|
|
Lang.bind(this, this._onDestroy));
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2010-08-30 12:59:36 +00:00
|
|
|
this._spacing = 0;
|
|
|
|
|
|
|
|
let box = new Shell.GenericContainer();
|
|
|
|
box.connect('allocate', Lang.bind(this, this._allocate));
|
|
|
|
box.connect('get-preferred-width',
|
|
|
|
Lang.bind(this, this._getPreferredWidth));
|
|
|
|
box.connect('get-preferred-height',
|
|
|
|
Lang.bind(this, this._getPreferredHeight));
|
2010-07-21 02:22:19 +00:00
|
|
|
this.actor.set_child(box);
|
|
|
|
|
|
|
|
this.iconSize = ICON_SIZE;
|
2011-12-20 18:11:07 +00:00
|
|
|
this._iconBin = new St.Bin({ x_align: St.Align.MIDDLE,
|
|
|
|
y_align: St.Align.MIDDLE });
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2010-08-30 12:59:36 +00:00
|
|
|
box.add_actor(this._iconBin);
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2011-01-20 14:25:25 +00:00
|
|
|
if (params.showLabel) {
|
2011-03-08 18:33:57 +00:00
|
|
|
this.label = new St.Label({ text: label });
|
|
|
|
box.add_actor(this.label);
|
2011-01-20 14:25:25 +00:00
|
|
|
} else {
|
2011-03-08 18:33:57 +00:00
|
|
|
this.label = null;
|
2011-01-20 14:25:25 +00:00
|
|
|
}
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2010-08-30 12:59:45 +00:00
|
|
|
if (params.createIcon)
|
|
|
|
this.createIcon = params.createIcon;
|
|
|
|
this._setSizeManually = params.setSizeManually;
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2011-03-02 23:25:23 +00:00
|
|
|
this.icon = null;
|
2012-12-01 01:35:04 +00:00
|
|
|
|
|
|
|
let cache = St.TextureCache.get_default();
|
|
|
|
this._iconThemeChangedId = cache.connect('icon-theme-changed', Lang.bind(this, this._onIconThemeChanged));
|
2010-07-21 02:22:19 +00:00
|
|
|
},
|
|
|
|
|
2010-08-30 12:59:36 +00:00
|
|
|
_allocate: function(actor, box, flags) {
|
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
|
2011-01-20 14:25:25 +00:00
|
|
|
let iconSize = availHeight;
|
|
|
|
|
2010-08-30 12:59:36 +00:00
|
|
|
let [iconMinHeight, iconNatHeight] = this._iconBin.get_preferred_height(-1);
|
2011-02-11 19:37:27 +00:00
|
|
|
let [iconMinWidth, iconNatWidth] = this._iconBin.get_preferred_width(-1);
|
2011-01-20 14:25:25 +00:00
|
|
|
let preferredHeight = iconNatHeight;
|
2010-08-30 12:59:36 +00:00
|
|
|
|
|
|
|
let childBox = new Clutter.ActorBox();
|
|
|
|
|
2011-03-08 18:33:57 +00:00
|
|
|
if (this.label) {
|
|
|
|
let [labelMinHeight, labelNatHeight] = this.label.get_preferred_height(-1);
|
2011-01-20 14:25:25 +00:00
|
|
|
preferredHeight += this._spacing + labelNatHeight;
|
|
|
|
|
|
|
|
let labelHeight = availHeight >= preferredHeight ? labelNatHeight
|
|
|
|
: labelMinHeight;
|
|
|
|
iconSize -= this._spacing + labelHeight;
|
|
|
|
|
|
|
|
childBox.x1 = 0;
|
|
|
|
childBox.x2 = availWidth;
|
|
|
|
childBox.y1 = iconSize + this._spacing;
|
|
|
|
childBox.y2 = childBox.y1 + labelHeight;
|
2011-03-08 18:33:57 +00:00
|
|
|
this.label.allocate(childBox, flags);
|
2011-01-20 14:25:25 +00:00
|
|
|
}
|
|
|
|
|
2011-02-11 19:37:27 +00:00
|
|
|
childBox.x1 = Math.floor((availWidth - iconNatWidth) / 2);
|
|
|
|
childBox.y1 = Math.floor((iconSize - iconNatHeight) / 2);
|
|
|
|
childBox.x2 = childBox.x1 + iconNatWidth;
|
|
|
|
childBox.y2 = childBox.y1 + iconNatHeight;
|
2010-08-30 12:59:36 +00:00
|
|
|
this._iconBin.allocate(childBox, flags);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredWidth: function(actor, forHeight, alloc) {
|
|
|
|
this._getPreferredHeight(actor, -1, alloc);
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function(actor, forWidth, alloc) {
|
|
|
|
let [iconMinHeight, iconNatHeight] = this._iconBin.get_preferred_height(forWidth);
|
2011-01-20 14:25:25 +00:00
|
|
|
alloc.min_size = iconMinHeight;
|
|
|
|
alloc.natural_size = iconNatHeight;
|
|
|
|
|
2011-03-08 18:33:57 +00:00
|
|
|
if (this.label) {
|
|
|
|
let [labelMinHeight, labelNatHeight] = this.label.get_preferred_height(forWidth);
|
2011-01-20 14:25:25 +00:00
|
|
|
alloc.min_size += this._spacing + labelMinHeight;
|
|
|
|
alloc.natural_size += this._spacing + labelNatHeight;
|
|
|
|
}
|
2010-08-30 12:59:36 +00:00
|
|
|
},
|
|
|
|
|
2010-07-21 02:22:19 +00:00
|
|
|
// This can be overridden by a subclass, or by the createIcon
|
|
|
|
// parameter to _init()
|
|
|
|
createIcon: function(size) {
|
|
|
|
throw new Error('no implementation of createIcon in ' + this);
|
|
|
|
},
|
|
|
|
|
2010-08-30 12:59:45 +00:00
|
|
|
setIconSize: function(size) {
|
|
|
|
if (!this._setSizeManually)
|
|
|
|
throw new Error('setSizeManually has to be set to use setIconsize');
|
|
|
|
|
|
|
|
if (size == this.iconSize)
|
|
|
|
return;
|
|
|
|
|
2011-03-02 23:25:23 +00:00
|
|
|
this._createIconTexture(size);
|
|
|
|
},
|
|
|
|
|
|
|
|
_createIconTexture: function(size) {
|
|
|
|
if (this.icon)
|
|
|
|
this.icon.destroy();
|
2010-08-30 12:59:45 +00:00
|
|
|
this.iconSize = size;
|
|
|
|
this.icon = this.createIcon(this.iconSize);
|
2011-02-10 15:30:28 +00:00
|
|
|
|
2011-12-20 18:11:07 +00:00
|
|
|
this._iconBin.child = this.icon;
|
|
|
|
|
2011-02-10 15:30:28 +00:00
|
|
|
// The icon returned by createIcon() might actually be smaller than
|
|
|
|
// the requested icon size (for instance StTextureCache does this
|
|
|
|
// for fallback icons), so set the size explicitly.
|
2011-12-20 18:11:07 +00:00
|
|
|
this._iconBin.set_size(this.iconSize, this.iconSize);
|
2010-08-30 12:59:45 +00:00
|
|
|
},
|
|
|
|
|
2010-07-21 02:22:19 +00:00
|
|
|
_onStyleChanged: function() {
|
2010-08-30 12:59:36 +00:00
|
|
|
let node = this.actor.get_theme_node();
|
StThemeNode: simplify use of get_color/get_double/get_length
Although within St itself there are situations where the semantics of
these functions (return TRUE or FALSE and return the actual value in
an out parameter) is useful, it's mostly just annoying at the
application level, where you generally know that the CSS property is
going to specified, and there is no especially sane fallback if it's
not.
So rename the current methods to lookup_color, lookup_double, and
lookup_length, and add new get_color, get_double, and get_length
methods that don't take an "inherit" parameter, and return their
values directly. (Well, except for get_color, due to the lack of (out
caller-allocates) in gjs.)
And update the code to use either the old or new methods as appropriate.
https://bugzilla.gnome.org/show_bug.cgi?id=632590
2010-09-26 21:38:36 +00:00
|
|
|
this._spacing = node.get_length('spacing');
|
2010-08-30 12:59:36 +00:00
|
|
|
|
2011-03-02 23:25:23 +00:00
|
|
|
let size;
|
|
|
|
if (this._setSizeManually) {
|
|
|
|
size = this.iconSize;
|
|
|
|
} else {
|
|
|
|
let [found, len] = node.lookup_length('icon-size', false);
|
|
|
|
size = found ? len : ICON_SIZE;
|
|
|
|
}
|
2010-08-30 12:59:36 +00:00
|
|
|
|
2012-12-01 01:35:04 +00:00
|
|
|
if (this.iconSize == size && this._iconBin.child)
|
|
|
|
return;
|
|
|
|
|
2011-03-02 23:25:23 +00:00
|
|
|
this._createIconTexture(size);
|
2012-12-01 01:35:04 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onDestroy: function() {
|
|
|
|
if (this._iconThemeChangedId > 0) {
|
|
|
|
let cache = St.TextureCache.get_default();
|
|
|
|
cache.disconnect(this._iconThemeChangedId);
|
|
|
|
this._iconThemeChangedId = 0;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_onIconThemeChanged: function() {
|
|
|
|
this._createIconTexture(this.iconSize);
|
2010-07-21 02:22:19 +00:00
|
|
|
}
|
2011-11-20 16:07:14 +00:00
|
|
|
});
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2011-11-20 17:56:27 +00:00
|
|
|
const IconGrid = new Lang.Class({
|
|
|
|
Name: 'IconGrid',
|
2010-07-21 02:22:19 +00:00
|
|
|
|
|
|
|
_init: function(params) {
|
2010-10-14 12:27:22 +00:00
|
|
|
params = Params.parse(params, { rowLimit: null,
|
|
|
|
columnLimit: null,
|
|
|
|
xAlign: St.Align.MIDDLE });
|
2010-07-21 02:22:19 +00:00
|
|
|
this._rowLimit = params.rowLimit;
|
|
|
|
this._colLimit = params.columnLimit;
|
2010-10-14 12:27:22 +00:00
|
|
|
this._xAlign = params.xAlign;
|
2010-07-21 02:22:19 +00:00
|
|
|
|
|
|
|
this.actor = new St.BoxLayout({ style_class: 'icon-grid',
|
|
|
|
vertical: true });
|
|
|
|
// Pulled from CSS, but hardcode some defaults here
|
|
|
|
this._spacing = 0;
|
2012-02-14 15:30:40 +00:00
|
|
|
this._hItemSize = this._vItemSize = ICON_SIZE;
|
2010-07-21 02:22:19 +00:00
|
|
|
this._grid = new Shell.GenericContainer();
|
|
|
|
this.actor.add(this._grid, { expand: true, y_align: St.Align.START });
|
|
|
|
this.actor.connect('style-changed', Lang.bind(this, this._onStyleChanged));
|
|
|
|
|
|
|
|
this._grid.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
|
|
|
|
this._grid.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
|
|
|
|
this._grid.connect('allocate', Lang.bind(this, this._allocate));
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredWidth: function (grid, forHeight, alloc) {
|
|
|
|
let children = this._grid.get_children();
|
|
|
|
let nColumns = this._colLimit ? Math.min(this._colLimit,
|
|
|
|
children.length)
|
|
|
|
: children.length;
|
|
|
|
let totalSpacing = Math.max(0, nColumns - 1) * this._spacing;
|
|
|
|
// Kind of a lie, but not really an issue right now. If
|
|
|
|
// we wanted to support some sort of hidden/overflow that would
|
|
|
|
// need higher level design
|
2012-02-14 15:30:40 +00:00
|
|
|
alloc.min_size = this._hItemSize;
|
|
|
|
alloc.natural_size = nColumns * this._hItemSize + totalSpacing;
|
2010-07-21 02:22:19 +00:00
|
|
|
},
|
|
|
|
|
2010-12-18 19:09:58 +00:00
|
|
|
_getVisibleChildren: function() {
|
2010-07-21 02:22:19 +00:00
|
|
|
let children = this._grid.get_children();
|
2010-12-18 19:09:58 +00:00
|
|
|
children = children.filter(function(actor) {
|
|
|
|
return actor.visible;
|
|
|
|
});
|
|
|
|
return children;
|
|
|
|
},
|
|
|
|
|
|
|
|
_getPreferredHeight: function (grid, forWidth, alloc) {
|
|
|
|
let children = this._getVisibleChildren();
|
2013-02-20 02:18:48 +00:00
|
|
|
let nColumns, spacing;
|
|
|
|
if (forWidth < 0) {
|
2012-07-01 16:38:28 +00:00
|
|
|
nColumns = children.length;
|
2013-02-20 02:18:48 +00:00
|
|
|
spacing = this._spacing;
|
|
|
|
} else {
|
|
|
|
[nColumns, , spacing] = this._computeLayout(forWidth);
|
|
|
|
}
|
|
|
|
|
2010-07-21 02:22:19 +00:00
|
|
|
let nRows;
|
|
|
|
if (nColumns > 0)
|
|
|
|
nRows = Math.ceil(children.length / nColumns);
|
|
|
|
else
|
|
|
|
nRows = 0;
|
|
|
|
if (this._rowLimit)
|
|
|
|
nRows = Math.min(nRows, this._rowLimit);
|
2013-02-20 02:18:48 +00:00
|
|
|
let totalSpacing = Math.max(0, nRows - 1) * spacing;
|
2012-02-14 15:30:40 +00:00
|
|
|
let height = nRows * this._vItemSize + totalSpacing;
|
2010-07-21 02:22:19 +00:00
|
|
|
alloc.min_size = height;
|
|
|
|
alloc.natural_size = height;
|
|
|
|
},
|
|
|
|
|
|
|
|
_allocate: function (grid, box, flags) {
|
2010-12-18 19:09:58 +00:00
|
|
|
let children = this._getVisibleChildren();
|
2010-07-21 02:22:19 +00:00
|
|
|
let availWidth = box.x2 - box.x1;
|
|
|
|
let availHeight = box.y2 - box.y1;
|
|
|
|
|
2013-02-19 23:38:11 +00:00
|
|
|
let [nColumns, usedWidth, spacing] = this._computeLayout(availWidth);
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2010-10-14 12:27:22 +00:00
|
|
|
let leftPadding;
|
|
|
|
switch(this._xAlign) {
|
|
|
|
case St.Align.START:
|
|
|
|
leftPadding = 0;
|
|
|
|
break;
|
|
|
|
case St.Align.MIDDLE:
|
|
|
|
leftPadding = Math.floor((availWidth - usedWidth) / 2);
|
|
|
|
break;
|
|
|
|
case St.Align.END:
|
|
|
|
leftPadding = availWidth - usedWidth;
|
|
|
|
}
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2010-10-14 12:27:22 +00:00
|
|
|
let x = box.x1 + leftPadding;
|
2010-07-21 02:22:19 +00:00
|
|
|
let y = box.y1;
|
|
|
|
let columnIndex = 0;
|
|
|
|
let rowIndex = 0;
|
|
|
|
for (let i = 0; i < children.length; i++) {
|
|
|
|
let [childMinWidth, childMinHeight, childNaturalWidth, childNaturalHeight]
|
|
|
|
= children[i].get_preferred_size();
|
|
|
|
|
|
|
|
/* Center the item in its allocation horizontally */
|
2012-02-14 15:30:40 +00:00
|
|
|
let width = Math.min(this._hItemSize, childNaturalWidth);
|
2010-07-21 02:22:19 +00:00
|
|
|
let childXSpacing = Math.max(0, width - childNaturalWidth) / 2;
|
2012-02-14 15:30:40 +00:00
|
|
|
let height = Math.min(this._vItemSize, childNaturalHeight);
|
2010-07-21 02:22:19 +00:00
|
|
|
let childYSpacing = Math.max(0, height - childNaturalHeight) / 2;
|
|
|
|
|
|
|
|
let childBox = new Clutter.ActorBox();
|
2012-02-14 01:37:28 +00:00
|
|
|
if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
|
2010-07-21 02:22:19 +00:00
|
|
|
let _x = box.x2 - (x + width);
|
|
|
|
childBox.x1 = Math.floor(_x - childXSpacing);
|
|
|
|
} else {
|
|
|
|
childBox.x1 = Math.floor(x + childXSpacing);
|
|
|
|
}
|
|
|
|
childBox.y1 = Math.floor(y + childYSpacing);
|
|
|
|
childBox.x2 = childBox.x1 + width;
|
|
|
|
childBox.y2 = childBox.y1 + height;
|
|
|
|
|
|
|
|
if (this._rowLimit && rowIndex >= this._rowLimit) {
|
|
|
|
this._grid.set_skip_paint(children[i], true);
|
|
|
|
} else {
|
|
|
|
children[i].allocate(childBox, flags);
|
|
|
|
this._grid.set_skip_paint(children[i], false);
|
|
|
|
}
|
|
|
|
|
|
|
|
columnIndex++;
|
|
|
|
if (columnIndex == nColumns) {
|
|
|
|
columnIndex = 0;
|
|
|
|
rowIndex++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (columnIndex == 0) {
|
2013-02-19 23:38:11 +00:00
|
|
|
y += this._vItemSize + spacing;
|
2010-10-14 12:27:22 +00:00
|
|
|
x = box.x1 + leftPadding;
|
2010-07-21 02:22:19 +00:00
|
|
|
} else {
|
2013-02-19 23:38:11 +00:00
|
|
|
x += this._hItemSize + spacing;
|
2010-07-21 02:22:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-03-21 22:53:07 +00:00
|
|
|
childrenInRow: function(rowWidth) {
|
|
|
|
return this._computeLayout(rowWidth)[0];
|
|
|
|
},
|
|
|
|
|
2012-05-08 20:03:17 +00:00
|
|
|
getRowLimit: function() {
|
|
|
|
return this._rowLimit;
|
|
|
|
},
|
|
|
|
|
2010-07-21 02:22:19 +00:00
|
|
|
_computeLayout: function (forWidth) {
|
|
|
|
let nColumns = 0;
|
|
|
|
let usedWidth = 0;
|
2013-02-19 23:38:11 +00:00
|
|
|
let spacing = this._spacing;
|
|
|
|
|
|
|
|
if (this._colLimit) {
|
|
|
|
let itemWidth = this._hItemSize * this._colLimit;
|
|
|
|
let emptyArea = forWidth - itemWidth;
|
|
|
|
spacing = Math.max(this._spacing, emptyArea / (2 * this._colLimit));
|
|
|
|
spacing = Math.round(spacing);
|
|
|
|
}
|
|
|
|
|
2010-07-21 02:22:19 +00:00
|
|
|
while ((this._colLimit == null || nColumns < this._colLimit) &&
|
2012-02-14 15:30:40 +00:00
|
|
|
(usedWidth + this._hItemSize <= forWidth)) {
|
2013-02-19 23:38:11 +00:00
|
|
|
usedWidth += this._hItemSize + spacing;
|
2010-07-21 02:22:19 +00:00
|
|
|
nColumns += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nColumns > 0)
|
2013-02-19 23:38:11 +00:00
|
|
|
usedWidth -= spacing;
|
2010-07-21 02:22:19 +00:00
|
|
|
|
2013-02-19 23:38:11 +00:00
|
|
|
return [nColumns, usedWidth, spacing];
|
2010-07-21 02:22:19 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
_onStyleChanged: function() {
|
|
|
|
let themeNode = this.actor.get_theme_node();
|
StThemeNode: simplify use of get_color/get_double/get_length
Although within St itself there are situations where the semantics of
these functions (return TRUE or FALSE and return the actual value in
an out parameter) is useful, it's mostly just annoying at the
application level, where you generally know that the CSS property is
going to specified, and there is no especially sane fallback if it's
not.
So rename the current methods to lookup_color, lookup_double, and
lookup_length, and add new get_color, get_double, and get_length
methods that don't take an "inherit" parameter, and return their
values directly. (Well, except for get_color, due to the lack of (out
caller-allocates) in gjs.)
And update the code to use either the old or new methods as appropriate.
https://bugzilla.gnome.org/show_bug.cgi?id=632590
2010-09-26 21:38:36 +00:00
|
|
|
this._spacing = themeNode.get_length('spacing');
|
2012-02-14 15:30:40 +00:00
|
|
|
this._hItemSize = themeNode.get_length('-shell-grid-horizontal-item-size') || ICON_SIZE;
|
|
|
|
this._vItemSize = themeNode.get_length('-shell-grid-vertical-item-size') || ICON_SIZE;
|
2010-07-21 02:22:19 +00:00
|
|
|
this._grid.queue_relayout();
|
|
|
|
},
|
|
|
|
|
2012-05-30 00:36:45 +00:00
|
|
|
removeAll: function() {
|
|
|
|
this._grid.destroy_all_children();
|
2010-07-21 02:22:19 +00:00
|
|
|
},
|
|
|
|
|
2012-06-11 17:53:32 +00:00
|
|
|
addItem: function(actor, index) {
|
|
|
|
if (index !== undefined)
|
|
|
|
this._grid.insert_child_at_index(actor, index);
|
|
|
|
else
|
|
|
|
this._grid.add_actor(actor);
|
2010-07-21 02:22:19 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getItemAtIndex: function(index) {
|
2012-05-30 00:36:45 +00:00
|
|
|
return this._grid.get_child_at_index(index);
|
2010-07-21 02:22:19 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
visibleItemsCount: function() {
|
2012-05-30 00:36:45 +00:00
|
|
|
return this._grid.get_n_children() - this._grid.get_n_skip_paint();
|
2010-07-21 02:22:19 +00:00
|
|
|
}
|
2011-11-20 17:56:27 +00:00
|
|
|
});
|