80a869e768
Since quite a few strings of dialogs provided by external programs are not updated yet and the string freeze is already in effect, make sure we don't break those dialogs by stripping aways large parts of the headline. To do that, detect if the title label is larger than the available width and if it is, switch to a smaller font-size of 13pt. This makes sure we still show about the same number of characters in the headline as we did in previous releases. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/1012
357 lines
10 KiB
JavaScript
357 lines
10 KiB
JavaScript
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
|
|
/* exported Dialog, MessageDialogContent, ListSection, ListSectionItem */
|
|
|
|
const { Clutter, GObject, Meta, Pango, St } = imports.gi;
|
|
|
|
function _setLabel(label, value) {
|
|
label.set({
|
|
text: value || '',
|
|
visible: value !== null,
|
|
});
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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: {
|
|
'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) {
|
|
Meta.later_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;
|
|
|
|
this._updateTitleStyleLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
|
|
this._updateTitleStyleLater = 0;
|
|
this._title.add_style_class_name('leightweight');
|
|
return false;
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
set title(title) {
|
|
_setLabel(this._title, title);
|
|
|
|
this._title.remove_style_class_name('leightweight');
|
|
this._updateTitleStyle();
|
|
|
|
this.notify('title');
|
|
}
|
|
|
|
set description(description) {
|
|
_setLabel(this._description, description);
|
|
this.notify('description');
|
|
}
|
|
});
|
|
|
|
var 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_actor(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');
|
|
}
|
|
});
|
|
|
|
var 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);
|
|
}
|
|
|
|
// eslint-disable-next-line camelcase
|
|
get icon_actor() {
|
|
return this._iconActorBin.get_child();
|
|
}
|
|
|
|
// eslint-disable-next-line camelcase
|
|
set icon_actor(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');
|
|
}
|
|
});
|