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