modalDialog: Add modal dialog base class

This is a base class to make it easier to
gain a consistent look for system modal dialogs.

It handles creating a darkened backdrop behind the dialog, setting
up buttons in the dialog, keynav, etc.

https://bugzilla.gnome.org/show_bug.cgi?id=637187
This commit is contained in:
Ray Strode 2010-10-20 20:46:38 -04:00
parent dc020628b5
commit 47b8d16067
3 changed files with 282 additions and 0 deletions

View File

@ -1080,6 +1080,54 @@ StTooltip StLabel {
border-radius: 8px; border-radius: 8px;
} }
/* Modal Dialogs */
.modal-dialog {
font: 12pt sans-serif;
border-radius: 24px;
background-color: rgba(0.0, 0.0, 0.0, 0.9);
border: 2px solid #868686;
color: #ffffff;
padding-right: 42px;
padding-left: 42px;
padding-bottom: 30px;
padding-top: 30px;
}
.modal-dialog-button {
border: 1px solid #8b8b8b;
border-radius: 18px;
font: 14px sans-serif;
margin-left: 10px;
margin-right: 10px;
padding-left: 32px;
padding-right: 32px;
padding-top: 8px;
padding-bottom: 8px;
background-gradient-direction: vertical;
background-gradient-start: #29323b;
background-gradient-end: #121a24;
}
.modal-dialog-button:active,
.modal-dialog-button:pressed {
border-color: #a5a5a5;
background-gradient-start: #121a24;
background-gradient-end: #29323b;
}
.modal-dialog-button:focus {
border: 2px solid #a5a5a5;
padding-left: 31px;
padding-right: 31px;
padding-top: 7px;
padding-bottom: 7px;
}
/* Run Dialog */ /* Run Dialog */
.run-dialog-label { .run-dialog-label {
font: 12px sans-serif; font: 12px sans-serif;

View File

@ -32,6 +32,7 @@ nobase_dist_js_DATA = \
ui/magnifierDBus.js \ ui/magnifierDBus.js \
ui/main.js \ ui/main.js \
ui/messageTray.js \ ui/messageTray.js \
ui/modalDialog.js \
ui/notificationDaemon.js \ ui/notificationDaemon.js \
ui/overview.js \ ui/overview.js \
ui/panel.js \ ui/panel.js \

233
js/ui/modalDialog.js Normal file
View File

@ -0,0 +1,233 @@
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
const Clutter = imports.gi.Clutter;
const Gdk = imports.gi.Gdk;
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Pango = imports.gi.Pango;
const St = imports.gi.St;
const Shell = imports.gi.Shell;
const Signals = imports.signals;
const Gettext = imports.gettext.domain('gnome-shell');
const _ = Gettext.gettext;
const Params = imports.misc.params;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const OPEN_AND_CLOSE_TIME = 0.1;
const FADE_OUT_DIALOG_TIME = 1.0;
const State = {
OPENED: 0,
CLOSED: 1,
OPENING: 2,
CLOSING: 3,
FADED_OUT: 4
};
function ModalDialog() {
this._init();
}
ModalDialog.prototype = {
_init: function(params) {
params = Params.parse(params, { styleClass: null });
this.state = State.CLOSED;
this._group = new St.Group({ visible: false,
x: 0,
y: 0 });
Main.uiGroup.add_actor(this._group);
global.focus_manager.add_group(this._group);
this._initialKeyFocus = this._group;
this._group.connect('destroy', Lang.bind(this, this._onGroupDestroy));
this._actionKeys = {};
this._group.connect('key-press-event', Lang.bind(this, this._onKeyPressEvent));
this._lightbox = new Lightbox.Lightbox(this._group,
{ inhibitEvents: true });
this._backgroundBin = new St.Bin();
this._group.add_actor(this._backgroundBin);
this._lightbox.highlight(this._backgroundBin);
this._dialogLayout = new St.BoxLayout({ style_class: 'modal-dialog',
vertical: true });
if (params.styleClass != null) {
this._dialogLayout.add_style_class_name(params.styleClass);
}
this._backgroundBin.child = this._dialogLayout;
this.contentLayout = new St.BoxLayout({ vertical: true });
this._dialogLayout.add(this.contentLayout,
{ x_fill: true,
y_fill: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.START });
this._buttonLayout = new St.BoxLayout({ opacity: 220,
vertical: false });
this._dialogLayout.add(this._buttonLayout,
{ expand: true,
x_align: St.Align.MIDDLE,
y_align: St.Align.END });
},
setButtons: function(buttons) {
this._buttonLayout.remove_all();
let i = 0;
for (let index in buttons) {
let buttonInfo = buttons[index];
let label = buttonInfo['label'];
let action = buttonInfo['action'];
let key = buttonInfo['key'];
let button = new St.Button({ style_class: 'modal-dialog-button',
reactive: true,
can_focus: true,
label: label });
let x_alignment;
if (buttons.length == 1)
x_alignment = St.Align.END;
else if (i == 0)
x_alignment = St.Align.START;
else if (i == buttons.length - 1)
x_alignment = St.Align.END;
else
x_alignment = St.Align.MIDDLE;
this._initialKeyFocus = button;
this._buttonLayout.add(button,
{ expand: true,
x_fill: false,
y_fill: false,
x_align: x_alignment,
y_align: St.Align.MIDDLE });
button.connect('clicked', action);
if (key)
this._actionKeys[key] = action;
i++;
}
},
_onKeyPressEvent: function(object, keyPressEvent) {
let symbol = keyPressEvent.get_key_symbol();
let action = this._actionKeys[symbol];
if (action)
action();
},
_onGroupDestroy: function() {
this.emit('destroy');
},
_fadeOpen: function() {
let monitor = global.get_focus_monitor();
this._backgroundBin.set_position(monitor.x, monitor.y);
this._backgroundBin.set_size(monitor.width, monitor.height);
this.state = State.OPENING;
this._dialogLayout.opacity = 255;
this._lightbox.show();
this._group.opacity = 0;
this._group.show();
Tweener.addTween(this._group,
{ opacity: 255,
time: OPEN_AND_CLOSE_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this,
function() {
this._initialKeyFocus.grab_key_focus();
this.state = State.OPENED;
this.emit('opened');
}),
});
},
open: function(timestamp) {
if (this.state == State.OPENED || this.state == State.OPENING)
return true;
if (!Main.pushModal(this._group, timestamp))
return false;
global.stage.set_key_focus(this._group);
this._fadeOpen();
return true;
},
close: function(timestamp) {
if (this.state == State.CLOSED || this.state == State.CLOSING)
return;
let needsPopModal;
if (this.state == State.OPENED || this.state == State.OPENING)
needsPopModal = true;
else
needsPopModal = false;
this.state = State.CLOSING;
Tweener.addTween(this._group,
{ opacity: 0,
time: OPEN_AND_CLOSE_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this,
function() {
this.state = State.CLOSED;
this._group.hide();
if (needsPopModal)
Main.popModal(this._group, timestamp);
})
});
},
// This method is like close, but fades the dialog out much slower,
// and leaves the lightbox in place. Once in the faded out state,
// the dialog can be brought back by an open call, or the lightbox
// can be dismissed by a close call.
//
// The main point of this method is to give some indication to the user
// that the dialog reponse has been acknowledged but will take a few
// moments before being processed.
// e.g., if a user clicked "Log Out" then the dialog should go away
// imediately, but the lightbox should remain until the logout is
// complete.
_fadeOutDialog: function(timestamp) {
if (this.state == State.CLOSED || this.state == State.CLOSING)
return;
if (this.state == State.FADED_OUT)
return;
Tweener.addTween(this._dialogLayout,
{ opacity: 0,
time: FADE_OUT_DIALOG_TIME,
transition: 'easeOutQuad',
onComplete: Lang.bind(this,
function() {
this.state = State.FADED_OUT;
Main.popModal(this._group, timestamp);
})
});
}
};
Signals.addSignalMethods(ModalDialog.prototype);