c860409da5
ES6 allows to omit property names where they match the name of the assigned variable, which makes code less redunant and thus cleaner. We will soon enforce that in our eslint rules, so make sure we use the shorthand wherever possible. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/805
243 lines
7.8 KiB
JavaScript
243 lines
7.8 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported Dialog, MessageDialogContent */
|
|
|
|
const { Clutter, Gio, GObject, Pango, St } = imports.gi;
|
|
|
|
var Dialog = GObject.registerClass(
|
|
class Dialog extends St.Widget {
|
|
_init(parentActor, styleClass) {
|
|
super._init({ layout_manager: new Clutter.BinLayout() });
|
|
this.connect('destroy', this._onDestroy.bind(this));
|
|
|
|
this._initialKeyFocus = null;
|
|
this._initialKeyFocusDestroyId = 0;
|
|
this._pressedKey = null;
|
|
this._buttonKeys = {};
|
|
this._createDialog();
|
|
this.add_child(this._dialog);
|
|
|
|
if (styleClass != null)
|
|
this._dialog.add_style_class_name(styleClass);
|
|
|
|
this._parentActor = parentActor;
|
|
this._eventId = this._parentActor.connect('event', this._modalEventHandler.bind(this));
|
|
this._parentActor.add_child(this);
|
|
}
|
|
|
|
_createDialog() {
|
|
this._dialog = new St.BoxLayout({ style_class: 'modal-dialog',
|
|
x_align: Clutter.ActorAlign.CENTER,
|
|
y_align: Clutter.ActorAlign.CENTER,
|
|
vertical: true });
|
|
|
|
// modal dialogs are fixed width and grow vertically; set the request
|
|
// mode accordingly so wrapped labels are handled correctly during
|
|
// size requests.
|
|
this._dialog.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
|
|
this._dialog.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
|
|
|
|
this.contentLayout = new St.BoxLayout({
|
|
vertical: true,
|
|
style_class: 'modal-dialog-content-box',
|
|
y_expand: true,
|
|
});
|
|
this._dialog.add_child(this.contentLayout);
|
|
|
|
this.buttonLayout = new St.Widget({ layout_manager: new Clutter.BoxLayout({ homogeneous: true }) });
|
|
this._dialog.add_child(this.buttonLayout);
|
|
}
|
|
|
|
_onDestroy() {
|
|
if (this._eventId != 0)
|
|
this._parentActor.disconnect(this._eventId);
|
|
this._eventId = 0;
|
|
}
|
|
|
|
_modalEventHandler(actor, event) {
|
|
if (event.type() == Clutter.EventType.KEY_PRESS) {
|
|
this._pressedKey = event.get_key_symbol();
|
|
} else if (event.type() == Clutter.EventType.KEY_RELEASE) {
|
|
let pressedKey = this._pressedKey;
|
|
this._pressedKey = null;
|
|
|
|
let symbol = event.get_key_symbol();
|
|
if (symbol != pressedKey)
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
let buttonInfo = this._buttonKeys[symbol];
|
|
if (!buttonInfo)
|
|
return Clutter.EVENT_PROPAGATE;
|
|
|
|
let { button, action } = buttonInfo;
|
|
|
|
if (action && button.reactive) {
|
|
action();
|
|
return Clutter.EVENT_STOP;
|
|
}
|
|
}
|
|
|
|
return Clutter.EVENT_PROPAGATE;
|
|
}
|
|
|
|
_setInitialKeyFocus(actor) {
|
|
if (this._initialKeyFocus)
|
|
this._initialKeyFocus.disconnect(this._initialKeyFocusDestroyId);
|
|
|
|
this._initialKeyFocus = actor;
|
|
|
|
this._initialKeyFocusDestroyId = actor.connect('destroy', () => {
|
|
this._initialKeyFocus = null;
|
|
this._initialKeyFocusDestroyId = 0;
|
|
});
|
|
}
|
|
|
|
get initialKeyFocus() {
|
|
return this._initialKeyFocus || this;
|
|
}
|
|
|
|
addContent(actor) {
|
|
this.contentLayout.add(actor, { expand: true });
|
|
}
|
|
|
|
addButton(buttonInfo) {
|
|
let { label, action, key } = buttonInfo;
|
|
let isDefault = buttonInfo['default'];
|
|
let keys;
|
|
|
|
if (key)
|
|
keys = [key];
|
|
else if (isDefault)
|
|
keys = [Clutter.KEY_Return, Clutter.KEY_KP_Enter, Clutter.KEY_ISO_Enter];
|
|
else
|
|
keys = [];
|
|
|
|
let button = new St.Button({ style_class: 'modal-dialog-linked-button',
|
|
button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
|
|
reactive: true,
|
|
can_focus: true,
|
|
x_expand: true,
|
|
y_expand: true,
|
|
label });
|
|
button.connect('clicked', action);
|
|
|
|
buttonInfo['button'] = button;
|
|
|
|
if (isDefault)
|
|
button.add_style_pseudo_class('default');
|
|
|
|
if (this._initialKeyFocus == null || isDefault)
|
|
this._setInitialKeyFocus(button);
|
|
|
|
for (let i in keys)
|
|
this._buttonKeys[keys[i]] = buttonInfo;
|
|
|
|
this.buttonLayout.add_actor(button);
|
|
|
|
return button;
|
|
}
|
|
|
|
clearButtons() {
|
|
this.buttonLayout.destroy_all_children();
|
|
this._buttonKeys = {};
|
|
}
|
|
});
|
|
|
|
var MessageDialogContent = GObject.registerClass({
|
|
Properties: {
|
|
'icon': GObject.ParamSpec.object('icon', 'icon', 'icon',
|
|
GObject.ParamFlags.READWRITE |
|
|
GObject.ParamFlags.CONSTRUCT,
|
|
Gio.Icon.$gtype),
|
|
'title': GObject.ParamSpec.string('title', 'title', 'title',
|
|
GObject.ParamFlags.READWRITE |
|
|
GObject.ParamFlags.CONSTRUCT,
|
|
null),
|
|
'subtitle': GObject.ParamSpec.string('subtitle', 'subtitle', 'subtitle',
|
|
GObject.ParamFlags.READWRITE |
|
|
GObject.ParamFlags.CONSTRUCT,
|
|
null),
|
|
'body': GObject.ParamSpec.string('body', 'body', 'body',
|
|
GObject.ParamFlags.READWRITE |
|
|
GObject.ParamFlags.CONSTRUCT,
|
|
null)
|
|
}
|
|
}, class MessageDialogContent extends St.BoxLayout {
|
|
_init(params) {
|
|
this._icon = new St.Icon({ y_align: Clutter.ActorAlign.START });
|
|
this._title = new St.Label({ style_class: 'headline' });
|
|
this._subtitle = new St.Label();
|
|
this._body = new St.Label();
|
|
|
|
['icon', 'title', 'subtitle', 'body'].forEach(prop => {
|
|
this[`_${prop}`].add_style_class_name(`message-dialog-${prop}`);
|
|
});
|
|
|
|
let textProps = { ellipsize: Pango.EllipsizeMode.NONE,
|
|
line_wrap: true };
|
|
this._subtitle.clutter_text.set(textProps);
|
|
this._body.clutter_text.set(textProps);
|
|
|
|
let defaultParams = { style_class: 'message-dialog-main-layout' };
|
|
super._init(Object.assign(defaultParams, params));
|
|
|
|
this.messageBox = new St.BoxLayout({ style_class: 'message-dialog-content',
|
|
x_expand: true,
|
|
vertical: true });
|
|
|
|
this.messageBox.add_actor(this._title);
|
|
this.messageBox.add_actor(this._subtitle);
|
|
this.messageBox.add_actor(this._body);
|
|
|
|
this.add_actor(this._icon);
|
|
this.add_actor(this.messageBox);
|
|
}
|
|
|
|
get icon() {
|
|
return this._icon.gicon;
|
|
}
|
|
|
|
get title() {
|
|
return this._title.text;
|
|
}
|
|
|
|
get subtitle() {
|
|
return this._subtitle.text;
|
|
}
|
|
|
|
get body() {
|
|
return this._body.text;
|
|
}
|
|
|
|
set icon(icon) {
|
|
this._icon.set({
|
|
gicon: icon,
|
|
visible: icon != null
|
|
});
|
|
this.notify('icon');
|
|
}
|
|
|
|
set title(title) {
|
|
this._setLabel(this._title, 'title', title);
|
|
}
|
|
|
|
set subtitle(subtitle) {
|
|
this._setLabel(this._subtitle, 'subtitle', subtitle);
|
|
}
|
|
|
|
set body(body) {
|
|
this._setLabel(this._body, 'body', body);
|
|
}
|
|
|
|
_setLabel(label, prop, value) {
|
|
label.set({
|
|
text: value || '',
|
|
visible: value != null
|
|
});
|
|
this.notify(prop);
|
|
}
|
|
|
|
insertBeforeBody(actor) {
|
|
this.messageBox.insert_child_below(actor, this._body);
|
|
}
|
|
});
|