// -*- 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" }); this._dialog.add(this.contentLayout, { expand: true, x_fill: true, y_fill: true, x_align: St.Align.MIDDLE, y_align: St.Align.START }); this.buttonLayout = new St.Widget ({ layout_manager: new Clutter.BoxLayout({ homogeneous: true }) }); this._dialog.add(this.buttonLayout, { x_align: St.Align.MIDDLE, y_align: St.Align.START }); } makeInactive() { if (this._eventId != 0) this._parentActor.disconnect(this._eventId); this._eventId = 0; this.buttonLayout.get_children().forEach(c => c.set_reactive(false)); } _onDestroy() { this.makeInactive(); } _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: 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); } });