@qooxdoo/framework
Version:
The JS Framework for Coders
1,877 lines (1,522 loc) • 105 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://manual.qooxdoo.org/${qxversion}/pages/widget.html' 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 : function()
{
this.base(arguments);
// 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 : function(element, considerAnonymousState)
{
while(element)
{
if (qx.core.Environment.get("qx.debug")) {
qx.core.Assert.assertTrue((!element.$$widget && !element.$$widgetObject) ||
(element.$$widgetObject && element.$$widget && element.$$widgetObject.toHashCode() === element.$$widget));
}
var widget = element.$$widgetObject;
// 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 : function(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 : function() {
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 : function(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 : function(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 : function(left, top, width, height)
{
var changes = this.base(arguments, 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 : function()
{
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 : function(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 : function()
{
// 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 : function()
{
this.base(arguments);
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 : function()
{
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 : function(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 layout = this._getLayout();
if (layout && layout.hasHeightForWidth()) {
var 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 : function(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 : function()
{
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 : function()
{
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 : function(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 : function(duration) {
return this.getContentElement().fadeIn(duration);
},
/*
---------------------------------------------------------------------------
VISIBILITY SUPPORT: USER API
---------------------------------------------------------------------------
*/
// property apply
_applyAnonymous : function(value) {
if (value) {
this.getContentElement().setAttribute("qxanonymous", "true");
} else {
this.getContentElement().removeAttribute("qxanonymous");
}
},
/**
* Make this widget visible.
*
*/
show : function() {
this.setVisibility("visible");
},
/**
* Hide this widget.
*
*/
hide : function() {
this.setVisibility("hidden");
},
/**
* Hide this widget and exclude it from the underlying layout.
*
*/
exclude : function() {
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 : function() {
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 : function() {
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 : function() {
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 : function()
{
// 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 : function()
{
var el = this._createContentElement();
el.connectWidget(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 : function()
{
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 : function() {
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 : function()
{
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 : function() {
qx.ui.core.queue.Layout.add(this);
},
/**
* Resets the cache for children which should be laid out.
*/
invalidateLayoutChildren : function()
{
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 : function()
{
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 : function() {
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 : function() {
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 : function(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 : function()
{
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 : function(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.
* @pa