@qooxdoo/framework
Version:
The JS Framework for Coders
1,112 lines (941 loc) • 30.6 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(caption, icon) {
super();
// 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 properties
this.initVisibility();
this.initActive();
this.initModal();
// 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");
// ARIA attrs
this.getContentElement().setAttribute("role", "dialog");
this.addAriaLabelledBy(this.getChildControl("title"));
this.addAriaDescribedBy(this.getChildControl("statusbar-text"));
},
/*
*****************************************************************************
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
*****************************************************************************
*/
/* eslint-disable @qooxdoo/qx/no-refs-in-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() {
return this.getChildControl("pane");
},
// overridden
/**
* @lint ignoreReferenceField(_forwardStates)
*/
_forwardStates: {
active: true,
maximized: true,
showStatusbar: true,
modal: true
},
// overridden
setLayoutParent(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
super.setLayoutParent(parent);
// Re-add a listener for resize, if required
if (parent && this.getCenterOnContainerResize()) {
this.__centeringResizeId = parent.addListener(
"resize",
this.center,
this
);
}
},
// overridden
_createChildControlImpl(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 || super._createChildControlImpl(id);
},
/*
---------------------------------------------------------------------------
CAPTIONBAR INTERNALS
---------------------------------------------------------------------------
*/
/**
* Updates the status and the visibility of each element of the captionbar.
*/
_updateCaptionBar() {
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() {
if (!this.getAutoDestroy() && !this.isVisible()) {
return;
}
if (! this.fireNonBubblingEvent(
"beforeClose",
qx.event.type.Event,
[ false, true ])) {
// preventDefault() was called
return;
}
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() {
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() {
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() {
// 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() {
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() {
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(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() {
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() {
if (!this.isVisible()) {
return "minimized";
} else {
if (this.isMaximized()) {
return "maximized";
} else {
return "normal";
}
}
},
/*
---------------------------------------------------------------------------
PROPERTY APPLY ROUTINES
---------------------------------------------------------------------------
*/
// property apply
_applyActive(value, old) {
if (value) {
this.addState("active");
} else {
this.removeState("active");
}
},
// property apply
_applyModal(value, old) {
if (value) {
this.addState("modal");
} else {
this.removeState("modal");
}
// ARIA attrs
this.getContentElement().setAttribute("aria-modal", value);
},
/**
* Returns the element, to which the content padding should be applied.
*
* @return {qx.ui.core.Widget} The content padding target.
*/
_getContentPaddingTarget() {
return this.getChildControl("pane");
},
// property apply
_applyShowStatusbar(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(value, old) {
this._updateCaptionBar();
},
// property apply
_applyStatus(value, old) {
var label = this.getChildControl("statusbar-text", true);
if (label) {
label.setValue(value);
}
},
// overridden
_applyFocusable(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") {
super._applyFocusable(value, old);
}
},
_applyCenterOnAppear(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(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(e) {
e.stopPropagation();
},
/**
* Focuses the window instance.
*
* @param e {qx.event.type.Pointer} pointer down event
*/
_onWindowPointerDown(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(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(e) {
if (
this.getAllowMaximize() &&
(e.getTarget() === this.getChildControl("captionbar") ||
e.getTarget() === this.getChildControl("title"))
) {
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(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(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(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(e) {
this.close();
this.getChildControl("close-button").reset();
}
},
destruct() {
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);
}
}
});