@qooxdoo/framework
Version:
The JS Framework for Coders
1,201 lines (960 loc) • 30.4 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Sebastian Werner (wpbasti)
* Andreas Ecker (ecker)
* Fabian Jakobs (fjakobs)
* Christian Hagendorn (chris_schmidt)
************************************************************************ */
/**
* A window widget
*
* More information can be found in the package description {@link qx.ui.window}.
*
* @childControl statusbar {qx.ui.container.Composite} statusbar container which shows the statusbar text
* @childControl statusbar-text {qx.ui.basic.Label} text of the statusbar
* @childControl pane {qx.ui.container.Composite} window pane which holds the content
* @childControl captionbar {qx.ui.container.Composite} Container for all widgets inside the captionbar
* @childControl icon {qx.ui.basic.Image} icon at the left of the captionbar
* @childControl title {qx.ui.basic.Label} caption of the window
* @childControl minimize-button {qx.ui.form.Button} button to minimize the window
* @childControl restore-button {qx.ui.form.Button} button to restore the window
* @childControl maximize-button {qx.ui.form.Button} button to maximize the window
* @childControl close-button {qx.ui.form.Button} button to close the window
*/
qx.Class.define("qx.ui.window.Window",
{
extend : qx.ui.core.Widget,
include :
[
qx.ui.core.MRemoteChildrenHandling,
qx.ui.core.MRemoteLayoutHandling,
qx.ui.core.MResizable,
qx.ui.core.MMovable,
qx.ui.core.MContentPadding
],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @param caption {String?} The caption text
* @param icon {String?} The URL of the caption bar icon
*/
construct : function(caption, icon)
{
this.base(arguments);
// configure internal layout
this._setLayout(new qx.ui.layout.VBox());
// force creation of captionbar
this._createChildControl("captionbar");
this._createChildControl("pane");
// apply constructor parameters
if (icon != null) {
this.setIcon(icon);
}
if (caption != null) {
this.setCaption(caption);
}
// Update captionbar
this._updateCaptionBar();
// Activation listener
this.addListener("pointerdown", this._onWindowPointerDown, this, true);
// Focusout listener
this.addListener("focusout", this._onWindowFocusOut, this);
// Automatically add to application root.
qx.core.Init.getApplication().getRoot().add(this);
// Initialize visibility
this.initVisibility();
// Register as root for the focus handler
qx.ui.core.FocusHandler.getInstance().addRoot(this);
// Change the resize frames appearance
this._getResizeFrame().setAppearance("window-resize-frame");
},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics :
{
/** @type {Class} The default window manager class. */
DEFAULT_MANAGER_CLASS : qx.ui.window.Manager
},
/*
*****************************************************************************
EVENTS
*****************************************************************************
*/
events :
{
/**
* Fired before the window is closed.
*
* The close action can be prevented by calling
* {@link qx.event.type.Event#preventDefault} on the event object
*/
"beforeClose" : "qx.event.type.Event",
/** Fired if the window is closed */
"close" : "qx.event.type.Event",
/**
* Fired before the window is minimize.
*
* The minimize action can be prevented by calling
* {@link qx.event.type.Event#preventDefault} on the event object
*/
"beforeMinimize" : "qx.event.type.Event",
/** Fired if the window is minimized */
"minimize" : "qx.event.type.Event",
/**
* Fired before the window is maximize.
*
* The maximize action can be prevented by calling
* {@link qx.event.type.Event#preventDefault} on the event object
*/
"beforeMaximize" : "qx.event.type.Event",
/** Fired if the window is maximized */
"maximize" : "qx.event.type.Event",
/**
* Fired before the window is restored from a minimized or maximized state.
*
* The restored action can be prevented by calling
* {@link qx.event.type.Event#preventDefault} on the event object
*/
"beforeRestore" : "qx.event.type.Event",
/** Fired if the window is restored from a minimized or maximized state */
"restore" : "qx.event.type.Event"
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties :
{
/*
---------------------------------------------------------------------------
INTERNAL OPTIONS
---------------------------------------------------------------------------
*/
// overridden
appearance :
{
refine : true,
init : "window"
},
// overridden
visibility :
{
refine : true,
init : "excluded"
},
// overridden
focusable :
{
refine : true,
init : true
},
/**
* If the window is active, only one window in a single qx.ui.window.Manager could
* have set this to true at the same time.
*/
active :
{
check : "Boolean",
init : false,
apply : "_applyActive",
event : "changeActive"
},
/*
---------------------------------------------------------------------------
BASIC OPTIONS
---------------------------------------------------------------------------
*/
/** Should the window be always on top */
alwaysOnTop :
{
check : "Boolean",
init : false,
event : "changeAlwaysOnTop"
},
/** Should the window be modal (this disables minimize and maximize buttons) */
modal :
{
check : "Boolean",
init : false,
event : "changeModal",
apply : "_applyModal"
},
/** The text of the caption */
caption :
{
apply : "_applyCaptionBarChange",
event : "changeCaption",
nullable : true
},
/** The icon of the caption */
icon :
{
check : "String",
nullable : true,
apply : "_applyCaptionBarChange",
event : "changeIcon",
themeable : true
},
/** The text of the statusbar */
status :
{
check : "String",
nullable : true,
apply : "_applyStatus",
event :"changeStatus"
},
/*
---------------------------------------------------------------------------
HIDE CAPTIONBAR FEATURES
---------------------------------------------------------------------------
*/
/** Should the close button be shown */
showClose :
{
check : "Boolean",
init : true,
apply : "_applyCaptionBarChange",
themeable : true
},
/** Should the maximize button be shown */
showMaximize :
{
check : "Boolean",
init : true,
apply : "_applyCaptionBarChange",
themeable : true
},
/** Should the minimize button be shown */
showMinimize :
{
check : "Boolean",
init : true,
apply : "_applyCaptionBarChange",
themeable : true
},
/*
---------------------------------------------------------------------------
DISABLE CAPTIONBAR FEATURES
---------------------------------------------------------------------------
*/
/** Should the user have the ability to close the window */
allowClose :
{
check : "Boolean",
init : true,
apply : "_applyCaptionBarChange"
},
/** Should the user have the ability to maximize the window */
allowMaximize :
{
check : "Boolean",
init : true,
apply : "_applyCaptionBarChange"
},
/** Should the user have the ability to minimize the window */
allowMinimize :
{
check : "Boolean",
init : true,
apply : "_applyCaptionBarChange"
},
/*
---------------------------------------------------------------------------
STATUSBAR CONFIG
---------------------------------------------------------------------------
*/
/** Should the statusbar be shown */
showStatusbar :
{
check : "Boolean",
init : false,
apply : "_applyShowStatusbar"
},
/*
---------------------------------------------------------------------------
WHEN TO AUTOMATICALY CENTER
---------------------------------------------------------------------------
*/
/** Whether this window should be automatically centered when it appears */
centerOnAppear :
{
init : false,
check : "Boolean",
apply : "_applyCenterOnAppear"
},
/**
* Whether this window should be automatically centered when its container
* is resized.
*/
centerOnContainerResize :
{
init : false,
check : "Boolean",
apply : "_applyCenterOnContainerResize"
},
/*
---------------------------------------------------------------------------
CLOSE BEHAVIOR
---------------------------------------------------------------------------
*/
/**
* Should the window be automatically destroyed when it is closed.
*
* When false, closing the window behaves like hiding the window.
*
* When true, the window is removed from its container (the root), all
* listeners are removed, the window's widgets are removed, and the window
* is destroyed.
*
* NOTE: If any widgets that were added to this window require special
* clean-up, you should listen on the 'close' event and remove and clean
* up those widgets there.
*/
autoDestroy :
{
check : "Boolean",
init : false
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
/** @type {Integer} Original top value before maximation had occurred */
__restoredTop : null,
/** @type {Integer} Original left value before maximation had occurred */
__restoredLeft : null,
/** @type {Integer} Listener ID for centering on appear */
__centeringAppearId : null,
/** @type {Integer} Listener ID for centering on resize */
__centeringResizeId : null,
/*
---------------------------------------------------------------------------
WIDGET API
---------------------------------------------------------------------------
*/
/**
* The children container needed by the {@link qx.ui.core.MRemoteChildrenHandling}
* mixin
*
* @return {qx.ui.container.Composite} pane sub widget
*/
getChildrenContainer : function() {
return this.getChildControl("pane");
},
// overridden
/**
* @lint ignoreReferenceField(_forwardStates)
*/
_forwardStates :
{
active : true,
maximized : true,
showStatusbar : true,
modal : true
},
// overridden
setLayoutParent : function(parent)
{
var oldParent;
if (qx.core.Environment.get("qx.debug"))
{
parent && this.assertInterface(
parent, qx.ui.window.IDesktop,
"Windows can only be added to widgets, which implement the interface "+
"qx.ui.window.IDesktop. All root widgets implement this interface."
);
}
// Before changing the parent, if there's a prior one, remove our resize
// listener
oldParent = this.getLayoutParent();
if (oldParent && this.__centeringResizeId) {
oldParent.removeListenerById(this.__centeringResizeId);
this.__centeringResizeId = null;
}
// Call the superclass
this.base(arguments, parent);
// Re-add a listener for resize, if required
if (parent && this.getCenterOnContainerResize())
{
this.__centeringResizeId =
parent.addListener("resize", this.center, this);
}
},
// overridden
_createChildControlImpl : function(id, hash)
{
var control;
switch(id)
{
case "statusbar":
control = new qx.ui.container.Composite(new qx.ui.layout.HBox());
this._add(control);
control.add(this.getChildControl("statusbar-text"));
break;
case "statusbar-text":
control = new qx.ui.basic.Label();
control.setValue(this.getStatus());
break;
case "pane":
control = new qx.ui.container.Composite();
this._add(control, {flex: 1});
break;
case "captionbar":
// captionbar
var layout = new qx.ui.layout.Grid();
layout.setRowFlex(0, 1);
layout.setColumnFlex(1, 1);
control = new qx.ui.container.Composite(layout);
this._add(control);
// captionbar events
control.addListener("dbltap", this._onCaptionPointerDblTap, this);
// register as move handle
this._activateMoveHandle(control);
break;
case "icon":
control = new qx.ui.basic.Image(this.getIcon());
this.getChildControl("captionbar").add(control, {row: 0, column:0});
break;
case "title":
control = new qx.ui.basic.Label(this.getCaption());
control.setWidth(0);
control.setAllowGrowX(true);
var captionBar = this.getChildControl("captionbar");
captionBar.add(control, {row: 0, column:1});
break;
case "minimize-button":
control = new qx.ui.form.Button();
control.setFocusable(false);
control.addListener("execute", this._onMinimizeButtonTap, this);
this.getChildControl("captionbar").add(control, {row: 0, column:2});
break;
case "restore-button":
control = new qx.ui.form.Button();
control.setFocusable(false);
control.addListener("execute", this._onRestoreButtonTap, this);
this.getChildControl("captionbar").add(control, {row: 0, column:3});
break;
case "maximize-button":
control = new qx.ui.form.Button();
control.setFocusable(false);
control.addListener("execute", this._onMaximizeButtonTap, this);
this.getChildControl("captionbar").add(control, {row: 0, column:4});
break;
case "close-button":
control = new qx.ui.form.Button();
control.setFocusable(false);
control.addListener("execute", this._onCloseButtonTap, this);
this.getChildControl("captionbar").add(control, {row: 0, column:6});
break;
}
return control || this.base(arguments, id);
},
/*
---------------------------------------------------------------------------
CAPTIONBAR INTERNALS
---------------------------------------------------------------------------
*/
/**
* Updates the status and the visibility of each element of the captionbar.
*/
_updateCaptionBar : function()
{
var btn;
var icon = this.getIcon();
if (icon) {
this.getChildControl("icon").setSource(icon);
this._showChildControl("icon");
} else {
this._excludeChildControl("icon");
}
var caption = this.getCaption();
if (caption) {
this.getChildControl("title").setValue(caption);
this._showChildControl("title");
} else {
this._excludeChildControl("title");
}
if (this.getShowMinimize())
{
this._showChildControl("minimize-button");
btn = this.getChildControl("minimize-button");
this.getAllowMinimize() ? btn.resetEnabled() : btn.setEnabled(false);
}
else
{
this._excludeChildControl("minimize-button");
}
if (this.getShowMaximize())
{
if (this.isMaximized())
{
this._showChildControl("restore-button");
this._excludeChildControl("maximize-button");
}
else
{
this._showChildControl("maximize-button");
this._excludeChildControl("restore-button");
}
btn = this.getChildControl("maximize-button");
this.getAllowMaximize() ? btn.resetEnabled() : btn.setEnabled(false);
}
else
{
this._excludeChildControl("maximize-button");
this._excludeChildControl("restore-button");
}
if (this.getShowClose())
{
this._showChildControl("close-button");
btn = this.getChildControl("close-button");
this.getAllowClose() ? btn.resetEnabled() : btn.setEnabled(false);
}
else
{
this._excludeChildControl("close-button");
}
},
/*
---------------------------------------------------------------------------
USER API
---------------------------------------------------------------------------
*/
/**
* Close the current window instance.
*
* Simply calls the {@link qx.ui.core.Widget#hide} method if the
* {@link qx.ui.win.Window#autoDestroy} property is false; otherwise
* removes and destroys the window.
*/
close : function()
{
if (!this.getAutoDestroy() && !this.isVisible()) {
return;
}
if (this.fireNonBubblingEvent("beforeClose", qx.event.type.Event, [false, true]))
{
this.hide();
this.fireEvent("close");
}
// If automatically destroying the window upon close was requested, do
// so now. (Note that we explicitly re-obtain the autoDestroy property
// value, allowing the user's close handler to enable/disable it before
// here.)
if (this.getAutoDestroy())
{
this.dispose();
}
},
/**
* Open the window.
*/
open : function()
{
this.show();
this.setActive(true);
this.focus();
},
/**
* Centers the window to the parent.
*
* This call works with the size of the parent widget and the size of
* the window as calculated in the last layout flush. It is best to call
* this method just after rendering the window in the "resize" event:
* <pre class='javascript'>
* win.addListenerOnce("resize", this.center, this);
* </pre>
*/
center : function()
{
var parent = this.getLayoutParent();
if (parent)
{
var bounds = parent.getBounds();
if (bounds)
{
var hint = this.getSizeHint();
var left = Math.round((bounds.width - hint.width) / 2);
var top = Math.round((bounds.height - hint.height) / 2);
if (top < 0) {
top = 0;
}
this.moveTo(left, top);
return;
}
}
if (qx.core.Environment.get("qx.debug"))
{
this.warn("Centering depends on parent bounds!");
}
},
/**
* Maximize the window.
*/
maximize : function()
{
// If the window is already maximized -> return
if (this.isMaximized()) {
return;
}
// First check if the parent uses a canvas layout
// Otherwise maximize() is not possible
var parent = this.getLayoutParent();
if (parent != null && parent.supportsMaximize())
{
if (this.fireNonBubblingEvent("beforeMaximize", qx.event.type.Event, [false, true]))
{
if (!this.isVisible()) {
this.open();
}
// store current dimension and location
var props = this.getLayoutProperties();
this.__restoredLeft = props.left === undefined ? 0 : props.left;
this.__restoredTop = props.top === undefined ? 0 : props.top;
// Update layout properties
this.setLayoutProperties({
left: null,
top: null,
edge: 0
});
// Add state
this.addState("maximized");
// Update captionbar
this._updateCaptionBar();
// Fire user event
this.fireEvent("maximize");
}
}
},
/**
* Minimized the window.
*/
minimize : function()
{
if (!this.isVisible()) {
return;
}
if (this.fireNonBubblingEvent("beforeMinimize", qx.event.type.Event, [false, true]))
{
// store current dimension and location
var props = this.getLayoutProperties();
this.__restoredLeft = props.left === undefined ? 0 : props.left;
this.__restoredTop = props.top === undefined ? 0 : props.top;
this.removeState("maximized");
this.hide();
this.fireEvent("minimize");
}
},
/**
* Restore the window to <code>"normal"</code>, if it is
* <code>"maximized"</code> or <code>"minimized"</code>.
*/
restore : function()
{
if (this.getMode() === "normal") {
return;
}
if (this.fireNonBubblingEvent("beforeRestore", qx.event.type.Event, [false, true]))
{
if (!this.isVisible()) {
this.open();
}
// Restore old properties
var left = this.__restoredLeft;
var top = this.__restoredTop;
this.setLayoutProperties({
edge : null,
left : left,
top : top
});
// Remove maximized state
this.removeState("maximized");
// Update captionbar
this._updateCaptionBar();
// Fire user event
this.fireEvent("restore");
}
},
/**
* Set the window's position relative to its parent
*
* @param left {Integer} The left position
* @param top {Integer} The top position
*/
moveTo : function(left, top)
{
if (this.isMaximized()) {
return;
}
this.setLayoutProperties({
left : left,
top : top
});
},
/**
* Return <code>true</code> if the window is in maximized state,
* but note that the window in maximized state could also be invisible, this
* is equivalent to minimized. So use the {@link qx.ui.window.Window#getMode}
* to get the window mode.
*
* @return {Boolean} <code>true</code> if the window is maximized,
* <code>false</code> otherwise.
*/
isMaximized : function()
{
return this.hasState("maximized");
},
/**
* Return the window mode as <code>String</code>:
* <code>"maximized"</code>, <code>"normal"</code> or <code>"minimized"</code>.
*
* @return {String} The window mode as <code>String</code> value.
*/
getMode : function()
{
if(!this.isVisible()) {
return "minimized";
} else {
if(this.isMaximized()) {
return "maximized";
} else {
return "normal";
}
}
},
/*
---------------------------------------------------------------------------
PROPERTY APPLY ROUTINES
---------------------------------------------------------------------------
*/
// property apply
_applyActive : function(value, old)
{
if (old) {
this.removeState("active");
} else {
this.addState("active");
}
},
// property apply
_applyModal : function(value, old)
{
if (old) {
this.removeState("modal");
} else {
this.addState("modal");
}
},
/**
* Returns the element, to which the content padding should be applied.
*
* @return {qx.ui.core.Widget} The content padding target.
*/
_getContentPaddingTarget : function() {
return this.getChildControl("pane");
},
// property apply
_applyShowStatusbar : function(value, old)
{
// store the state if the status bar is shown
var resizeFrame = this._getResizeFrame();
if (value) {
this.addState("showStatusbar");
resizeFrame.addState("showStatusbar");
} else {
this.removeState("showStatusbar");
resizeFrame.removeState("showStatusbar");
}
if (value) {
this._showChildControl("statusbar");
} else {
this._excludeChildControl("statusbar");
}
},
// property apply
_applyCaptionBarChange : function(value, old) {
this._updateCaptionBar();
},
// property apply
_applyStatus : function(value, old)
{
var label = this.getChildControl("statusbar-text", true);
if (label) {
label.setValue(value);
}
},
// overridden
_applyFocusable : function(value, old)
{
// Workaround for bug #7581: Don't set the tabIndex
// to prevent native scrolling on focus in IE
if (qx.core.Environment.get("engine.name") !== "mshtml") {
this.base(arguments, value, old);
}
},
_applyCenterOnAppear : function(value, old)
{
// Remove prior listener for centering on appear
if (this.__centeringAppearId !== null) {
this.removeListenerById(this.__centeringAppearId);
this.__centeringAppearId = null;
}
// If we are to center on appear, arrange to do so
if (value) {
this.__centeringAppearId =
this.addListener("appear", this.center, this);
}
},
_applyCenterOnContainerResize : function(value, old)
{
var parent = this.getLayoutParent();
// Remove prior listener for centering on resize
if (this.__centeringResizeId !== null) {
parent.removeListenerById(this.__centeringResizeId);
this.__centeringResizeId = null;
}
// If we are to center on resize, arrange to do so
if (value) {
if (parent) {
this.__centeringResizeId =
parent.addListener("resize", this.center, this);
}
}
},
/*
---------------------------------------------------------------------------
BASIC EVENT HANDLERS
---------------------------------------------------------------------------
*/
/**
* Stops every event
*
* @param e {qx.event.type.Event} any event
*/
_onWindowEventStop : function(e) {
e.stopPropagation();
},
/**
* Focuses the window instance.
*
* @param e {qx.event.type.Pointer} pointer down event
*/
_onWindowPointerDown : function(e) {
this.setActive(true);
},
/**
* Listens to the "focusout" event to deactivate the window (if the
* currently focused widget is not a child of the window)
*
* @param e {qx.event.type.Focus} focus event
*/
_onWindowFocusOut : function(e) {
// only needed for non-modal windows
if (this.getModal())
{
return;
}
// get the current focused widget and check if it is a child
var current = e.getRelatedTarget();
if (current != null && !qx.ui.core.Widget.contains(this, current))
{
this.setActive(false);
}
},
/**
* Maximizes the window or restores it if it is already
* maximized.
*
* @param e {qx.event.type.Pointer} double tap event
*/
_onCaptionPointerDblTap : function(e)
{
if (this.getAllowMaximize()) {
this.isMaximized() ? this.restore() : this.maximize();
}
},
/*
---------------------------------------------------------------------------
EVENTS FOR CAPTIONBAR BUTTONS
---------------------------------------------------------------------------
*/
/**
* Minimizes the window, removes all states from the minimize button and
* stops the further propagation of the event (calling {@link qx.event.type.Event#stopPropagation}).
*
* @param e {qx.event.type.Pointer} pointer tap event
*/
_onMinimizeButtonTap : function(e)
{
this.minimize();
this.getChildControl("minimize-button").reset();
},
/**
* Restores the window, removes all states from the restore button and
* stops the further propagation of the event (calling {@link qx.event.type.Event#stopPropagation}).
*
* @param e {qx.event.type.Pointer} pointer pointer event
*/
_onRestoreButtonTap : function(e)
{
this.restore();
this.getChildControl("restore-button").reset();
},
/**
* Maximizes the window, removes all states from the maximize button and
* stops the further propagation of the event (calling {@link qx.event.type.Event#stopPropagation}).
*
* @param e {qx.event.type.Pointer} pointer pointer event
*/
_onMaximizeButtonTap : function(e)
{
this.maximize();
this.getChildControl("maximize-button").reset();
},
/**
* Closes the window, removes all states from the close button and
* stops the further propagation of the event (calling {@link qx.event.type.Event#stopPropagation}).
*
* @param e {qx.event.type.Pointer} pointer pointer event
*/
_onCloseButtonTap : function(e)
{
this.close();
this.getChildControl("close-button").reset();
}
},
destruct : function()
{
var id;
var parent;
// Remove ourselves from the focus handler
qx.ui.core.FocusHandler.getInstance().removeRoot(this);
// If we haven't been removed from our parent, clean it up too.
parent = this.getLayoutParent();
if (parent) {
// Remove the listener for resize, if there is one
id = this.__centeringResizeId;
id && parent.removeListenerById(id);
// Remove ourself from our parent
parent.remove(this);
}
}
});