UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,877 lines (1,522 loc) 105 kB
/* ************************************************************************ 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