diff --git a/js/js-resources.gresource.xml b/js/js-resources.gresource.xml index 591875f8c..71e713d2b 100644 --- a/js/js-resources.gresource.xml +++ b/js/js-resources.gresource.xml @@ -47,6 +47,7 @@ ui/ctrlAltTab.js ui/dash.js ui/dateMenu.js + ui/dialog.js ui/dnd.js ui/edgeDragAction.js ui/endSessionDialog.js diff --git a/js/ui/dialog.js b/js/ui/dialog.js new file mode 100644 index 000000000..941ef7536 --- /dev/null +++ b/js/ui/dialog.js @@ -0,0 +1,131 @@ +// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- + +const Clutter = imports.gi.Clutter; +const St = imports.gi.St; +const Lang = imports.lang; + +const Dialog = new Lang.Class({ + Name: 'Dialog', + Extends: St.Widget, + + _init: function (parentActor, styleClass) { + this.parent({ layout_manager: new Clutter.BinLayout() }); + this.connect('destroy', Lang.bind(this, this._onDestroy)); + + 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', Lang.bind(this, this._modalEventHandler)); + this._parentActor.add_child(this); + }, + + _createDialog: function () { + 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.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 }); + }, + + _onDestroy: function () { + if (this._eventId != 0) + this._parentActor.disconnect(this._eventId); + this._eventId = 0; + }, + + _modalEventHandler: function (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; + }, + + addContent: function (actor) { + this.contentLayout.add (actor, { expand: true }); + }, + + addButton: function (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._initialKeyFocusDestroyId) + this._initialKeyFocus = button; + + for (let i in keys) + this._buttonKeys[keys[i]] = buttonInfo; + + this.buttonLayout.add_actor(button); + + return button; + }, + + clearButtons: function () { + this.buttonLayout.destroy_all_children(); + this._buttonKeys = {}; + }, +}); diff --git a/js/ui/modalDialog.js b/js/ui/modalDialog.js index ac773056d..ed1b92f9d 100644 --- a/js/ui/modalDialog.js +++ b/js/ui/modalDialog.js @@ -14,6 +14,7 @@ const Atk = imports.gi.Atk; const Params = imports.misc.params; +const Dialog = imports.ui.dialog; const Layout = imports.ui.layout; const Lightbox = imports.ui.lightbox; const Main = imports.ui.main; @@ -61,11 +62,6 @@ const ModalDialog = new Lang.Class({ this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy)); - this._pressedKey = null; - this._buttonKeys = {}; - this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent)); - this._group.connect('key-release-event', Lang.bind(this, this._onKeyReleaseEvent)); - this.backgroundStack = new St.Widget({ layout_manager: new Clutter.BinLayout() }); this._backgroundBin = new St.Bin({ child: this.backgroundStack, x_fill: true, y_fill: true }); @@ -73,17 +69,9 @@ const ModalDialog = new Lang.Class({ this._backgroundBin.add_constraint(this._monitorConstraint); this._group.add_actor(this._backgroundBin); - this.dialogLayout = 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.dialogLayout.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH; - - if (params.styleClass != null) - this.dialogLayout.add_style_class_name(params.styleClass); + this.dialogLayout = new Dialog.Dialog(this.backgroundStack, params.styleClass); + this.contentLayout = this.dialogLayout.contentLayout; + this.buttonLayout = this.dialogLayout.buttonLayout; if (!this._shellReactive) { this._lightbox = new Lightbox.Lightbox(this._group, @@ -94,22 +82,6 @@ const ModalDialog = new Lang.Class({ this._eventBlocker = new Clutter.Actor({ reactive: true }); this.backgroundStack.add_actor(this._eventBlocker); } - this.backgroundStack.add_actor(this.dialogLayout); - - - this.contentLayout = new St.BoxLayout({ vertical: true, - style_class: "modal-dialog-content-box" }); - this.dialogLayout.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.dialogLayout.add(this.buttonLayout, - { x_align: St.Align.MIDDLE, - y_align: St.Align.END }); global.focus_manager.add_group(this.dialogLayout); this._initialKeyFocus = this.dialogLayout; @@ -122,8 +94,7 @@ const ModalDialog = new Lang.Class({ }, clearButtons: function() { - this.buttonLayout.destroy_all_children(); - this._buttonKeys = {}; + this.dialogLayout.clearButtons(); }, setButtons: function(buttons) { @@ -146,72 +117,8 @@ const ModalDialog = new Lang.Class({ } }, - addButton: function(buttonInfo) { - let label = buttonInfo['label'] - let action = buttonInfo['action']; - let key = buttonInfo['key']; - 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._initialKeyFocusDestroyId) - this._initialKeyFocus = button; - - for (let i in keys) - this._buttonKeys[keys[i]] = buttonInfo; - - this.buttonLayout.add_actor(button); - - return button; - }, - - _onKeyPressEvent: function(object, event) { - this._pressedKey = event.get_key_symbol(); - return Clutter.EVENT_PROPAGATE; - }, - - _onKeyReleaseEvent: function(object, event) { - 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 = buttonInfo['button']; - let action = buttonInfo['action']; - - if (action && button.reactive) { - action(); - return Clutter.EVENT_STOP; - } - - return Clutter.EVENT_PROPAGATE; + addButton: function (buttonInfo) { + return this.dialogLayout.addButton(buttonInfo); }, _onGroupDestroy: function() {