2011-09-28 09:16:26 -04:00
|
|
|
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
2019-01-31 09:07:06 -05:00
|
|
|
/* exported ModalDialog */
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2019-05-23 16:45:44 -04:00
|
|
|
const { Atk, Clutter, GObject, Shell, St } = imports.gi;
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2017-01-19 13:49:52 -05:00
|
|
|
const Dialog = imports.ui.dialog;
|
2012-08-16 15:37:27 -04:00
|
|
|
const Layout = imports.ui.layout;
|
2010-10-20 20:46:38 -04:00
|
|
|
const Lightbox = imports.ui.lightbox;
|
|
|
|
const Main = imports.ui.main;
|
2019-02-08 22:21:36 -05:00
|
|
|
const Params = imports.misc.params;
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2019-08-01 19:13:10 -04:00
|
|
|
var OPEN_AND_CLOSE_TIME = 100;
|
|
|
|
var FADE_OUT_DIALOG_TIME = 1000;
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2017-07-18 13:47:27 -04:00
|
|
|
var State = {
|
2010-10-20 20:46:38 -04:00
|
|
|
OPENED: 0,
|
|
|
|
CLOSED: 1,
|
|
|
|
OPENING: 2,
|
|
|
|
CLOSING: 3,
|
|
|
|
FADED_OUT: 4
|
|
|
|
};
|
|
|
|
|
2019-05-23 16:45:44 -04:00
|
|
|
var ModalDialog = GObject.registerClass({
|
2019-01-29 13:15:23 -05:00
|
|
|
Properties: {
|
|
|
|
'state': GObject.ParamSpec.int('state', 'Dialog state', 'state',
|
|
|
|
GObject.ParamFlags.READABLE,
|
|
|
|
Math.min(...Object.values(State)),
|
|
|
|
Math.max(...Object.values(State)),
|
|
|
|
State.CLOSED)
|
|
|
|
},
|
2019-05-23 16:45:44 -04:00
|
|
|
Signals: { 'opened': {}, 'closed': {} }
|
|
|
|
}, class ModalDialog extends St.Widget {
|
|
|
|
_init(params) {
|
|
|
|
super._init({ visible: false,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
accessible_role: Atk.Role.DIALOG });
|
|
|
|
|
2011-08-27 23:04:56 -04:00
|
|
|
params = Params.parse(params, { shellReactive: false,
|
2012-05-24 16:47:48 -04:00
|
|
|
styleClass: null,
|
2014-12-11 09:35:40 -05:00
|
|
|
actionMode: Shell.ActionMode.SYSTEM_MODAL,
|
2013-04-06 10:53:11 -04:00
|
|
|
shouldFadeIn: true,
|
2014-05-08 18:56:23 -04:00
|
|
|
shouldFadeOut: true,
|
2013-04-06 10:53:11 -04:00
|
|
|
destroyOnClose: true });
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2019-05-23 16:58:53 -04:00
|
|
|
this._state = State.CLOSED;
|
2011-03-18 11:28:18 -04:00
|
|
|
this._hasModal = false;
|
2014-12-11 09:35:40 -05:00
|
|
|
this._actionMode = params.actionMode;
|
2011-08-27 23:04:56 -04:00
|
|
|
this._shellReactive = params.shellReactive;
|
2012-08-22 03:27:32 -04:00
|
|
|
this._shouldFadeIn = params.shouldFadeIn;
|
2014-05-08 18:56:23 -04:00
|
|
|
this._shouldFadeOut = params.shouldFadeOut;
|
2013-04-06 10:53:11 -04:00
|
|
|
this._destroyOnClose = params.destroyOnClose;
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2019-05-23 16:45:44 -04:00
|
|
|
Main.layoutManager.modalDialogGroup.add_actor(this);
|
2011-03-16 18:57:05 -04:00
|
|
|
|
|
|
|
let constraint = new Clutter.BindConstraint({ source: global.stage,
|
2012-02-15 11:58:56 -05:00
|
|
|
coordinate: Clutter.BindCoordinate.ALL });
|
2019-05-23 16:45:44 -04:00
|
|
|
this.add_constraint(constraint);
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2013-05-07 16:11:42 -04:00
|
|
|
this.backgroundStack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
|
2013-03-22 19:39:05 -04:00
|
|
|
this._backgroundBin = new St.Bin({ child: this.backgroundStack,
|
|
|
|
x_fill: true, y_fill: true });
|
2012-08-16 15:37:27 -04:00
|
|
|
this._monitorConstraint = new Layout.MonitorConstraint();
|
|
|
|
this._backgroundBin.add_constraint(this._monitorConstraint);
|
2019-05-23 16:45:44 -04:00
|
|
|
this.add_actor(this._backgroundBin);
|
2011-03-18 11:28:18 -04:00
|
|
|
|
2017-01-19 13:49:52 -05:00
|
|
|
this.dialogLayout = new Dialog.Dialog(this.backgroundStack, params.styleClass);
|
|
|
|
this.contentLayout = this.dialogLayout.contentLayout;
|
|
|
|
this.buttonLayout = this.dialogLayout.buttonLayout;
|
2011-08-27 23:04:56 -04:00
|
|
|
|
|
|
|
if (!this._shellReactive) {
|
2019-05-23 16:45:44 -04:00
|
|
|
this._lightbox = new Lightbox.Lightbox(this,
|
2012-12-14 20:41:03 -05:00
|
|
|
{ inhibitEvents: true,
|
|
|
|
radialEffect: true });
|
2011-08-27 23:04:56 -04:00
|
|
|
this._lightbox.highlight(this._backgroundBin);
|
|
|
|
|
2013-02-22 06:23:56 -05:00
|
|
|
this._eventBlocker = new Clutter.Actor({ reactive: true });
|
2013-03-22 19:39:05 -04:00
|
|
|
this.backgroundStack.add_actor(this._eventBlocker);
|
2011-08-27 23:04:56 -04:00
|
|
|
}
|
2011-04-06 12:54:47 -04:00
|
|
|
|
2012-08-07 14:33:46 -04:00
|
|
|
global.focus_manager.add_group(this.dialogLayout);
|
2017-09-28 06:38:33 -04:00
|
|
|
this._initialKeyFocus = null;
|
2012-02-12 13:55:59 -05:00
|
|
|
this._initialKeyFocusDestroyId = 0;
|
2011-04-06 12:54:47 -04:00
|
|
|
this._savedKeyFocus = null;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2019-05-23 16:58:53 -04:00
|
|
|
get state() {
|
|
|
|
return this._state;
|
|
|
|
}
|
|
|
|
|
|
|
|
_setState(state) {
|
|
|
|
if (this._state == state)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this._state = state;
|
|
|
|
this.notify('state');
|
|
|
|
}
|
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
clearButtons() {
|
2017-01-19 13:49:52 -05:00
|
|
|
this.dialogLayout.clearButtons();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-01-30 18:15:19 -05:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
setButtons(buttons) {
|
2012-11-01 12:07:48 -04:00
|
|
|
this.clearButtons();
|
2012-02-28 10:53:46 -05:00
|
|
|
|
2018-03-02 18:31:00 -05:00
|
|
|
for (let buttonInfo of buttons)
|
2015-07-29 07:45:11 -04:00
|
|
|
this.addButton(buttonInfo);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
addButton(buttonInfo) {
|
2017-01-19 13:49:52 -05:00
|
|
|
return this.dialogLayout.addButton(buttonInfo);
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_fadeOpen(onPrimary) {
|
2012-10-14 12:34:22 -04:00
|
|
|
if (onPrimary)
|
|
|
|
this._monitorConstraint.primary = true;
|
|
|
|
else
|
2018-01-03 02:55:38 -05:00
|
|
|
this._monitorConstraint.index = global.display.get_current_monitor();
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2019-05-23 16:58:53 -04:00
|
|
|
this._setState(State.OPENING);
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2012-08-07 14:33:46 -04:00
|
|
|
this.dialogLayout.opacity = 255;
|
2011-08-27 23:04:56 -04:00
|
|
|
if (this._lightbox)
|
|
|
|
this._lightbox.show();
|
2019-05-23 16:45:44 -04:00
|
|
|
this.opacity = 0;
|
|
|
|
this.show();
|
2018-07-20 15:46:19 -04:00
|
|
|
this.ease({
|
|
|
|
opacity: 255,
|
|
|
|
duration: this._shouldFadeIn ? OPEN_AND_CLOSE_TIME : 0,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => {
|
|
|
|
this._setState(State.OPENED);
|
|
|
|
this.emit('opened');
|
|
|
|
}
|
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
setInitialKeyFocus(actor) {
|
2012-02-12 13:55:59 -05:00
|
|
|
if (this._initialKeyFocusDestroyId)
|
|
|
|
this._initialKeyFocus.disconnect(this._initialKeyFocusDestroyId);
|
|
|
|
|
2011-03-15 16:05:40 -04:00
|
|
|
this._initialKeyFocus = actor;
|
2012-02-12 13:55:59 -05:00
|
|
|
|
2017-10-30 20:38:18 -04:00
|
|
|
this._initialKeyFocusDestroyId = actor.connect('destroy', () => {
|
2017-09-28 06:38:33 -04:00
|
|
|
this._initialKeyFocus = null;
|
2012-02-12 13:55:59 -05:00
|
|
|
this._initialKeyFocusDestroyId = 0;
|
2017-10-30 20:38:18 -04:00
|
|
|
});
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-03-15 16:05:40 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
open(timestamp, onPrimary) {
|
2010-10-20 20:46:38 -04:00
|
|
|
if (this.state == State.OPENED || this.state == State.OPENING)
|
|
|
|
return true;
|
|
|
|
|
2014-01-21 21:39:52 -05:00
|
|
|
if (!this.pushModal(timestamp))
|
2010-10-20 20:46:38 -04:00
|
|
|
return false;
|
|
|
|
|
2012-10-14 12:34:22 -04:00
|
|
|
this._fadeOpen(onPrimary);
|
2010-10-20 20:46:38 -04:00
|
|
|
return true;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
_closeComplete() {
|
2019-05-23 16:58:53 -04:00
|
|
|
this._setState(State.CLOSED);
|
2019-05-23 16:45:44 -04:00
|
|
|
this.hide();
|
2014-05-08 18:56:23 -04:00
|
|
|
this.emit('closed');
|
|
|
|
|
|
|
|
if (this._destroyOnClose)
|
|
|
|
this.destroy();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2014-05-08 18:56:23 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
close(timestamp) {
|
2010-10-20 20:46:38 -04:00
|
|
|
if (this.state == State.CLOSED || this.state == State.CLOSING)
|
|
|
|
return;
|
|
|
|
|
2019-05-23 16:58:53 -04:00
|
|
|
this._setState(State.CLOSING);
|
2011-03-18 11:28:18 -04:00
|
|
|
this.popModal(timestamp);
|
2011-04-06 12:54:47 -04:00
|
|
|
this._savedKeyFocus = null;
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2018-07-20 15:46:19 -04:00
|
|
|
if (this._shouldFadeOut) {
|
|
|
|
this.ease({
|
|
|
|
opacity: 0,
|
|
|
|
duration: OPEN_AND_CLOSE_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => this._closeComplete()
|
|
|
|
});
|
|
|
|
} else {
|
2014-05-08 18:56:23 -04:00
|
|
|
this._closeComplete();
|
2018-07-20 15:46:19 -04:00
|
|
|
}
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2010-10-20 20:46:38 -04:00
|
|
|
|
2011-03-18 11:28:18 -04:00
|
|
|
// Drop modal status without closing the dialog; this makes the
|
|
|
|
// dialog insensitive as well, so it needs to be followed shortly
|
|
|
|
// by either a close() or a pushModal()
|
2017-10-30 20:03:21 -04:00
|
|
|
popModal(timestamp) {
|
2011-03-18 11:28:18 -04:00
|
|
|
if (!this._hasModal)
|
|
|
|
return;
|
|
|
|
|
|
|
|
let focus = global.stage.key_focus;
|
2019-05-23 16:45:44 -04:00
|
|
|
if (focus && this.contains(focus))
|
2011-03-18 11:28:18 -04:00
|
|
|
this._savedKeyFocus = focus;
|
|
|
|
else
|
|
|
|
this._savedKeyFocus = null;
|
2019-05-23 16:45:44 -04:00
|
|
|
Main.popModal(this, timestamp);
|
2011-03-18 11:28:18 -04:00
|
|
|
this._hasModal = false;
|
|
|
|
|
2011-08-27 23:04:56 -04:00
|
|
|
if (!this._shellReactive)
|
|
|
|
this._eventBlocker.raise_top();
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-03-18 11:28:18 -04:00
|
|
|
|
2017-10-30 20:03:21 -04:00
|
|
|
pushModal(timestamp) {
|
2011-03-18 11:28:18 -04:00
|
|
|
if (this._hasModal)
|
|
|
|
return true;
|
2014-01-21 21:39:52 -05:00
|
|
|
|
|
|
|
let params = { actionMode: this._actionMode };
|
|
|
|
if (timestamp)
|
|
|
|
params['timestamp'] = timestamp;
|
2019-05-23 16:45:44 -04:00
|
|
|
if (!Main.pushModal(this, params))
|
2011-03-18 11:28:18 -04:00
|
|
|
return false;
|
|
|
|
|
|
|
|
this._hasModal = true;
|
|
|
|
if (this._savedKeyFocus) {
|
|
|
|
this._savedKeyFocus.grab_key_focus();
|
|
|
|
this._savedKeyFocus = null;
|
2013-07-03 11:26:01 -04:00
|
|
|
} else {
|
2017-09-28 06:38:33 -04:00
|
|
|
let focus = this._initialKeyFocus || this.dialogLayout.initialKeyFocus;
|
|
|
|
focus.grab_key_focus();
|
2013-07-03 11:26:01 -04:00
|
|
|
}
|
2011-03-18 11:28:18 -04:00
|
|
|
|
2011-08-27 23:04:56 -04:00
|
|
|
if (!this._shellReactive)
|
|
|
|
this._eventBlocker.lower_bottom();
|
2011-03-18 11:28:18 -04:00
|
|
|
return true;
|
2017-10-30 21:19:44 -04:00
|
|
|
}
|
2011-03-18 11:28:18 -04:00
|
|
|
|
2010-10-20 20:46:38 -04:00
|
|
|
// 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
|
2019-05-15 15:32:29 -04:00
|
|
|
// that the dialog response has been acknowledged but will take a few
|
2010-10-20 20:46:38 -04:00
|
|
|
// moments before being processed.
|
|
|
|
// e.g., if a user clicked "Log Out" then the dialog should go away
|
2019-05-15 15:32:29 -04:00
|
|
|
// immediately, but the lightbox should remain until the logout is
|
2010-10-20 20:46:38 -04:00
|
|
|
// complete.
|
2017-10-30 20:03:21 -04:00
|
|
|
_fadeOutDialog(timestamp) {
|
2010-10-20 20:46:38 -04:00
|
|
|
if (this.state == State.CLOSED || this.state == State.CLOSING)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (this.state == State.FADED_OUT)
|
|
|
|
return;
|
|
|
|
|
2011-03-18 11:28:18 -04:00
|
|
|
this.popModal(timestamp);
|
2018-07-20 15:46:19 -04:00
|
|
|
this.dialogLayout.ease({
|
|
|
|
opacity: 0,
|
|
|
|
duration: FADE_OUT_DIALOG_TIME,
|
|
|
|
mode: Clutter.AnimationMode.EASE_OUT_QUAD,
|
|
|
|
onComplete: () => this.state = State.FADED_OUT
|
|
|
|
});
|
2010-10-20 20:46:38 -04:00
|
|
|
}
|
2019-05-23 16:45:44 -04:00
|
|
|
});
|