/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; const Gdk = imports.gi.Gdk; const Gio = imports.gi.Gio; const GLib = imports.gi.GLib; const Lang = imports.lang; const Meta = imports.gi.Meta; const Pango = imports.gi.Pango; const St = imports.gi.St; const Shell = imports.gi.Shell; const Signals = imports.signals; const Gettext = imports.gettext.domain('gnome-shell'); const _ = Gettext.gettext; const Params = imports.misc.params; const Lightbox = imports.ui.lightbox; const Main = imports.ui.main; const Tweener = imports.ui.tweener; const OPEN_AND_CLOSE_TIME = 0.1; const FADE_OUT_DIALOG_TIME = 1.0; const State = { OPENED: 0, CLOSED: 1, OPENING: 2, CLOSING: 3, FADED_OUT: 4 }; function ModalDialog() { this._init(); } ModalDialog.prototype = { _init: function(params) { params = Params.parse(params, { styleClass: null }); this.state = State.CLOSED; this._group = new St.Group({ visible: false, x: 0, y: 0 }); Main.uiGroup.add_actor(this._group); global.focus_manager.add_group(this._group); this._initialKeyFocus = this._group; this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy)); this._actionKeys = {}; this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); this._lightbox = new Lightbox.Lightbox(this._group, { inhibitEvents: true }); this._backgroundBin = new St.Bin(); this._group.add_actor(this._backgroundBin); this._lightbox.highlight(this._backgroundBin); this._dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog', vertical: true }); if (params.styleClass != null) { this._dialogLayout.add_style_class_name(params.styleClass); } this._backgroundBin.child = this._dialogLayout; this.contentLayout = new St.BoxLayout({ vertical: true }); this._dialogLayout.add(this.contentLayout, { x_fill: true, y_fill: true, x_align: St.Align.MIDDLE, y_align: St.Align.START }); this._buttonLayout = new St.BoxLayout({ opacity: 220, vertical: false }); this._dialogLayout.add(this._buttonLayout, { expand: true, x_align: St.Align.MIDDLE, y_align: St.Align.END }); }, setButtons: function(buttons) { this._buttonLayout.destroy_children(); this._actionKeys = {}; let i = 0; for (let index in buttons) { let buttonInfo = buttons[index]; let label = buttonInfo['label']; let action = buttonInfo['action']; let key = buttonInfo['key']; let button = new St.Button({ style_class: 'modal-dialog-button', reactive: true, can_focus: true, label: label }); let x_alignment; if (buttons.length == 1) x_alignment = St.Align.END; else if (i == 0) x_alignment = St.Align.START; else if (i == buttons.length - 1) x_alignment = St.Align.END; else x_alignment = St.Align.MIDDLE; this._initialKeyFocus = button; this._buttonLayout.add(button, { expand: true, x_fill: false, y_fill: false, x_align: x_alignment, y_align: St.Align.MIDDLE }); button.connect('clicked', action); if (key) this._actionKeys[key] = action; i++; } }, _onKeyPressEvent: function(object, keyPressEvent) { let symbol = keyPressEvent.get_key_symbol(); let action = this._actionKeys[symbol]; if (action) action(); }, _onGroupDestroy: function() { this.emit('destroy'); }, _fadeOpen: function() { let monitor = global.get_focus_monitor(); this._backgroundBin.set_position(monitor.x, monitor.y); this._backgroundBin.set_size(monitor.width, monitor.height); this.state = State.OPENING; this._dialogLayout.opacity = 255; this._lightbox.show(); this._group.opacity = 0; this._group.show(); Tweener.addTween(this._group, { opacity: 255, time: OPEN_AND_CLOSE_TIME, transition: 'easeOutQuad', onComplete: Lang.bind(this, function() { this._initialKeyFocus.grab_key_focus(); this.state = State.OPENED; this.emit('opened'); }), }); }, open: function(timestamp) { if (this.state == State.OPENED || this.state == State.OPENING) return true; if (!Main.pushModal(this._group, timestamp)) return false; global.stage.set_key_focus(this._group); this._fadeOpen(); return true; }, close: function(timestamp) { if (this.state == State.CLOSED || this.state == State.CLOSING) return; let needsPopModal; if (this.state == State.OPENED || this.state == State.OPENING) needsPopModal = true; else needsPopModal = false; this.state = State.CLOSING; Tweener.addTween(this._group, { opacity: 0, time: OPEN_AND_CLOSE_TIME, transition: 'easeOutQuad', onComplete: Lang.bind(this, function() { this.state = State.CLOSED; this._group.hide(); if (needsPopModal) Main.popModal(this._group, timestamp); }) }); }, // This method is like close, but fades the dialog out much slower, // and leaves the lightbox in place. Once in the faded out state, // the dialog can be brought back by an open call, or the lightbox // can be dismissed by a close call. // // The main point of this method is to give some indication to the user // that the dialog reponse has been acknowledged but will take a few // moments before being processed. // e.g., if a user clicked "Log Out" then the dialog should go away // imediately, but the lightbox should remain until the logout is // complete. _fadeOutDialog: function(timestamp) { if (this.state == State.CLOSED || this.state == State.CLOSING) return; if (this.state == State.FADED_OUT) return; Tweener.addTween(this._dialogLayout, { opacity: 0, time: FADE_OUT_DIALOG_TIME, transition: 'easeOutQuad', onComplete: Lang.bind(this, function() { this.state = State.FADED_OUT; Main.popModal(this._group, timestamp); }) }); } }; Signals.addSignalMethods(ModalDialog.prototype);