Blame js/ui/messageList.js

Packit d345d1
const { Atk, Clutter, Gio, GLib, GObject, Meta, Pango, St } = imports.gi;
Packit d345d1
const Main = imports.ui.main;
Packit d345d1
const MessageTray = imports.ui.messageTray;
Packit d345d1
const Signals = imports.signals;
Packit d345d1
Packit d345d1
const Calendar = imports.ui.calendar;
Packit d345d1
const Tweener = imports.ui.tweener;
Packit d345d1
const Util = imports.misc.util;
Packit d345d1
Packit d345d1
var MESSAGE_ANIMATION_TIME = 0.1;
Packit d345d1
Packit d345d1
var DEFAULT_EXPAND_LINES = 6;
Packit d345d1
Packit d345d1
function _fixMarkup(text, allowMarkup) {
Packit d345d1
    if (allowMarkup) {
Packit d345d1
        // Support &, ", ', < and >, escape all other
Packit d345d1
        // occurrences of '&'.
Packit d345d1
        let _text = text.replace(/&(?!amp;|quot;|apos;|lt;|gt;)/g, '&');
Packit d345d1
Packit d345d1
        // Support , , and <u>, escape anything else
Packit d345d1
        // so it displays as raw markup.
Packit d345d1
        // Ref: https://developer.gnome.org/notification-spec/#markup
Packit d345d1
        _text = _text.replace(/<(?!\/?[biu]>)/g, '<');
Packit d345d1
Packit d345d1
        try {
Packit d345d1
            Pango.parse_markup(_text, -1, '');
Packit d345d1
            return _text;
Packit d345d1
        } catch (e) {}
Packit d345d1
    }
Packit d345d1
Packit d345d1
    // !allowMarkup, or invalid markup
Packit d345d1
    return GLib.markup_escape_text(text, -1);
Packit d345d1
}
Packit d345d1
Packit d345d1
var URLHighlighter = class URLHighlighter {
Packit d345d1
    constructor(text, lineWrap, allowMarkup) {
Packit d345d1
        if (!text)
Packit d345d1
            text = '';
Packit d345d1
        this.actor = new St.Label({ reactive: true, style_class: 'url-highlighter',
Packit d345d1
                                    x_expand: true, x_align: Clutter.ActorAlign.START });
Packit d345d1
        this._linkColor = '#ccccff';
Packit d345d1
        this.actor.connect('style-changed', () => {
Packit d345d1
            let [hasColor, color] = this.actor.get_theme_node().lookup_color('link-color', false);
Packit d345d1
            if (hasColor) {
Packit d345d1
                let linkColor = color.to_string().substr(0, 7);
Packit d345d1
                if (linkColor != this._linkColor) {
Packit d345d1
                    this._linkColor = linkColor;
Packit d345d1
                    this._highlightUrls();
Packit d345d1
                }
Packit d345d1
            }
Packit d345d1
        });
Packit d345d1
        this.actor.clutter_text.line_wrap = lineWrap;
Packit d345d1
        this.actor.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;
Packit d345d1
Packit d345d1
        this.setMarkup(text, allowMarkup);
Packit d345d1
        this.actor.connect('button-press-event', (actor, event) => {
Packit d345d1
            // Don't try to URL highlight when invisible.
Packit d345d1
            // The MessageTray doesn't actually hide us, so
Packit d345d1
            // we need to check for paint opacities as well.
Packit d345d1
            if (!actor.visible || actor.get_paint_opacity() == 0)
Packit d345d1
                return Clutter.EVENT_PROPAGATE;
Packit d345d1
Packit d345d1
            // Keep Notification.actor from seeing this and taking
Packit d345d1
            // a pointer grab, which would block our button-release-event
Packit d345d1
            // handler, if an URL is clicked
Packit d345d1
            return this._findUrlAtPos(event) != -1;
Packit d345d1
        });
Packit d345d1
        this.actor.connect('button-release-event', (actor, event) => {
Packit d345d1
            if (!actor.visible || actor.get_paint_opacity() == 0)
Packit d345d1
                return Clutter.EVENT_PROPAGATE;
Packit d345d1
Packit d345d1
            let urlId = this._findUrlAtPos(event);
Packit d345d1
            if (urlId != -1) {
Packit d345d1
                let url = this._urls[urlId].url;
Packit d345d1
                if (url.indexOf(':') == -1)
Packit d345d1
                    url = 'http://' + url;
Packit d345d1
Packit d345d1
                Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context(0, -1));
Packit d345d1
                return Clutter.EVENT_STOP;
Packit d345d1
            }
Packit d345d1
            return Clutter.EVENT_PROPAGATE;
Packit d345d1
        });
Packit d345d1
        this.actor.connect('motion-event', (actor, event) => {
Packit d345d1
            if (!actor.visible || actor.get_paint_opacity() == 0)
Packit d345d1
                return Clutter.EVENT_PROPAGATE;
Packit d345d1
Packit d345d1
            let urlId = this._findUrlAtPos(event);
Packit d345d1
            if (urlId != -1 && !this._cursorChanged) {
Packit d345d1
                global.display.set_cursor(Meta.Cursor.POINTING_HAND);
Packit d345d1
                this._cursorChanged = true;
Packit d345d1
            } else if (urlId == -1) {
Packit d345d1
                global.display.set_cursor(Meta.Cursor.DEFAULT);
Packit d345d1
                this._cursorChanged = false;
Packit d345d1
            }
Packit d345d1
            return Clutter.EVENT_PROPAGATE;
Packit d345d1
        });
Packit d345d1
        this.actor.connect('leave-event', () => {
Packit d345d1
            if (!this.actor.visible || this.actor.get_paint_opacity() == 0)
Packit d345d1
                return Clutter.EVENT_PROPAGATE;
Packit d345d1
Packit d345d1
            if (this._cursorChanged) {
Packit d345d1
                this._cursorChanged = false;
Packit d345d1
                global.display.set_cursor(Meta.Cursor.DEFAULT);
Packit d345d1
            }
Packit d345d1
            return Clutter.EVENT_PROPAGATE;
Packit d345d1
        });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setMarkup(text, allowMarkup) {
Packit d345d1
        text = text ? _fixMarkup(text, allowMarkup) : '';
Packit d345d1
        this._text = text;
Packit d345d1
Packit d345d1
        this.actor.clutter_text.set_markup(text);
Packit d345d1
        /* clutter_text.text contain text without markup */
Packit d345d1
        this._urls = Util.findUrls(this.actor.clutter_text.text);
Packit d345d1
        this._highlightUrls();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _highlightUrls() {
Packit d345d1
        // text here contain markup
Packit d345d1
        let urls = Util.findUrls(this._text);
Packit d345d1
        let markup = '';
Packit d345d1
        let pos = 0;
Packit d345d1
        for (let i = 0; i < urls.length; i++) {
Packit d345d1
            let url = urls[i];
Packit d345d1
            let str = this._text.substr(pos, url.pos - pos);
Packit d345d1
            markup += str + '<u>' + url.url + '</u>';
Packit d345d1
            pos = url.pos + url.url.length;
Packit d345d1
        }
Packit d345d1
        markup += this._text.substr(pos);
Packit d345d1
        this.actor.clutter_text.set_markup(markup);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _findUrlAtPos(event) {
Packit d345d1
        let success;
Packit d345d1
        let [x, y] = event.get_coords();
Packit d345d1
        [success, x, y] = this.actor.transform_stage_point(x, y);
Packit d345d1
        let find_pos = -1;
Packit d345d1
        for (let i = 0; i < this.actor.clutter_text.text.length; i++) {
Packit d345d1
            let [success, px, py, line_height] = this.actor.clutter_text.position_to_coords(i);
Packit d345d1
            if (py > y || py + line_height < y || x < px)
Packit d345d1
                continue;
Packit d345d1
            find_pos = i;
Packit d345d1
        }
Packit d345d1
        if (find_pos != -1) {
Packit d345d1
            for (let i = 0; i < this._urls.length; i++)
Packit d345d1
            if (find_pos >= this._urls[i].pos &&
Packit d345d1
                this._urls[i].pos + this._urls[i].url.length > find_pos)
Packit d345d1
                return i;
Packit d345d1
        }
Packit d345d1
        return -1;
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Packit d345d1
var ScaleLayout = GObject.registerClass(
Packit d345d1
class ScaleLayout extends Clutter.BinLayout {
Packit d345d1
    _init(params) {
Packit d345d1
        this._container = null;
Packit d345d1
        super._init(params);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _connectContainer(container) {
Packit d345d1
        if (this._container == container)
Packit d345d1
            return;
Packit d345d1
Packit d345d1
        if (this._container)
Packit d345d1
            for (let id of this._signals)
Packit d345d1
                this._container.disconnect(id);
Packit d345d1
Packit d345d1
        this._container = container;
Packit d345d1
        this._signals = [];
Packit d345d1
Packit d345d1
        if (this._container)
Packit d345d1
            for (let signal of ['notify::scale-x', 'notify::scale-y']) {
Packit d345d1
                let id = this._container.connect(signal, () => {
Packit d345d1
                    this.layout_changed();
Packit d345d1
                });
Packit d345d1
                this._signals.push(id);
Packit d345d1
            }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_get_preferred_width(container, forHeight) {
Packit d345d1
        this._connectContainer(container);
Packit d345d1
Packit d345d1
        let [min, nat] = super.vfunc_get_preferred_width(container, forHeight);
Packit d345d1
        return [Math.floor(min * container.scale_x),
Packit d345d1
                Math.floor(nat * container.scale_x)];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_get_preferred_height(container, forWidth) {
Packit d345d1
        this._connectContainer(container);
Packit d345d1
Packit d345d1
        let [min, nat] = super.vfunc_get_preferred_height(container, forWidth);
Packit d345d1
        return [Math.floor(min * container.scale_y),
Packit d345d1
                Math.floor(nat * container.scale_y)];
Packit d345d1
    }
Packit d345d1
});
Packit d345d1
Packit d345d1
var LabelExpanderLayout = GObject.registerClass({
Packit d345d1
    Properties: { 'expansion': GObject.ParamSpec.double('expansion',
Packit d345d1
                                                        'Expansion',
Packit d345d1
                                                        'Expansion of the layout, between 0 (collapsed) ' +
Packit d345d1
                                                        'and 1 (fully expanded',
Packit d345d1
                                                         GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
Packit d345d1
                                                         0, 1, 0)},
Packit d345d1
}, class LabelExpanderLayout extends Clutter.LayoutManager {
Packit d345d1
    _init(params) {
Packit d345d1
        this._expansion = 0;
Packit d345d1
        this._expandLines = DEFAULT_EXPAND_LINES;
Packit d345d1
Packit d345d1
        super._init(params);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    get expansion() {
Packit d345d1
        return this._expansion;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    set expansion(v) {
Packit d345d1
        if (v == this._expansion)
Packit d345d1
            return;
Packit d345d1
        this._expansion = v;
Packit d345d1
        this.notify('expansion');
Packit d345d1
Packit d345d1
        let visibleIndex = this._expansion > 0 ? 1 : 0;
Packit d345d1
        for (let i = 0; this._container && i < this._container.get_n_children(); i++)
Packit d345d1
            this._container.get_child_at_index(i).visible = (i == visibleIndex);
Packit d345d1
Packit d345d1
        this.layout_changed();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    set expandLines(v) {
Packit d345d1
        if (v == this._expandLines)
Packit d345d1
            return;
Packit d345d1
        this._expandLines = v;
Packit d345d1
        if (this._expansion > 0)
Packit d345d1
            this.layout_changed();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_set_container(container) {
Packit d345d1
        this._container = container;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_get_preferred_width(container, forHeight) {
Packit d345d1
        let [min, nat] = [0, 0];
Packit d345d1
Packit d345d1
        for (let i = 0; i < container.get_n_children(); i++) {
Packit d345d1
            if (i > 1)
Packit d345d1
                break; // we support one unexpanded + one expanded child
Packit d345d1
Packit d345d1
            let child = container.get_child_at_index(i);
Packit d345d1
            let [childMin, childNat] = child.get_preferred_width(forHeight);
Packit d345d1
            [min, nat] = [Math.max(min, childMin), Math.max(nat, childNat)];
Packit d345d1
        }
Packit d345d1
Packit d345d1
        return [min, nat];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_get_preferred_height(container, forWidth) {
Packit d345d1
        let [min, nat] = [0, 0];
Packit d345d1
Packit d345d1
        let children = container.get_children();
Packit d345d1
        if (children[0])
Packit d345d1
            [min, nat] = children[0].get_preferred_height(forWidth);
Packit d345d1
Packit d345d1
        if (children[1]) {
Packit d345d1
            let [min2, nat2] = children[1].get_preferred_height(forWidth);
Packit d345d1
            let [expMin, expNat] = [Math.min(min2, min * this._expandLines),
Packit d345d1
                                    Math.min(nat2, nat * this._expandLines)];
Packit d345d1
            [min, nat] = [min + this._expansion * (expMin - min),
Packit d345d1
                          nat + this._expansion * (expNat - nat)];
Packit d345d1
        }
Packit d345d1
Packit d345d1
        return [min, nat];
Packit d345d1
    }
Packit d345d1
Packit d345d1
    vfunc_allocate(container, box, flags) {
Packit d345d1
        for (let i = 0; i < container.get_n_children(); i++) {
Packit d345d1
            let child = container.get_child_at_index(i);
Packit d345d1
Packit d345d1
            if (child.visible)
Packit d345d1
                child.allocate(box, flags);
Packit d345d1
        }
Packit d345d1
Packit d345d1
    }
Packit d345d1
});
Packit d345d1
Packit d345d1
var Message = class Message {
Packit d345d1
    constructor(title, body) {
Packit d345d1
        this.expanded = false;
Packit d345d1
Packit d345d1
        this._useBodyMarkup = false;
Packit d345d1
Packit d345d1
        this.actor = new St.Button({ style_class: 'message',
Packit d345d1
                                     accessible_role: Atk.Role.NOTIFICATION,
Packit d345d1
                                     can_focus: true,
Packit d345d1
                                     x_expand: true, x_fill: true });
Packit d345d1
        this.actor.connect('key-press-event',
Packit d345d1
                           this._onKeyPressed.bind(this));
Packit d345d1
Packit d345d1
        let vbox = new St.BoxLayout({ vertical: true });
Packit d345d1
        this.actor.set_child(vbox);
Packit d345d1
Packit d345d1
        let hbox = new St.BoxLayout();
Packit d345d1
        vbox.add_actor(hbox);
Packit d345d1
Packit d345d1
        this._actionBin = new St.Widget({ layout_manager: new ScaleLayout(),
Packit d345d1
                                          visible: false });
Packit d345d1
        vbox.add_actor(this._actionBin);
Packit d345d1
Packit d345d1
        this._iconBin = new St.Bin({ style_class: 'message-icon-bin',
Packit d345d1
                                     y_expand: true,
Packit d345d1
                                     y_align: St.Align.START,
Packit d345d1
                                     visible: false });
Packit d345d1
        hbox.add_actor(this._iconBin);
Packit d345d1
Packit d345d1
        let contentBox = new St.BoxLayout({ style_class: 'message-content',
Packit d345d1
                                            vertical: true, x_expand: true });
Packit d345d1
        hbox.add_actor(contentBox);
Packit d345d1
Packit d345d1
        this._mediaControls = new St.BoxLayout();
Packit d345d1
        hbox.add_actor(this._mediaControls);
Packit d345d1
Packit d345d1
        let titleBox = new St.BoxLayout();
Packit d345d1
        contentBox.add_actor(titleBox);
Packit d345d1
Packit d345d1
        this.titleLabel = new St.Label({ style_class: 'message-title' });
Packit d345d1
        this.setTitle(title);
Packit d345d1
        titleBox.add_actor(this.titleLabel);
Packit d345d1
Packit d345d1
        this._secondaryBin = new St.Bin({ style_class: 'message-secondary-bin',
Packit d345d1
                                          x_expand: true, y_expand: true,
Packit d345d1
                                          x_fill: true, y_fill: true });
Packit d345d1
        titleBox.add_actor(this._secondaryBin);
Packit d345d1
Packit d345d1
        let closeIcon = new St.Icon({ icon_name: 'window-close-symbolic',
Packit d345d1
                                      icon_size: 16 });
Packit d345d1
        this._closeButton = new St.Button({ child: closeIcon, opacity: 0 });
Packit d345d1
        titleBox.add_actor(this._closeButton);
Packit d345d1
Packit d345d1
        this._bodyStack = new St.Widget({ x_expand: true });
Packit d345d1
        this._bodyStack.layout_manager = new LabelExpanderLayout();
Packit d345d1
        contentBox.add_actor(this._bodyStack);
Packit d345d1
Packit d345d1
        this.bodyLabel = new URLHighlighter('', false, this._useBodyMarkup);
Packit d345d1
        this.bodyLabel.actor.add_style_class_name('message-body');
Packit d345d1
        this._bodyStack.add_actor(this.bodyLabel.actor);
Packit d345d1
        this.setBody(body);
Packit d345d1
Packit d345d1
        this._closeButton.connect('clicked', this.close.bind(this));
Packit d345d1
        let actorHoverId = this.actor.connect('notify::hover', this._sync.bind(this));
Packit d345d1
        this._closeButton.connect('destroy', this.actor.disconnect.bind(this.actor, actorHoverId));
Packit d345d1
        this.actor.connect('clicked', this._onClicked.bind(this));
Packit d345d1
        this.actor.connect('destroy', this._onDestroy.bind(this));
Packit d345d1
        this._sync();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    close() {
Packit d345d1
        this.emit('close');
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setIcon(actor) {
Packit d345d1
        this._iconBin.child = actor;
Packit d345d1
        this._iconBin.visible = (actor != null);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setSecondaryActor(actor) {
Packit d345d1
        this._secondaryBin.child = actor;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setTitle(text) {
Packit d345d1
        let title = text ? _fixMarkup(text.replace(/\n/g, ' '), false) : '';
Packit d345d1
        this.titleLabel.clutter_text.set_markup(title);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setBody(text) {
Packit d345d1
        this._bodyText = text;
Packit d345d1
        this.bodyLabel.setMarkup(text ? text.replace(/\n/g, ' ') : '',
Packit d345d1
                                 this._useBodyMarkup);
Packit d345d1
        if (this._expandedLabel)
Packit d345d1
            this._expandedLabel.setMarkup(text, this._useBodyMarkup);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setUseBodyMarkup(enable) {
Packit d345d1
        if (this._useBodyMarkup === enable)
Packit d345d1
            return;
Packit d345d1
        this._useBodyMarkup = enable;
Packit d345d1
        if (this.bodyLabel)
Packit d345d1
            this.setBody(this._bodyText);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setActionArea(actor) {
Packit d345d1
        if (actor == null) {
Packit d345d1
            if (this._actionBin.get_n_children() > 0)
Packit d345d1
                this._actionBin.get_child_at_index(0).destroy();
Packit d345d1
            return;
Packit d345d1
        }
Packit d345d1
Packit d345d1
        if (this._actionBin.get_n_children() > 0)
Packit d345d1
            throw new Error('Message already has an action area');
Packit d345d1
Packit d345d1
        this._actionBin.add_actor(actor);
Packit d345d1
        this._actionBin.visible = this.expanded;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    addMediaControl(iconName, callback) {
Packit d345d1
        let icon = new St.Icon({ icon_name: iconName, icon_size: 16 });
Packit d345d1
        let button = new St.Button({ style_class: 'message-media-control',
Packit d345d1
                                     child: icon });
Packit d345d1
        button.connect('clicked', callback);
Packit d345d1
        this._mediaControls.add_actor(button);
Packit d345d1
        return button;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setExpandedBody(actor) {
Packit d345d1
        if (actor == null) {
Packit d345d1
            if (this._bodyStack.get_n_children() > 1)
Packit d345d1
                this._bodyStack.get_child_at_index(1).destroy();
Packit d345d1
            return;
Packit d345d1
        }
Packit d345d1
Packit d345d1
        if (this._bodyStack.get_n_children() > 1)
Packit d345d1
            throw new Error('Message already has an expanded body actor');
Packit d345d1
Packit d345d1
        this._bodyStack.insert_child_at_index(actor, 1);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setExpandedLines(nLines) {
Packit d345d1
        this._bodyStack.layout_manager.expandLines = nLines;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    expand(animate) {
Packit d345d1
        this.expanded = true;
Packit d345d1
Packit d345d1
        this._actionBin.visible = (this._actionBin.get_n_children() > 0);
Packit d345d1
Packit d345d1
        if (this._bodyStack.get_n_children() < 2) {
Packit d345d1
            this._expandedLabel = new URLHighlighter(this._bodyText,
Packit d345d1
                                                     true, this._useBodyMarkup);
Packit d345d1
            this.setExpandedBody(this._expandedLabel.actor);
Packit d345d1
        }
Packit d345d1
Packit d345d1
        if (animate) {
Packit d345d1
            Tweener.addTween(this._bodyStack.layout_manager,
Packit d345d1
                             { expansion: 1,
Packit d345d1
                               time: MessageTray.ANIMATION_TIME,
Packit d345d1
                               transition: 'easeOutQuad' });
Packit d345d1
            this._actionBin.scale_y = 0;
Packit d345d1
            Tweener.addTween(this._actionBin,
Packit d345d1
                             { scale_y: 1,
Packit d345d1
                               time: MessageTray.ANIMATION_TIME,
Packit d345d1
                               transition: 'easeOutQuad' });
Packit d345d1
        } else {
Packit d345d1
            this._bodyStack.layout_manager.expansion = 1;
Packit d345d1
            this._actionBin.scale_y = 1;
Packit d345d1
        }
Packit d345d1
Packit d345d1
        this.emit('expanded');
Packit d345d1
    }
Packit d345d1
Packit d345d1
    unexpand(animate) {
Packit d345d1
        if (animate) {
Packit d345d1
            Tweener.addTween(this._bodyStack.layout_manager,
Packit d345d1
                             { expansion: 0,
Packit d345d1
                               time: MessageTray.ANIMATION_TIME,
Packit d345d1
                               transition: 'easeOutQuad' });
Packit d345d1
            Tweener.addTween(this._actionBin,
Packit d345d1
                             { scale_y: 0,
Packit d345d1
                               time: MessageTray.ANIMATION_TIME,
Packit d345d1
                               transition: 'easeOutQuad',
Packit d345d1
                               onCompleteScope: this,
Packit d345d1
                               onComplete() {
Packit d345d1
                                   this._actionBin.hide();
Packit d345d1
                                   this.expanded = false;
Packit d345d1
                               }});
Packit d345d1
        } else {
Packit d345d1
            this._bodyStack.layout_manager.expansion = 0;
Packit d345d1
            this._actionBin.scale_y = 0;
Packit d345d1
            this.expanded = false;
Packit d345d1
        }
Packit d345d1
Packit d345d1
        this.emit('unexpanded');
Packit d345d1
    }
Packit d345d1
Packit d345d1
    canClose() {
Packit d345d1
        return false;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _sync() {
Packit d345d1
        let visible = this.actor.hover && this.canClose();
Packit d345d1
        this._closeButton.opacity = visible ? 255 : 0;
Packit d345d1
        this._closeButton.reactive = visible;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onClicked() {
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onDestroy() {
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onKeyPressed(a, event) {
Packit d345d1
        let keysym = event.get_key_symbol();
Packit d345d1
Packit d345d1
        if (keysym == Clutter.KEY_Delete ||
Packit d345d1
            keysym == Clutter.KEY_KP_Delete) {
Packit d345d1
            this.close();
Packit d345d1
            return Clutter.EVENT_STOP;
Packit d345d1
        }
Packit d345d1
        return Clutter.EVENT_PROPAGATE;
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Signals.addSignalMethods(Message.prototype);
Packit d345d1
Packit d345d1
var MessageListSection = class MessageListSection {
Packit d345d1
    constructor() {
Packit d345d1
        this.actor = new St.BoxLayout({ style_class: 'message-list-section',
Packit d345d1
                                        clip_to_allocation: true,
Packit d345d1
                                        x_expand: true, vertical: true });
Packit d345d1
Packit d345d1
        this._list = new St.BoxLayout({ style_class: 'message-list-section-list',
Packit d345d1
                                        vertical: true });
Packit d345d1
        this.actor.add_actor(this._list);
Packit d345d1
Packit d345d1
        this._list.connect('actor-added', this._sync.bind(this));
Packit d345d1
        this._list.connect('actor-removed', this._sync.bind(this));
Packit d345d1
Packit d345d1
        let id = Main.sessionMode.connect('updated',
Packit d345d1
                                          this._sync.bind(this));
Packit d345d1
        this.actor.connect('destroy', () => {
Packit d345d1
            Main.sessionMode.disconnect(id);
Packit d345d1
        });
Packit d345d1
Packit d345d1
        this._messages = new Map();
Packit d345d1
        this._date = new Date();
Packit d345d1
        this.empty = true;
Packit d345d1
        this.canClear = false;
Packit d345d1
        this._sync();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _onKeyFocusIn(actor) {
Packit d345d1
        this.emit('key-focus-in', actor);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    get allowed() {
Packit d345d1
        return true;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    setDate(date) {
Packit d345d1
        if (Calendar.sameDay(date, this._date))
Packit d345d1
            return;
Packit d345d1
        this._date = date;
Packit d345d1
        this._sync();
Packit d345d1
    }
Packit d345d1
Packit d345d1
    addMessage(message, animate) {
Packit d345d1
        this.addMessageAtIndex(message, -1, animate);
Packit d345d1
    }
Packit d345d1
Packit d345d1
    addMessageAtIndex(message, index, animate) {
Packit d345d1
        let obj = {
Packit d345d1
            container: null,
Packit d345d1
            destroyId: 0,
Packit d345d1
            keyFocusId: 0,
Packit d345d1
            closeId: 0
Packit d345d1
        };
Packit d345d1
        let pivot = new Clutter.Point({ x: .5, y: .5 });
Packit d345d1
        let scale = animate ? 0 : 1;
Packit d345d1
        obj.container = new St.Widget({ layout_manager: new ScaleLayout(),
Packit d345d1
                                        pivot_point: pivot,
Packit d345d1
                                        scale_x: scale, scale_y: scale });
Packit d345d1
        obj.keyFocusId = message.actor.connect('key-focus-in',
Packit d345d1
            this._onKeyFocusIn.bind(this));
Packit d345d1
        obj.destroyId = message.actor.connect('destroy', () => {
Packit d345d1
            this.removeMessage(message, false);
Packit d345d1
        });
Packit d345d1
        obj.closeId = message.connect('close', () => {
Packit d345d1
            this.removeMessage(message, true);
Packit d345d1
        });
Packit d345d1
Packit d345d1
        this._messages.set(message, obj);
Packit d345d1
        obj.container.add_actor(message.actor);
Packit d345d1
Packit d345d1
        this._list.insert_child_at_index(obj.container, index);
Packit d345d1
Packit d345d1
        if (animate)
Packit d345d1
            Tweener.addTween(obj.container, { scale_x: 1,
Packit d345d1
                                              scale_y: 1,
Packit d345d1
                                              time: MESSAGE_ANIMATION_TIME,
Packit d345d1
                                              transition: 'easeOutQuad' });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    moveMessage(message, index, animate) {
Packit d345d1
        let obj = this._messages.get(message);
Packit d345d1
Packit d345d1
        if (!animate) {
Packit d345d1
            this._list.set_child_at_index(obj.container, index);
Packit d345d1
            return;
Packit d345d1
        }
Packit d345d1
Packit d345d1
        let onComplete = () => {
Packit d345d1
            this._list.set_child_at_index(obj.container, index);
Packit d345d1
            Tweener.addTween(obj.container, { scale_x: 1,
Packit d345d1
                                              scale_y: 1,
Packit d345d1
                                              time: MESSAGE_ANIMATION_TIME,
Packit d345d1
                                              transition: 'easeOutQuad' });
Packit d345d1
        };
Packit d345d1
        Tweener.addTween(obj.container, { scale_x: 0,
Packit d345d1
                                          scale_y: 0,
Packit d345d1
                                          time: MESSAGE_ANIMATION_TIME,
Packit d345d1
                                          transition: 'easeOutQuad',
Packit d345d1
                                          onComplete: onComplete });
Packit d345d1
    }
Packit d345d1
Packit d345d1
    removeMessage(message, animate) {
Packit d345d1
        let obj = this._messages.get(message);
Packit d345d1
Packit d345d1
        message.actor.disconnect(obj.destroyId);
Packit d345d1
        message.actor.disconnect(obj.keyFocusId);
Packit d345d1
        message.disconnect(obj.closeId);
Packit d345d1
Packit d345d1
        this._messages.delete(message);
Packit d345d1
Packit d345d1
        if (animate) {
Packit d345d1
            Tweener.addTween(obj.container, { scale_x: 0, scale_y: 0,
Packit d345d1
                                              time: MESSAGE_ANIMATION_TIME,
Packit d345d1
                                              transition: 'easeOutQuad',
Packit d345d1
                                              onComplete() {
Packit d345d1
                                                  obj.container.destroy();
Packit d345d1
                                                  global.sync_pointer();
Packit d345d1
                                              }});
Packit d345d1
        } else {
Packit d345d1
            obj.container.destroy();
Packit d345d1
            global.sync_pointer();
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    clear() {
Packit d345d1
        let messages = [...this._messages.keys()].filter(msg => msg.canClose());
Packit d345d1
Packit d345d1
        // If there are few messages, letting them all zoom out looks OK
Packit d345d1
        if (messages.length < 2) {
Packit d345d1
            messages.forEach(message => {
Packit d345d1
                message.close();
Packit d345d1
            });
Packit d345d1
        } else {
Packit d345d1
            // Otherwise we slide them out one by one, and then zoom them
Packit d345d1
            // out "off-screen" in the end to smoothly shrink the parent
Packit d345d1
            let delay = MESSAGE_ANIMATION_TIME / Math.max(messages.length, 5);
Packit d345d1
            for (let i = 0; i < messages.length; i++) {
Packit d345d1
                let message = messages[i];
Packit d345d1
                let obj = this._messages.get(message);
Packit d345d1
                Tweener.addTween(obj.container,
Packit d345d1
                                 { anchor_x: this._list.width,
Packit d345d1
                                   opacity: 0,
Packit d345d1
                                   time: MESSAGE_ANIMATION_TIME,
Packit d345d1
                                   delay: i * delay,
Packit d345d1
                                   transition: 'easeOutQuad',
Packit d345d1
                                   onComplete() {
Packit d345d1
                                       message.close();
Packit d345d1
                                   }});
Packit d345d1
            }
Packit d345d1
        }
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _canClear() {
Packit d345d1
        for (let message of this._messages.keys())
Packit d345d1
            if (message.canClose())
Packit d345d1
                return true;
Packit d345d1
        return false;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _shouldShow() {
Packit d345d1
        return !this.empty;
Packit d345d1
    }
Packit d345d1
Packit d345d1
    _sync() {
Packit d345d1
        let empty = this._list.get_n_children() == 0;
Packit d345d1
        let changed = this.empty !== empty;
Packit d345d1
        this.empty = empty;
Packit d345d1
Packit d345d1
        if (changed)
Packit d345d1
            this.emit('empty-changed');
Packit d345d1
Packit d345d1
        let canClear = this._canClear();
Packit d345d1
        changed = this.canClear !== canClear;
Packit d345d1
        this.canClear = canClear;
Packit d345d1
Packit d345d1
        if (changed)
Packit d345d1
            this.emit('can-clear-changed');
Packit d345d1
Packit d345d1
        this.actor.visible = this.allowed && this._shouldShow();
Packit d345d1
    }
Packit d345d1
};
Packit d345d1
Signals.addSignalMethods(MessageListSection.prototype);