@qooxdoo/framework
Version:
The JS Framework for Coders
1,730 lines (1,485 loc) • 106 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)
* Fabian Jakobs (fjakobs)
************************************************************************ */
/* ************************************************************************
************************************************************************ */
/**
* This is the base class for all widgets.
*
* *External Documentation*
*
* <a href='http://qooxdoo.org/docs/#core/' target='_blank'>
* Documentation of this widget in the qooxdoo manual.</a>
*
* NOTE: Instances of this class must be disposed of after use
*
* @use(qx.ui.core.EventHandler)
* @use(qx.event.handler.DragDrop)
* @asset(qx/static/blank.gif)
*
* @ignore(qx.ui.root.Inline)
*/
qx.Class.define("qx.ui.core.Widget", {
extend: qx.ui.core.LayoutItem,
include: [qx.locale.MTranslation],
implement: [qx.core.IDisposable],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
construct() {
super();
// Create basic element
this.__contentElement = this.__createContentElement();
// Initialize properties
this.initFocusable();
this.initSelectable();
this.initNativeContextMenu();
},
/*
*****************************************************************************
EVENTS
*****************************************************************************
*/
events: {
/**
* Fired after the widget appears on the screen.
*/
appear: "qx.event.type.Event",
/**
* Fired after the widget disappears from the screen.
*/
disappear: "qx.event.type.Event",
/**
* Fired after the creation of a child control. The passed data is the
* newly created child widget.
*/
createChildControl: "qx.event.type.Data",
/**
* Fired on resize (after layout) of the widget.
* The data property of the event contains the widget's computed location
* and dimension as returned by {@link qx.ui.core.LayoutItem#getBounds}
*/
resize: "qx.event.type.Data",
/**
* Fired on move (after layout) of the widget.
* The data property of the event contains the widget's computed location
* and dimension as returned by {@link qx.ui.core.LayoutItem#getBounds}
*/
move: "qx.event.type.Data",
/**
* Fired after the appearance has been applied. This happens before the
* widget becomes visible, on state and appearance changes. The data field
* contains the state map. This can be used to react on state changes or to
* read properties set by the appearance.
*/
syncAppearance: "qx.event.type.Data",
/** Fired if the mouse cursor moves over the widget.
* The data property of the event contains the widget's computed location
* and dimension as returned by {@link qx.ui.core.LayoutItem#getBounds}
*/
mousemove: "qx.event.type.Mouse",
/**
* Fired if the mouse cursor enters the widget.
*
* Note: This event is also dispatched if the widget is disabled!
*/
mouseover: "qx.event.type.Mouse",
/**
* Fired if the mouse cursor leaves widget.
*
* Note: This event is also dispatched if the widget is disabled!
*/
mouseout: "qx.event.type.Mouse",
/** Mouse button is pressed on the widget. */
mousedown: "qx.event.type.Mouse",
/** Mouse button is released on the widget. */
mouseup: "qx.event.type.Mouse",
/** Widget is clicked using left or middle button.
{@link qx.event.type.Mouse#getButton} for more details.*/
click: "qx.event.type.Mouse",
/** Widget is clicked using a non primary button.
{@link qx.event.type.Mouse#getButton} for more details.*/
auxclick: "qx.event.type.Mouse",
/** Widget is double clicked using left or middle button.
{@link qx.event.type.Mouse#getButton} for more details.*/
dblclick: "qx.event.type.Mouse",
/** Widget is clicked using the right mouse button. */
contextmenu: "qx.event.type.Mouse",
/** Fired before the context menu is opened. */
beforeContextmenuOpen: "qx.event.type.Data",
/** Fired if the mouse wheel is used over the widget. */
mousewheel: "qx.event.type.MouseWheel",
/** Fired if a touch at the screen is started. */
touchstart: "qx.event.type.Touch",
/** Fired if a touch at the screen has ended. */
touchend: "qx.event.type.Touch",
/** Fired during a touch at the screen. */
touchmove: "qx.event.type.Touch",
/** Fired if a touch at the screen is canceled. */
touchcancel: "qx.event.type.Touch",
/** Fired when a pointer taps on the screen. */
tap: "qx.event.type.Tap",
/** Fired when a pointer holds on the screen. */
longtap: "qx.event.type.Tap",
/** Fired when a pointer taps twice on the screen. */
dbltap: "qx.event.type.Tap",
/** Fired when a pointer swipes over the screen. */
swipe: "qx.event.type.Touch",
/** Fired when two pointers performing a rotate gesture on the screen. */
rotate: "qx.event.type.Rotate",
/** Fired when two pointers performing a pinch in/out gesture on the screen. */
pinch: "qx.event.type.Pinch",
/** Fired when an active pointer moves on the screen (after pointerdown till pointerup). */
track: "qx.event.type.Track",
/** Fired when an active pointer moves on the screen or the mouse wheel is used. */
roll: "qx.event.type.Roll",
/** Fired if a pointer (mouse/touch/pen) moves or changes any of it's values. */
pointermove: "qx.event.type.Pointer",
/** Fired if a pointer (mouse/touch/pen) hovers the widget. */
pointerover: "qx.event.type.Pointer",
/** Fired if a pointer (mouse/touch/pen) leaves this widget. */
pointerout: "qx.event.type.Pointer",
/**
* Fired if a pointer (mouse/touch/pen) button is pressed or
* a finger touches the widget.
*/
pointerdown: "qx.event.type.Pointer",
/**
* Fired if all pointer (mouse/touch/pen) buttons are released or
* the finger is lifted from the widget.
*/
pointerup: "qx.event.type.Pointer",
/** Fired if a pointer (mouse/touch/pen) action is canceled. */
pointercancel: "qx.event.type.Pointer",
/** This event if fired if a keyboard key is released. */
keyup: "qx.event.type.KeySequence",
/**
* This event if fired if a keyboard key is pressed down. This event is
* only fired once if the user keeps the key pressed for a while.
*/
keydown: "qx.event.type.KeySequence",
/**
* This event is fired any time a key is pressed. It will be repeated if
* the user keeps the key pressed. The pressed key can be determined using
* {@link qx.event.type.KeySequence#getKeyIdentifier}.
*/
keypress: "qx.event.type.KeySequence",
/**
* This event is fired if the pressed key or keys result in a printable
* character. Since the character is not necessarily associated with a
* single physical key press, the event does not have a key identifier
* getter. This event gets repeated if the user keeps pressing the key(s).
*
* The unicode code of the pressed key can be read using
* {@link qx.event.type.KeyInput#getCharCode}.
*/
keyinput: "qx.event.type.KeyInput",
/**
* The event is fired when the widget gets focused. Only widgets which are
* {@link #focusable} receive this event.
*/
focus: "qx.event.type.Focus",
/**
* The event is fired when the widget gets blurred. Only widgets which are
* {@link #focusable} receive this event.
*/
blur: "qx.event.type.Focus",
/**
* When the widget itself or any child of the widget receive the focus.
*/
focusin: "qx.event.type.Focus",
/**
* When the widget itself or any child of the widget lost the focus.
*/
focusout: "qx.event.type.Focus",
/**
* When the widget gets active (receives keyboard events etc.)
*/
activate: "qx.event.type.Focus",
/**
* When the widget gets inactive
*/
deactivate: "qx.event.type.Focus",
/**
* Fired if the widget becomes the capturing widget by a call to {@link #capture}.
*/
capture: "qx.event.type.Event",
/**
* Fired if the widget looses the capturing mode by a call to
* {@link #releaseCapture} or a mouse click.
*/
losecapture: "qx.event.type.Event",
/**
* Fired on the drop target when the drag&drop action is finished
* successfully. This event is normally used to transfer the data
* from the drag to the drop target.
*
* Modeled after the WHATWG specification of Drag&Drop:
* http://www.whatwg.org/specs/web-apps/current-work/#dnd
*/
drop: "qx.event.type.Drag",
/**
* Fired on a potential drop target when leaving it.
*
* Modeled after the WHATWG specification of Drag&Drop:
* http://www.whatwg.org/specs/web-apps/current-work/#dnd
*/
dragleave: "qx.event.type.Drag",
/**
* Fired on a potential drop target when reaching it via the pointer.
* This event can be canceled if none of the incoming data types
* are supported.
*
* Modeled after the WHATWG specification of Drag&Drop:
* http://www.whatwg.org/specs/web-apps/current-work/#dnd
*/
dragover: "qx.event.type.Drag",
/**
* Fired during the drag. Contains the current pointer coordinates
* using {@link qx.event.type.Drag#getDocumentLeft} and
* {@link qx.event.type.Drag#getDocumentTop}
*
* Modeled after the WHATWG specification of Drag&Drop:
* http://www.whatwg.org/specs/web-apps/current-work/#dnd
*/
drag: "qx.event.type.Drag",
/**
* Initiate the drag-and-drop operation. This event is cancelable
* when the drag operation is currently not allowed/possible.
*
* Modeled after the WHATWG specification of Drag&Drop:
* http://www.whatwg.org/specs/web-apps/current-work/#dnd
*/
dragstart: "qx.event.type.Drag",
/**
* Fired on the source (drag) target every time a drag session was ended.
*/
dragend: "qx.event.type.Drag",
/**
* Fired when the drag configuration has been modified e.g. the user
* pressed a key which changed the selected action. This event will be
* fired on the draggable and the droppable element. In case of the
* droppable element, you can cancel the event and prevent a drop based on
* e.g. the current action.
*/
dragchange: "qx.event.type.Drag",
/**
* Fired when the drop was successfully done and the target widget
* is now asking for data. The listener should transfer the data,
* respecting the selected action, to the event. This can be done using
* the event's {@link qx.event.type.Drag#addData} method.
*/
droprequest: "qx.event.type.Drag"
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties: {
/*
---------------------------------------------------------------------------
PADDING
---------------------------------------------------------------------------
*/
/** Padding of the widget (top) */
paddingTop: {
check: "Integer",
init: 0,
apply: "_applyPadding",
themeable: true
},
/** Padding of the widget (right) */
paddingRight: {
check: "Integer",
init: 0,
apply: "_applyPadding",
themeable: true
},
/** Padding of the widget (bottom) */
paddingBottom: {
check: "Integer",
init: 0,
apply: "_applyPadding",
themeable: true
},
/** Padding of the widget (left) */
paddingLeft: {
check: "Integer",
init: 0,
apply: "_applyPadding",
themeable: true
},
/**
* The 'padding' property is a shorthand property for setting 'paddingTop',
* 'paddingRight', 'paddingBottom' and 'paddingLeft' at the same time.
*
* If four values are specified they apply to top, right, bottom and left respectively.
* If there is only one value, it applies to all sides, if there are two or three,
* the missing values are taken from the opposite side.
*/
padding: {
group: ["paddingTop", "paddingRight", "paddingBottom", "paddingLeft"],
mode: "shorthand",
themeable: true
},
/*
---------------------------------------------------------------------------
STYLING PROPERTIES
---------------------------------------------------------------------------
*/
/**
* The z-index property sets the stack order of an element. An element with
* greater stack order is always in front of another element with lower stack order.
*/
zIndex: {
nullable: true,
init: 10,
apply: "_applyZIndex",
event: "changeZIndex",
check: "Integer",
themeable: true
},
/**
* The decorator property points to an object, which is responsible
* for drawing the widget's decoration, e.g. border, background or shadow.
*
* This can be a decorator object or a string pointing to a decorator
* defined in the decoration theme.
*/
decorator: {
nullable: true,
init: null,
apply: "_applyDecorator",
event: "changeDecorator",
check: "Decorator",
themeable: true
},
/**
* The background color the rendered widget.
*/
backgroundColor: {
nullable: true,
check: "Color",
apply: "_applyBackgroundColor",
event: "changeBackgroundColor",
themeable: true
},
/**
* The text color the rendered widget.
*/
textColor: {
nullable: true,
check: "Color",
apply: "_applyTextColor",
event: "changeTextColor",
themeable: true,
inheritable: true
},
/**
* The widget's font. The value is either a font name defined in the font
* theme or an instance of {@link qx.bom.Font}.
*/
font: {
nullable: true,
apply: "_applyFont",
check: "Font",
event: "changeFont",
themeable: true,
inheritable: true,
dereference: true
},
/**
* Mapping to native style property opacity.
*
* The uniform opacity setting to be applied across an entire object.
* Behaves like the new CSS-3 Property.
* Any values outside the range 0.0 (fully transparent) to 1.0
* (fully opaque) will be clamped to this range.
*/
opacity: {
check: "Number",
apply: "_applyOpacity",
themeable: true,
nullable: true,
init: null
},
/**
* Mapping to native style property cursor.
*
* The name of the cursor to show when the pointer is over the widget.
* This is any valid CSS2 cursor name defined by W3C.
*
* The following values are possible crossbrowser:
* <ul><li>default</li>
* <li>crosshair</li>
* <li>pointer</li>
* <li>move</li>
* <li>n-resize</li>
* <li>ne-resize</li>
* <li>e-resize</li>
* <li>se-resize</li>
* <li>s-resize</li>
* <li>sw-resize</li>
* <li>w-resize</li>
* <li>nw-resize</li>
* <li>nesw-resize</li>
* <li>nwse-resize</li>
* <li>text</li>
* <li>wait</li>
* <li>help </li>
* </ul>
*/
cursor: {
check: "String",
apply: "_applyCursor",
themeable: true,
inheritable: true,
nullable: true,
init: null
},
/**
* Sets the tooltip instance to use for this widget. If only the tooltip
* text and icon have to be set its better to use the {@link #toolTipText}
* and {@link #toolTipIcon} properties since they use a shared tooltip
* instance.
*
* If this property is set the {@link #toolTipText} and {@link #toolTipIcon}
* properties are ignored.
*/
toolTip: {
check: "qx.ui.tooltip.ToolTip",
nullable: true
},
/**
* The text of the widget's tooltip. This text can contain HTML markup.
* The text is displayed using a shared tooltip instance. If the tooltip
* must be customized beyond the text and an icon {@link #toolTipIcon}, the
* {@link #toolTip} property has to be used
*/
toolTipText: {
check: "String",
nullable: true,
event: "changeToolTipText",
apply: "_applyToolTipText"
},
/**
* The icon URI of the widget's tooltip. This icon is displayed using a shared
* tooltip instance. If the tooltip must be customized beyond the tooltip text
* {@link #toolTipText} and the icon, the {@link #toolTip} property has to be
* used.
*/
toolTipIcon: {
check: "String",
nullable: true,
event: "changeToolTipText"
},
/**
* Controls if a tooltip should shown or not.
*/
blockToolTip: {
check: "Boolean",
init: false
},
/**
* Forces to show tooltip when widget is disabled.
*/
showToolTipWhenDisabled: {
check: "Boolean",
init: false
},
/*
---------------------------------------------------------------------------
MANAGEMENT PROPERTIES
---------------------------------------------------------------------------
*/
/**
* Controls the visibility. Valid values are:
*
* <ul>
* <li><b>visible</b>: Render the widget</li>
* <li><b>hidden</b>: Hide the widget but don't relayout the widget's parent.</li>
* <li><b>excluded</b>: Hide the widget and relayout the parent as if the
* widget was not a child of its parent.</li>
* </ul>
*/
visibility: {
check: ["visible", "hidden", "excluded"],
init: "visible",
apply: "_applyVisibility",
event: "changeVisibility"
},
/**
* Whether the widget is enabled. Disabled widgets are usually grayed out
* and do not process user created events. While in the disabled state most
* user input events are blocked. Only the {@link #pointerover} and
* {@link #pointerout} events will be dispatched.
*/
enabled: {
init: true,
check: "Boolean",
inheritable: true,
apply: "_applyEnabled",
event: "changeEnabled"
},
/**
* Whether the widget is anonymous.
*
* Anonymous widgets are ignored in the event hierarchy. This is useful
* for combined widgets where the internal structure do not have a custom
* appearance with a different styling from the element around. This is
* especially true for widgets like checkboxes or buttons where the text
* or icon are handled synchronously for state changes to the outer widget.
*/
anonymous: {
init: false,
check: "Boolean",
apply: "_applyAnonymous"
},
/**
* Defines the tab index of an widget. If widgets with tab indexes are part
* of the current focus root these elements are sorted in first priority. Afterwards
* the sorting continues by rendered position, zIndex and other criteria.
*
* Please note: The value must be between 1 and 32000.
*/
tabIndex: {
check: "Integer",
nullable: true,
apply: "_applyTabIndex"
},
/**
* Whether the widget is focusable e.g. rendering a focus border and visualize
* as active element.
*
* See also {@link #isTabable} which allows runtime checks for
* <code>isChecked</code> or other stuff to test whether the widget is
* reachable via the TAB key.
*/
focusable: {
check: "Boolean",
init: false,
apply: "_applyFocusable"
},
/**
* If this property is enabled, the widget and all of its child widgets
* will never get focused. The focus keeps at the currently
* focused widget.
*
* This only works for widgets which are not {@link #focusable}.
*
* This is mainly useful for widget authors. Please use with caution!
*/
keepFocus: {
check: "Boolean",
init: false,
apply: "_applyKeepFocus"
},
/**
* If this property if enabled, the widget and all of its child widgets
* will never get activated. The activation keeps at the currently
* activated widget.
*
* This is mainly useful for widget authors. Please use with caution!
*/
keepActive: {
check: "Boolean",
init: false,
apply: "_applyKeepActive"
},
/** Whether the widget acts as a source for drag&drop operations */
draggable: {
check: "Boolean",
init: false,
apply: "_applyDraggable"
},
/** Whether the widget acts as a target for drag&drop operations */
droppable: {
check: "Boolean",
init: false,
apply: "_applyDroppable"
},
/**
* Whether the widget contains content which may be selected by the user.
*
* If the value set to <code>true</code> the native browser selection can
* be used for text selection. But it is normally useful for
* forms fields, longer texts/documents, editors, etc.
*/
selectable: {
check: "Boolean",
init: false,
event: "changeSelectable",
apply: "_applySelectable"
},
/**
* Whether to show a context menu and which one
*/
contextMenu: {
check: "qx.ui.menu.Menu",
apply: "_applyContextMenu",
nullable: true,
event: "changeContextMenu"
},
/**
* Whether the native context menu should be enabled for this widget. To
* globally enable the native context menu set the {@link #nativeContextMenu}
* property of the root widget ({@link qx.ui.root.Abstract}) to
* <code>true</code>.
*/
nativeContextMenu: {
check: "Boolean",
init: false,
themeable: true,
event: "changeNativeContextMenu",
apply: "_applyNativeContextMenu"
},
/**
* The appearance ID. This ID is used to identify the appearance theme
* entry to use for this widget. This controls the styling of the element.
*/
appearance: {
check: "String",
init: "widget",
apply: "_applyAppearance",
event: "changeAppearance"
}
},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics: {
/** Whether the widget should print out hints and debug messages */
DEBUG: false,
/** Whether to throw an error on focus/blur if the widget is unfocusable */
UNFOCUSABLE_WIDGET_FOCUS_BLUR_ERROR: true,
/**
* Returns the widget, which contains the given DOM element.
*
* @param element {Element} The DOM element to search the widget for.
* @param considerAnonymousState {Boolean?false} If true, anonymous widget
* will not be returned.
* @return {qx.ui.core.Widget} The widget containing the element.
*/
getWidgetByElement(element, considerAnonymousState) {
while (element) {
if (qx.core.Environment.get("qx.debug")) {
qx.core.Assert.assertTrue(
(!element.$$qxObjectHash && !element.$$qxObject) ||
(element.$$qxObject &&
element.$$qxObjectHash &&
element.$$qxObject.toHashCode() === element.$$qxObjectHash)
);
}
var widget = element.$$qxObject;
// check for anonymous widgets
if (widget) {
if (!considerAnonymousState || !widget.getAnonymous()) {
return widget;
}
}
// Fix for FF, which occasionally breaks (BUG#3525)
try {
element = element.parentNode;
} catch (e) {
return null;
}
}
return null;
},
/**
* Whether the "parent" widget contains the "child" widget.
*
* @param parent {qx.ui.core.Widget} The parent widget
* @param child {qx.ui.core.Widget} The child widget
* @return {Boolean} Whether one of the "child"'s parents is "parent"
*/
contains(parent, child) {
while (child) {
child = child.getLayoutParent();
if (parent == child) {
return true;
}
}
return false;
},
/** @type {Map} Contains all pooled separators for reuse */
__separatorPool: new qx.util.ObjectPool()
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members: {
__contentElement: null,
__initialAppearanceApplied: null,
__toolTipTextListenerId: null,
/*
---------------------------------------------------------------------------
LAYOUT INTERFACE
---------------------------------------------------------------------------
*/
/**
* @type {qx.ui.layout.Abstract} The connected layout manager
*/
__layoutManager: null,
// overridden
_getLayout() {
return this.__layoutManager;
},
/**
* Set a layout manager for the widget. A a layout manager can only be connected
* with one widget. Reset the connection with a previous widget first, if you
* like to use it in another widget instead.
*
* @param layout {qx.ui.layout.Abstract} The new layout or
* <code>null</code> to reset the layout.
*/
_setLayout(layout) {
if (qx.core.Environment.get("qx.debug")) {
if (layout) {
this.assertInstance(layout, qx.ui.layout.Abstract);
}
}
if (this.__layoutManager) {
this.__layoutManager.connectToWidget(null);
}
if (layout) {
layout.connectToWidget(this);
}
this.__layoutManager = layout;
qx.ui.core.queue.Layout.add(this);
},
// overridden
setLayoutParent(parent) {
if (this.$$parent === parent) {
return;
}
var content = this.getContentElement();
if (this.$$parent && !this.$$parent.$$disposed) {
this.$$parent.getContentElement().remove(content);
}
this.$$parent = parent || null;
if (parent && !parent.$$disposed) {
this.$$parent.getContentElement().add(content);
}
// Update inheritable properties
this.$$refreshInheritables();
// Update visibility cache
qx.ui.core.queue.Visibility.add(this);
},
/** @type {Boolean} Whether insets have changed and must be updated */
_updateInsets: null,
// overridden
renderLayout(left, top, width, height) {
var changes = super.renderLayout(left, top, width, height);
// Directly return if superclass has detected that no
// changes needs to be applied
if (!changes) {
return null;
}
if (qx.lang.Object.isEmpty(changes) && !this._updateInsets) {
return null;
}
var content = this.getContentElement();
var inner = changes.size || this._updateInsets;
var pixel = "px";
var contentStyles = {};
// Move content to new position
if (changes.position) {
contentStyles.left = left + pixel;
contentStyles.top = top + pixel;
}
if (inner || changes.margin) {
contentStyles.width = width + pixel;
contentStyles.height = height + pixel;
}
if (Object.keys(contentStyles).length > 0) {
content.setStyles(contentStyles);
}
if (inner || changes.local || changes.margin) {
if (this.__layoutManager && this.hasLayoutChildren()) {
var inset = this.getInsets();
var innerWidth = width - inset.left - inset.right;
var innerHeight = height - inset.top - inset.bottom;
var decorator = this.getDecorator();
var decoratorPadding = { left: 0, right: 0, top: 0, bottom: 0 };
if (decorator) {
decorator =
qx.theme.manager.Decoration.getInstance().resolve(decorator);
decoratorPadding = decorator.getPadding();
}
var padding = {
top: this.getPaddingTop() + decoratorPadding.top,
right: this.getPaddingRight() + decoratorPadding.right,
bottom: this.getPaddingBottom() + decoratorPadding.bottom,
left: this.getPaddingLeft() + decoratorPadding.left
};
this.__layoutManager.renderLayout(innerWidth, innerHeight, padding);
} else if (this.hasLayoutChildren()) {
throw new Error(
"At least one child in control " +
this._findTopControl() +
" requires a layout, but no one was defined!"
);
}
}
// Fire events
if (changes.position && this.hasListener("move")) {
this.fireDataEvent("move", this.getBounds());
}
if (changes.size && this.hasListener("resize")) {
this.fireDataEvent("resize", this.getBounds());
}
// Cleanup flags
delete this._updateInsets;
return changes;
},
/*
---------------------------------------------------------------------------
SEPARATOR SUPPORT
---------------------------------------------------------------------------
*/
__separators: null,
// overridden
clearSeparators() {
var reg = this.__separators;
if (!reg) {
return;
}
var pool = qx.ui.core.Widget.__separatorPool;
var content = this.getContentElement();
var widget;
for (var i = 0, l = reg.length; i < l; i++) {
widget = reg[i];
pool.poolObject(widget);
content.remove(widget.getContentElement());
}
// Clear registry
reg.length = 0;
},
// overridden
renderSeparator(separator, bounds) {
// Insert
var widget = qx.ui.core.Widget.__separatorPool.getObject(
qx.ui.core.Widget
);
widget.set({
decorator: separator
});
var elem = widget.getContentElement();
this.getContentElement().add(elem);
// Move
var domEl = elem.getDomElement();
// use the DOM element because the cache of the qx.html.Element could be
// wrong due to changes made by the decorators which work on the DOM element too
if (domEl) {
domEl.style.top = bounds.top + "px";
domEl.style.left = bounds.left + "px";
domEl.style.width = bounds.width + "px";
domEl.style.height = bounds.height + "px";
} else {
elem.setStyles({
left: bounds.left + "px",
top: bounds.top + "px",
width: bounds.width + "px",
height: bounds.height + "px"
});
}
// Remember element
if (!this.__separators) {
this.__separators = [];
}
this.__separators.push(widget);
},
/*
---------------------------------------------------------------------------
SIZE HINTS
---------------------------------------------------------------------------
*/
// overridden
_computeSizeHint() {
// Start with the user defined values
var width = this.getWidth();
var minWidth = this.getMinWidth();
var maxWidth = this.getMaxWidth();
var height = this.getHeight();
var minHeight = this.getMinHeight();
var maxHeight = this.getMaxHeight();
if (qx.core.Environment.get("qx.debug")) {
if (minWidth !== null && maxWidth !== null) {
this.assert(
minWidth <= maxWidth,
"minWidth is larger than maxWidth!"
);
}
if (minHeight !== null && maxHeight !== null) {
this.assert(
minHeight <= maxHeight,
"minHeight is larger than maxHeight!"
);
}
}
// Ask content
var contentHint = this._getContentHint();
var insets = this.getInsets();
var insetX = insets.left + insets.right;
var insetY = insets.top + insets.bottom;
if (width == null) {
width = contentHint.width + insetX;
}
if (height == null) {
height = contentHint.height + insetY;
}
if (minWidth == null) {
minWidth = insetX;
if (contentHint.minWidth != null) {
minWidth += contentHint.minWidth;
// do not apply bigger min width than max width [BUG #5008]
if (minWidth > maxWidth && maxWidth != null) {
minWidth = maxWidth;
}
}
}
if (minHeight == null) {
minHeight = insetY;
if (contentHint.minHeight != null) {
minHeight += contentHint.minHeight;
// do not apply bigger min height than max height [BUG #5008]
if (minHeight > maxHeight && maxHeight != null) {
minHeight = maxHeight;
}
}
}
if (maxWidth == null) {
if (contentHint.maxWidth == null) {
maxWidth = Infinity;
} else {
maxWidth = contentHint.maxWidth + insetX;
// do not apply bigger min width than max width [BUG #5008]
if (maxWidth < minWidth && minWidth != null) {
maxWidth = minWidth;
}
}
}
if (maxHeight == null) {
if (contentHint.maxHeight == null) {
maxHeight = Infinity;
} else {
maxHeight = contentHint.maxHeight + insetY;
// do not apply bigger min width than max width [BUG #5008]
if (maxHeight < minHeight && minHeight != null) {
maxHeight = minHeight;
}
}
}
// Build size hint and return
return {
width: width,
minWidth: minWidth,
maxWidth: maxWidth,
height: height,
minHeight: minHeight,
maxHeight: maxHeight
};
},
// overridden
invalidateLayoutCache() {
super.invalidateLayoutCache();
if (this.__layoutManager) {
this.__layoutManager.invalidateLayoutCache();
}
},
/**
* Returns the recommended/natural dimensions of the widget's content.
*
* For labels and images this may be their natural size when defined without
* any dimensions. For containers this may be the recommended size of the
* underlying layout manager.
*
* Developer note: This can be overwritten by the derived classes to allow
* a custom handling here.
*
* @return {Map}
*/
_getContentHint() {
var layout = this.__layoutManager;
if (layout) {
if (this.hasLayoutChildren()) {
var hint = layout.getSizeHint();
if (qx.core.Environment.get("qx.debug")) {
var msg =
"The layout of the widget" +
this.toString() +
" returned an invalid size hint!";
this.assertInteger(hint.width, "Wrong 'left' argument. " + msg);
this.assertInteger(hint.height, "Wrong 'top' argument. " + msg);
}
return hint;
} else {
return {
width: 0,
height: 0
};
}
} else {
return {
width: 100,
height: 50
};
}
},
// overridden
_getHeightForWidth(width) {
// Prepare insets
var insets = this.getInsets();
var insetX = insets.left + insets.right;
var insetY = insets.top + insets.bottom;
// Compute content width
var contentWidth = width - insetX;
// Compute height
var contentHeight = 0;
var layout = this._getLayout();
if (layout && layout.hasHeightForWidth()) {
contentHeight = layout.getHeightForWidth(contentWidth);
} else {
contentHeight = this._getContentHeightForWidth(contentWidth);
}
// Computed box height
var height = contentHeight + insetY;
return height;
},
/**
* Returns the computed height for the given width.
*
* @abstract
* @param width {Integer} Incoming width (as limitation)
* @return {Integer} Computed height while respecting the given width.
*/
_getContentHeightForWidth(width) {
throw new Error("Abstract method call: _getContentHeightForWidth()!");
},
/*
---------------------------------------------------------------------------
INSET CALCULATION SUPPORT
---------------------------------------------------------------------------
*/
/**
* Returns the sum of the widget's padding and border width.
*
* @return {Map} Contains the keys <code>top</code>, <code>right</code>,
* <code>bottom</code> and <code>left</code>. All values are integers.
*/
getInsets() {
var top = this.getPaddingTop();
var right = this.getPaddingRight();
var bottom = this.getPaddingBottom();
var left = this.getPaddingLeft();
if (this.getDecorator()) {
var decorator = qx.theme.manager.Decoration.getInstance().resolve(
this.getDecorator()
);
var inset = decorator.getInsets();
if (qx.core.Environment.get("qx.debug")) {
this.assertNumber(
inset.top,
"Invalid top decorator inset detected: " + inset.top
);
this.assertNumber(
inset.right,
"Invalid right decorator inset detected: " + inset.right
);
this.assertNumber(
inset.bottom,
"Invalid bottom decorator inset detected: " + inset.bottom
);
this.assertNumber(
inset.left,
"Invalid left decorator inset detected: " + inset.left
);
}
top += inset.top;
right += inset.right;
bottom += inset.bottom;
left += inset.left;
}
return {
top: top,
right: right,
bottom: bottom,
left: left
};
},
/*
---------------------------------------------------------------------------
COMPUTED LAYOUT SUPPORT
---------------------------------------------------------------------------
*/
/**
* Returns the widget's computed inner size as available
* through the layout process.
*
* This function is guaranteed to return a correct value
* during a {@link #resize} or {@link #move} event dispatch.
*
* @return {Map} The widget inner dimension in pixel (if the layout is
* valid). Contains the keys <code>width</code> and <code>height</code>.
*/
getInnerSize() {
var computed = this.getBounds();
if (!computed) {
return null;
}
// Return map data
var insets = this.getInsets();
return {
width: computed.width - insets.left - insets.right,
height: computed.height - insets.top - insets.bottom
};
},
/*
---------------------------------------------------------------------------
ANIMATION SUPPORT: USER API
---------------------------------------------------------------------------
*/
/**
* Fade out this widget.
* @param duration {Number} Time in ms.
* @return {qx.bom.element.AnimationHandle} The animation handle to react for
* the fade animation.
*/
fadeOut(duration) {
return this.getContentElement().fadeOut(duration);
},
/**
* Fade in the widget.
* @param duration {Number} Time in ms.
* @return {qx.bom.element.AnimationHandle} The animation handle to react for
* the fade animation.
*/
fadeIn(duration) {
return this.getContentElement().fadeIn(duration);
},
/*
---------------------------------------------------------------------------
VISIBILITY SUPPORT: USER API
---------------------------------------------------------------------------
*/
// property apply
_applyAnonymous(value) {
if (value) {
this.getContentElement().setAttribute("qxanonymous", "true");
} else {
this.getContentElement().removeAttribute("qxanonymous");
}
},
/**
* Make this widget visible.
*
*/
show() {
this.setVisibility("visible");
},
/**
* Hide this widget.
*
*/
hide() {
this.setVisibility("hidden");
},
/**
* Hide this widget and exclude it from the underlying layout.
*
*/
exclude() {
this.setVisibility("excluded");
},
/**
* Whether the widget is locally visible.
*
* Note: This method does not respect the hierarchy.
*
* @return {Boolean} Returns <code>true</code> when the widget is visible
*/
isVisible() {
return this.getVisibility() === "visible";
},
/**
* Whether the widget is locally hidden.
*
* Note: This method does not respect the hierarchy.
*
* @return {Boolean} Returns <code>true</code> when the widget is hidden
*/
isHidden() {
return this.getVisibility() !== "visible";
},
/**
* Whether the widget is locally excluded.
*
* Note: This method does not respect the hierarchy.
*
* @return {Boolean} Returns <code>true</code> when the widget is excluded
*/
isExcluded() {
return this.getVisibility() === "excluded";
},
/**
* Detects if the widget and all its parents are visible.
*
* WARNING: Please use this method with caution because it flushes the
* internal queues which might be an expensive operation.
*
* @return {Boolean} true, if the widget is currently on the screen
*/
isSeeable() {
// Flush the queues because to detect if the widget ins visible, the
// queues need to be flushed (see bug #5254)
qx.ui.core.queue.Manager.flush();
// if the element is already rendered, a check for the offsetWidth is enough
var element = this.getContentElement().getDomElement();
if (element) {
// will also be 0 if the parents are not visible
return element.offsetWidth > 0;
}
// if no element is available, it can not be visible
return false;
},
/*
---------------------------------------------------------------------------
CREATION OF HTML ELEMENTS
---------------------------------------------------------------------------
*/
/**
* Create the widget's content HTML element.
*
* @return {qx.html.Element} The content HTML element
*/
__createContentElement() {
var el = this._createContentElement();
el.connectObject(this);
// make sure to allow all pointer events
el.setStyles({ "touch-action": "none", "-ms-touch-action": "none" });
if (qx.core.Environment.get("qx.debug")) {
el.setAttribute("qxClass", this.classname);
}
var styles = {
zIndex: 10,
boxSizing: "border-box"
};
if (!qx.ui.root.Inline || !(this instanceof qx.ui.root.Inline)) {
styles.position = "absolute";
}
el.setStyles(styles);
return el;
},
/**
* Creates the content element. The style properties
* position and zIndex are modified from the Widget
* core.
*
* This function may be overridden to customize a class
* content.
*
* @return {qx.html.Element} The widget's content element
*/
_createContentElement() {
return new qx.html.Element("div", {
overflowX: "hidden",
overflowY: "hidden"
});
},
/**
* Returns the element wrapper of the widget's content element.
* This method exposes widget internal and must be used with caution!
*
* @return {qx.html.Element} The widget's content element
*/
getContentElement() {
return this.__contentElement;
},
/*
---------------------------------------------------------------------------
CHILDREN HANDLING
---------------------------------------------------------------------------
*/
/** @type {qx.ui.core.LayoutItem[]} List of all child widgets */
__widgetChildren: null,
/**
* Returns all children, which are layout relevant. This excludes all widgets,
* which have a {@link qx.ui.core.Widget#visibility} value of <code>exclude</code>.
*
* @internal
* @return {qx.ui.core.Widget[]} All layout relevant children.
*/
getLayoutChildren() {
var children = this.__widgetChildren;
if (!children) {
return this.__emptyChildren;
}
var layoutChildren;
for (var i = 0, l = children.length; i < l; i++) {
var child = children[i];
if (child.hasUserBounds() || child.isExcluded()) {
if (layoutChildren == null) {
layoutChildren = children.concat();
}
qx.lang.Array.remove(layoutChildren, child);
}
}
return layoutChildren || children;
},
/**
* Marks the layout of this widget as invalid and triggers a layout update.
* This is a shortcut for <code>qx.ui.core.queue.Layout.add(this);</code>.
*/
scheduleLayoutUpdate() {
qx.ui.core.queue.Layout.add(this);
},
/**
* Resets the cache for children which should be laid out.
*/
invalidateLayoutChildren() {
var layout = this.__layoutManager;
if (layout) {
layout.invalidateChildrenCache();
}
qx.ui.core.queue.Layout.add(this);
},
/**
* Returns whether the layout has children, which are layout relevant. This
* excludes all widgets, which have a {@link qx.ui.core.Widget#visibility}
* value of <code>exclude</code>.
*
* @return {Boolean} Whether the layout has layout relevant children
*/
hasLayoutChildren() {
var children = this.__widgetChildren;
if (!children) {
return false;
}
var child;
for (var i = 0, l = children.length; i < l; i++) {
child = children[i];
if (!child.hasUserBounds() && !child.isExcluded()) {
return true;
}
}
return false;
},
/**
* Returns the widget which contains the children and
* is relevant for laying them out. This is from the user point of
* view and may not be identical to the technical structure.
*
* @return {qx.ui.core.Widget} Widget which contains the children.
*/
getChildrenContainer() {
return this;
},
/**
* @type {Array} Placeholder for children list in empty widgets.
* Mainly to keep instance number low.
*
* @lint ignoreReferenceField(__emptyChildren)
*/
__emptyChildren: [],
/**
* Returns the children list
*
* @return {qx.ui.core.LayoutItem[]} The children array (Arrays are
* reference types, so please do not modify it in-place).
*/
_getChildren() {
return this.__widgetChildren || this.__emptyChildren;
},
/**
* Returns the index position of the given widget if it is
* a child widget. Otherwise it returns <code>-1</code>.
*
* @param child {qx.ui.core.Widget} the widget to query for
* @return {Integer} The index position or <code>-1</code> when
* the given widget is no child of this layout.
*/
_indexOf(child) {
var children = this.__widgetChildren;
if (!children) {
return -1;
}
return children.indexOf(child);
},
/**
* Whether the widget contains children.
*
* @return {Boolean} Returns <code>true</code> when the widget has children.
*/
_hasChildren() {
var children = this.__widgetChildren;
return children != null && !!children[0];
},
/**
* Recursively adds all children to the given queue
*
* @param queue {Array} The queue to add widgets to
*/
addChildrenToQueue(queue) {
var children = this.__widgetChildren;
if (!children) {
return;
}
var child;
for (var i = 0, l = children.length; i < l; i++) {
child = children[i];
queue.push(child);
child.addChildrenToQueue(queue);
}
},
/**
* Adds a new child widget.
*
* The supported keys of the layout options map depend on the layout manager
* used to position the widget. The options are documented in the class
* documentation of each layout manager {@link qx.ui.layout}.
*
* @param child {qx.ui.core.LayoutItem} the widget to add.
* @param options {Map?null} Optional layout data for widget.
*/
_add(child, options) {
if (qx.core.Environment.get("qx.debug")) {
this.assertInstance(
child,
qx.ui.core.LayoutItem.constructor,
"'Child' must be an instance of qx.ui.core.LayoutItem!"
);
}
// When moving in the same widget, remove widget first
if (child.getLayoutParent() == this) {
qx.lang.Array.remove(this.__widgetChildren, child);
}
if (this.__widgetChildren) {
this.__widgetChildren.push(child);
} else {
this.__widgetChildren = [child];
}
this.__addHelper(child, options);
},
/**
* Add a child widget at the specified index
*
* @param child {qx.ui.core.LayoutItem} widget to add
* @param index {Integer} Index, at which the widget will be inserted. If no
* widget exi