gnome-shell/js/ui/dialog.js
Zander Brown 350cd296fa js: Stop using ClutterContainer API
These have been long deprecated over in clutter, and (via several
vtables) simply forward the call to the equivalent ClutterActor methods

Save ourselves the hassle and just use ClutterActor methods directly

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3010>
2023-11-10 20:19:13 +00:00

366 lines
10 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
import Clutter from 'gi://Clutter';
import GLib from 'gi://GLib';
import GObject from 'gi://GObject';
import Meta from 'gi://Meta';
import Pango from 'gi://Pango';
import St from 'gi://St';
function _setLabel(label, value) {
label.set({
text: value || '',
visible: value !== null,
});
}
export const Dialog = GObject.registerClass(
class Dialog extends St.Widget {
_init(parentActor, styleClass) {
super._init({
layout_manager: new Clutter.BinLayout(),
reactive: true,
});
this.connect('destroy', this._onDestroy.bind(this));
this._initialKeyFocus = null;
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._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);
}
makeInactive() {
this.buttonLayout.get_children().forEach(c => c.set_reactive(false));
}
_onDestroy() {
this.makeInactive();
}
vfunc_event(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) {
this._initialKeyFocus?.disconnectObject(this);
this._initialKeyFocus = actor;
actor.connectObject('destroy',
() => (this._initialKeyFocus = null), this);
}
get initialKeyFocus() {
return this._initialKeyFocus || this;
}
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_child(button);
return button;
}
clearButtons() {
this.buttonLayout.destroy_all_children();
this._buttonKeys = {};
}
});
export const MessageDialogContent = GObject.registerClass({
Properties: {
'title': GObject.ParamSpec.string(
'title', 'title', 'title',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null),
'description': GObject.ParamSpec.string(
'description', 'description', 'description',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null),
},
}, class MessageDialogContent extends St.BoxLayout {
_init(params) {
this._title = new St.Label({style_class: 'message-dialog-title'});
this._description = new St.Label({style_class: 'message-dialog-description'});
this._description.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
this._description.clutter_text.line_wrap = true;
let defaultParams = {
style_class: 'message-dialog-content',
x_expand: true,
vertical: true,
};
super._init(Object.assign(defaultParams, params));
this.connect('notify::size', this._updateTitleStyle.bind(this));
this.connect('destroy', this._onDestroy.bind(this));
this.add_child(this._title);
this.add_child(this._description);
}
_onDestroy() {
if (this._updateTitleStyleLater) {
const laters = global.compositor.get_laters();
laters.remove(this._updateTitleStyleLater);
delete this._updateTitleStyleLater;
}
}
get title() {
return this._title.text;
}
get description() {
return this._description.text;
}
_updateTitleStyle() {
if (!this._title.mapped)
return;
this._title.ensure_style();
const [, titleNatWidth] = this._title.get_preferred_width(-1);
if (titleNatWidth > this.width) {
if (this._updateTitleStyleLater)
return;
const laters = global.compositor.get_laters();
this._updateTitleStyleLater = laters.add(Meta.LaterType.BEFORE_REDRAW, () => {
this._updateTitleStyleLater = 0;
this._title.add_style_class_name('lightweight');
return GLib.SOURCE_REMOVE;
});
}
}
set title(title) {
if (this._title.text === title)
return;
_setLabel(this._title, title);
this._title.remove_style_class_name('lightweight');
this._updateTitleStyle();
this.notify('title');
}
set description(description) {
if (this._description.text === description)
return;
_setLabel(this._description, description);
this.notify('description');
}
});
export const ListSection = GObject.registerClass({
Properties: {
'title': GObject.ParamSpec.string(
'title', 'title', 'title',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null),
},
}, class ListSection extends St.BoxLayout {
_init(params) {
this._title = new St.Label({style_class: 'dialog-list-title'});
this._listScrollView = new St.ScrollView({
style_class: 'dialog-list-scrollview',
hscrollbar_policy: St.PolicyType.NEVER,
});
this.list = new St.BoxLayout({
style_class: 'dialog-list-box',
vertical: true,
});
this._listScrollView.add_child(this.list);
let defaultParams = {
style_class: 'dialog-list',
x_expand: true,
vertical: true,
};
super._init(Object.assign(defaultParams, params));
this.label_actor = this._title;
this.add_child(this._title);
this.add_child(this._listScrollView);
}
get title() {
return this._title.text;
}
set title(title) {
_setLabel(this._title, title);
this.notify('title');
}
});
export const ListSectionItem = GObject.registerClass({
Properties: {
'icon-actor': GObject.ParamSpec.object(
'icon-actor', 'icon-actor', 'Icon actor',
GObject.ParamFlags.READWRITE,
Clutter.Actor.$gtype),
'title': GObject.ParamSpec.string(
'title', 'title', 'title',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null),
'description': GObject.ParamSpec.string(
'description', 'description', 'description',
GObject.ParamFlags.READWRITE |
GObject.ParamFlags.CONSTRUCT,
null),
},
}, class ListSectionItem extends St.BoxLayout {
_init(params) {
this._iconActorBin = new St.Bin();
let textLayout = new St.BoxLayout({
vertical: true,
y_expand: true,
y_align: Clutter.ActorAlign.CENTER,
});
this._title = new St.Label({style_class: 'dialog-list-item-title'});
this._description = new St.Label({
style_class: 'dialog-list-item-title-description',
});
textLayout.add_child(this._title);
textLayout.add_child(this._description);
let defaultParams = {style_class: 'dialog-list-item'};
super._init(Object.assign(defaultParams, params));
this.label_actor = this._title;
this.add_child(this._iconActorBin);
this.add_child(textLayout);
}
get iconActor() {
return this._iconActorBin.get_child();
}
set iconActor(actor) {
this._iconActorBin.set_child(actor);
this.notify('icon-actor');
}
get title() {
return this._title.text;
}
set title(title) {
_setLabel(this._title, title);
this.notify('title');
}
get description() {
return this._description.text;
}
set description(description) {
_setLabel(this._description, description);
this.notify('description');
}
});