UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

1,730 lines (1,485 loc) 106 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://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