text: /* ************************************************************************
  
     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)
  
  ************************************************************************ */
  
  /**
   * The base class of all items, which should be laid out using a layout manager
   * {@link qx.ui.layout.Abstract}.
   */
  qx.Class.define("qx.ui.core.LayoutItem",
  {
    type : "abstract",
    extend : qx.core.Object,
  
    construct : function() {
      this.base(arguments);
  
      // dynamic theme switch
      if (qx.core.Environment.get("qx.dyntheme")) {
        qx.theme.manager.Meta.getInstance().addListener("changeTheme", this._onChangeTheme, this);
      }
    },
  
  
  
    /*
    *****************************************************************************
       PROPERTIES
    *****************************************************************************
    */
  
    properties :
    {
      /*
      ---------------------------------------------------------------------------
        DIMENSION
      ---------------------------------------------------------------------------
      */
  
      /**
       * The user provided minimal width.
       *
       * Also take a look at the related properties {@link #width} and {@link #maxWidth}.
       */
      minWidth :
      {
        check : "Integer",
        nullable : true,
        apply : "_applyDimension",
        init : null,
        themeable : true
      },
  
  
      /**
       * The <code>LayoutItem</code>'s preferred width.
       *
       * The computed width may differ from the given width due to
       * stretching. Also take a look at the related properties
       * {@link #minWidth} and {@link #maxWidth}.
       */
      width :
      {
        check : "Integer",
        event : "changeWidth",
        nullable : true,
        apply : "_applyDimension",
        init : null,
        themeable : true
      },
  
  
      /**
       * The user provided maximal width.
       *
       * Also take a look at the related properties {@link #width} and {@link #minWidth}.
       */
      maxWidth :
      {
        check : "Integer",
        nullable : true,
        apply : "_applyDimension",
        init : null,
        themeable : true
      },
  
  
      /**
       * The user provided minimal height.
       *
       * Also take a look at the related properties {@link #height} and {@link #maxHeight}.
       */
      minHeight :
      {
        check : "Integer",
        nullable : true,
        apply : "_applyDimension",
        init : null,
        themeable : true
      },
  
  
      /**
       * The item's preferred height.
       *
       * The computed height may differ from the given height due to
       * stretching. Also take a look at the related properties
       * {@link #minHeight} and {@link #maxHeight}.
       */
      height :
      {
        check : "Integer",
        event : "changeHeight",
        nullable : true,
        apply : "_applyDimension",
        init : null,
        themeable : true
      },
  
  
      /**
       * The user provided maximum height.
       *
       * Also take a look at the related properties {@link #height} and {@link #minHeight}.
       */
      maxHeight :
      {
        check : "Integer",
        nullable : true,
        apply : "_applyDimension",
        init : null,
        themeable : true
      },
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        STRETCHING
      ---------------------------------------------------------------------------
      */
  
      /** Whether the item can grow horizontally. */
      allowGrowX :
      {
        check : "Boolean",
        apply : "_applyStretching",
        init : true,
        themeable : true
      },
  
  
      /** Whether the item can shrink horizontally. */
      allowShrinkX :
      {
        check : "Boolean",
        apply : "_applyStretching",
        init : true,
        themeable : true
      },
  
  
      /** Whether the item can grow vertically. */
      allowGrowY :
      {
        check : "Boolean",
        apply : "_applyStretching",
        init : true,
        themeable : true
      },
  
  
      /** Whether the item can shrink vertically. */
      allowShrinkY :
      {
        check : "Boolean",
        apply : "_applyStretching",
        init : true,
        themeable : true
      },
  
  
      /** Growing and shrinking in the horizontal direction */
      allowStretchX :
      {
        group : [ "allowGrowX", "allowShrinkX" ],
        mode : "shorthand",
        themeable: true
      },
  
  
      /** Growing and shrinking in the vertical direction */
      allowStretchY :
      {
        group : [ "allowGrowY", "allowShrinkY" ],
        mode : "shorthand",
        themeable: true
      },
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        MARGIN
      ---------------------------------------------------------------------------
      */
  
      /** Margin of the widget (top) */
      marginTop :
      {
        check : "Integer",
        init : 0,
        apply : "_applyMargin",
        themeable : true
      },
  
  
      /** Margin of the widget (right) */
      marginRight :
      {
        check : "Integer",
        init : 0,
        apply : "_applyMargin",
        themeable : true
      },
  
  
      /** Margin of the widget (bottom) */
      marginBottom :
      {
        check : "Integer",
        init : 0,
        apply : "_applyMargin",
        themeable : true
      },
  
  
      /** Margin of the widget (left) */
      marginLeft :
      {
        check : "Integer",
        init : 0,
        apply : "_applyMargin",
        themeable : true
      },
  
  
      /**
       * The 'margin' property is a shorthand property for setting 'marginTop',
       * 'marginRight', 'marginBottom' and 'marginLeft' 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.
       */
      margin :
      {
        group : [ "marginTop", "marginRight", "marginBottom", "marginLeft" ],
        mode  : "shorthand",
        themeable : true
      },
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        ALIGN
      ---------------------------------------------------------------------------
      */
  
      /**
       * Horizontal alignment of the item in the parent layout.
       *
       * Note: Item alignment is only supported by {@link LayoutItem} layouts where
       * it would have a visual effect. Except for {@link Spacer}, which provides
       * blank space for layouts, all classes that inherit {@link LayoutItem} support alignment.
       */
      alignX :
      {
        check : [ "left", "center", "right" ],
        nullable : true,
        apply : "_applyAlign",
        themeable: true
      },
  
  
      /**
       * Vertical alignment of the item in the parent layout.
       *
       * Note: Item alignment is only supported by {@link LayoutItem} layouts where
       * it would have a visual effect. Except for {@link Spacer}, which provides
       * blank space for layouts, all classes that inherit {@link LayoutItem} support alignment.
       */
      alignY :
      {
        check : [ "top", "middle", "bottom", "baseline" ],
        nullable : true,
        apply : "_applyAlign",
        themeable: true
      }
    },
  
  
  
  
    /*
    *****************************************************************************
       MEMBERS
    *****************************************************************************
    */
  
    members :
    {
      /*
      ---------------------------------------------------------------------------
        DYNAMIC THEME SWITCH SUPPORT
      ---------------------------------------------------------------------------
      */
  
      /**
       * Handler for the dynamic theme change.
       * @signature function()
       */
      _onChangeTheme : qx.core.Environment.select("qx.dyntheme",
      {
        "true" : function() {
          // reset all themeable properties
          var props = qx.util.PropertyUtil.getAllProperties(this.constructor);
          for (var name in props) {
            var desc = props[name];
            // only themeable properties not having a user value
            if (desc.themeable) {
              var userValue = qx.util.PropertyUtil.getUserValue(this, name);
              if (userValue == null) {
                qx.util.PropertyUtil.resetThemed(this, name);
              }
            }
          }
        },
        "false" : null
      }),
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        LAYOUT PROCESS
      ---------------------------------------------------------------------------
      */
  
      /** @type {Integer} The computed height */
      __computedHeightForWidth : null,
  
      /** @type {Map} The computed size of the layout item */
      __computedLayout : null,
  
      /** @type {Boolean} Whether the current layout is valid */
      __hasInvalidLayout : null,
  
      /** @type {Map} Cached size hint */
      __sizeHint : null,
  
      /** @type {Boolean} Whether the margins have changed and must be updated */
      __updateMargin : null,
  
      /** @type {Map} user provided bounds of the widget, which override the layout manager */
      __userBounds : null,
  
      /** @type {Map} The item's layout properties */
      __layoutProperties : null,
  
  
      /**
       * Get the computed location and dimension as computed by
       * the layout manager.
       *
       * @return {Map|null} The location and dimensions in pixel
       *    (if the layout is valid). Contains the keys
       *    <code>width</code>, <code>height</code>, <code>left</code> and
       *    <code>top</code>.
       */
      getBounds : function() {
        return this.__userBounds || this.__computedLayout || null;
      },
  
  
      /**
       * Reconfigure number of separators
       */
      clearSeparators : function() {
        // empty template
      },
  
  
      /**
       * Renders a separator between two children
       *
       * @param separator {String|qx.ui.decoration.IDecorator} The separator to render
       * @param bounds {Map} Contains the left and top coordinate and the width and height
       *    of the separator to render.
       */
      renderSeparator : function(separator, bounds) {
        // empty template
      },
  
  
      /**
       * Used by the layout engine to apply coordinates and dimensions.
       *
       * @param left {Integer} Any integer value for the left position,
       *   always in pixels
       * @param top {Integer} Any integer value for the top position,
       *   always in pixels
       * @param width {Integer} Any positive integer value for the width,
       *   always in pixels
       * @param height {Integer} Any positive integer value for the height,
       *   always in pixels
       * @return {Map} A map of which layout sizes changed.
       */
      renderLayout : function(left, top, width, height)
      {
        // do not render if the layout item is already disposed
        if (this.isDisposed()) {
          return null;
        }
  
        if (qx.core.Environment.get("qx.debug"))
        {
          var msg = "Something went wrong with the layout of " + this.toString() + "!";
          this.assertInteger(left, "Wrong 'left' argument. " + msg);
          this.assertInteger(top, "Wrong 'top' argument. " + msg);
          this.assertInteger(width, "Wrong 'width' argument. " + msg);
          this.assertInteger(height, "Wrong 'height' argument. " + msg);
  
          // this.assertInRange(width, this.getMinWidth() || -1, this.getMaxWidth() || 32000);
          // this.assertInRange(height, this.getMinHeight() || -1, this.getMaxHeight() || 32000);
        }
  
  
  
        // Height for width support
        // Results into a relayout which means that width/height is applied in the next iteration.
        var flowHeight = null;
        if (this.getHeight() == null && this._hasHeightForWidth()) {
          var flowHeight = this._getHeightForWidth(width);
        }
  
        if (flowHeight != null && flowHeight !== this.__computedHeightForWidth)
        {
          // This variable is used in the next computation of the size hint
          this.__computedHeightForWidth = flowHeight;
  
          // Re-add to layout queue
          qx.ui.core.queue.Layout.add(this);
  
          return null;
        }
  
        // Detect size changes
  
        // Dynamically create data structure for computed layout
        var computed = this.__computedLayout;
        if (!computed) {
          computed = this.__computedLayout = {};
        }
  
        // Detect changes
        var changes = {};
  
        if (left !== computed.left || top !== computed.top)
        {
          changes.position = true;
  
          computed.left = left;
          computed.top = top;
        }
  
        if (width !== computed.width || height !== computed.height)
        {
          changes.size = true;
  
          computed.width = width;
          computed.height = height;
        }
  
        // Clear invalidation marker
        if (this.__hasInvalidLayout)
        {
          changes.local = true;
          delete this.__hasInvalidLayout;
        }
  
        if (this.__updateMargin)
        {
          changes.margin = true;
          delete this.__updateMargin;
        }
  
        // Returns changes, especially for deriving classes
        return changes;
      },
  
  
      /**
       * Whether the item should be excluded from the layout
       *
       * @return {Boolean} Should the item be excluded by the layout
       */
      isExcluded : function() {
        return false;
      },
  
  
      /**
       * Whether the layout of this item (to layout the children)
       * is valid.
       *
       * @return {Boolean} Returns <code>true</code>
       */
      hasValidLayout : function() {
        return !this.__hasInvalidLayout;
      },
  
  
      /**
       * Indicate that the item has layout changes and propagate this information
       * up the item hierarchy.
       *
       */
      scheduleLayoutUpdate : function() {
        qx.ui.core.queue.Layout.add(this);
      },
  
  
      /**
       * Called by the layout manager to mark this item's layout as invalid.
       * This function should clear all layout relevant caches.
       */
      invalidateLayoutCache : function()
      {
        // this.debug("Mark layout invalid!");
  
        this.__hasInvalidLayout = true;
        this.__sizeHint = null;
      },
  
  
      /**
       * A size hint computes the dimensions of a widget. It returns
       * the recommended dimensions as well as the min and max dimensions.
       * The min and max values already respect the stretching properties.
       *
       * <h3>Wording</h3>
       * <ul>
       * <li>User value: Value defined by the widget user, using the size properties</li>
       *
       * <li>Layout value: The value computed by {@link qx.ui.core.Widget#_getContentHint}</li>
       * </ul>
       *
       * <h3>Algorithm</h3>
       * <ul>
       * <li>minSize: If the user min size is not null, the user value is taken,
       *     otherwise the layout value is used.</li>
       *
       * <li>(preferred) size: If the user value is not null the user value is used,
       *     otherwise the layout value is used.</li>
       *
       * <li>max size: Same as the preferred size.</li>
       * </ul>
       *
       * @param compute {Boolean?true} Automatically compute size hint if currently not
       *   cached?
       * @return {Map} The map with the preferred width/height and the allowed
       *   minimum and maximum values in cases where shrinking or growing
       *   is required.
       */
      getSizeHint : function(compute)
      {
        var hint = this.__sizeHint;
        if (hint) {
          return hint;
        }
  
        if (compute === false) {
          return null;
        }
  
        // Compute as defined
        hint = this.__sizeHint = this._computeSizeHint();
  
        // Respect height for width
        if (this._hasHeightForWidth() && this.__computedHeightForWidth && this.getHeight() == null) {
          hint.height = this.__computedHeightForWidth;
        }
  
  
        // normalize width
        if (hint.minWidth > hint.width) {
          hint.width = hint.minWidth;
        }
        if (hint.maxWidth < hint.width) {
          hint.width = hint.maxWidth;
        }
  
        if (!this.getAllowGrowX()) {
          hint.maxWidth = hint.width;
        }
        if (!this.getAllowShrinkX()) {
          hint.minWidth = hint.width;
        }
  
  
        // normalize height
        if (hint.minHeight > hint.height) {
          hint.height = hint.minHeight;
        }
        if (hint.maxHeight < hint.height) {
          hint.height = hint.maxHeight;
        }
  
        if (!this.getAllowGrowY()) {
          hint.maxHeight = hint.height;
        }
        if (!this.getAllowShrinkY()) {
          hint.minHeight = hint.height;
        }
  
  
        // Finally return
        return hint;
      },
  
  
      /**
       * Computes the size hint of the layout item.
       *
       * @return {Map} The map with the preferred width/height and the allowed
       *   minimum and maximum values.
       */
      _computeSizeHint : function()
      {
        var minWidth = this.getMinWidth() || 0;
        var minHeight = this.getMinHeight() || 0;
  
        var width = this.getWidth() || minWidth;
        var height = this.getHeight() || minHeight;
  
        var maxWidth = this.getMaxWidth() || Infinity;
        var maxHeight = this.getMaxHeight() || Infinity;
  
        return {
          minWidth : minWidth,
          width : width,
          maxWidth : maxWidth,
          minHeight : minHeight,
          height : height,
          maxHeight : maxHeight
        };
      },
  
  
      /**
       * Whether the item supports height for width.
       *
       * @return {Boolean} Whether the item supports height for width
       */
      _hasHeightForWidth : function()
      {
        var layout = this._getLayout();
        if (layout) {
          return layout.hasHeightForWidth();
        }
        return false;
      },
  
  
      /**
       * If an item wants to trade height for width it has to implement this
       * method and return the preferred height of the item if it is resized to
       * the given width. This function returns <code>null</code> if the item
       * do not support height for width.
       *
       * @param width {Integer} The computed width
       * @return {Integer} The desired height
       */
      _getHeightForWidth : function(width)
      {
        var layout = this._getLayout();
        if (layout && layout.hasHeightForWidth()) {
          return layout.getHeightForWidth(width);
        }
  
        return null;
      },
  
  
      /**
       * Get the widget's layout manager.
       *
       * @return {qx.ui.layout.Abstract} The widget's layout manager
       */
      _getLayout : function() {
        return null;
      },
  
  
      // property apply
      _applyMargin : function()
      {
        this.__updateMargin = true;
  
        var parent = this.$$parent;
        if (parent) {
          parent.updateLayoutProperties();
        }
      },
  
  
      // property apply
      _applyAlign : function()
      {
        var parent = this.$$parent;
        if (parent) {
          parent.updateLayoutProperties();
        }
      },
  
  
      // property apply
      _applyDimension : function() {
        qx.ui.core.queue.Layout.add(this);
      },
  
  
      // property apply
      _applyStretching : function() {
        qx.ui.core.queue.Layout.add(this);
      },
  
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        SUPPORT FOR USER BOUNDARIES
      ---------------------------------------------------------------------------
      */
  
      /**
       * Whether user bounds are set on this layout item
       *
       * @return {Boolean} Whether user bounds are set on this layout item
       */
      hasUserBounds : function() {
        return !!this.__userBounds;
      },
  
  
      /**
       * Set user bounds of the widget. Widgets with user bounds are sized and
       * positioned manually and are ignored by any layout manager.
       *
       * @param left {Integer} left position (relative to the parent)
       * @param top {Integer} top position (relative to the parent)
       * @param width {Integer} width of the layout item
       * @param height {Integer} height of the layout item
       */
      setUserBounds : function(left, top, width, height)
      {
        if (!this.__userBounds) {
         var parent = this.$$parent;
         if (parent) {
            parent.updateLayoutProperties();
          }
        }
        
        this.__userBounds = {
          left: left,
          top: top,
          width: width,
          height: height
        };
  
        qx.ui.core.queue.Layout.add(this);
      },
  
  
      /**
       * Clear the user bounds. After this call the layout item is laid out by
       * the layout manager again.
       *
       */
      resetUserBounds : function()
      {
        if (this.__userBounds) {
          delete this.__userBounds;
  
          var parent = this.$$parent;
          if (parent) {
              parent.updateLayoutProperties();
          }
  
          qx.ui.core.queue.Layout.add(this);
        }
      },
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        LAYOUT PROPERTIES
      ---------------------------------------------------------------------------
      */
  
      /**
       * @type {Map} Empty storage pool
       *
       * @lint ignoreReferenceField(__emptyProperties)
       */
      __emptyProperties : {},
  
  
      /**
       * Stores the given layout properties
       *
       * @param props {Map} Incoming layout property data
       */
      setLayoutProperties : function(props)
      {
        if (props == null) {
          return;
        }
  
        var storage = this.__layoutProperties;
        if (!storage) {
          storage = this.__layoutProperties = {};
        }
  
        // Check values through parent
        var parent = this.getLayoutParent();
        if (parent) {
          parent.updateLayoutProperties(props);
        }
  
        // Copy over values
        for (var key in props)
        {
          if (props[key] == null) {
            delete storage[key];
          } else {
            storage[key] = props[key];
          }
        }
      },
  
  
      /**
       * Returns currently stored layout properties
       *
       * @return {Map} Returns a map of layout properties
       */
      getLayoutProperties : function() {
        return this.__layoutProperties || this.__emptyProperties;
      },
  
  
      /**
       * Removes all stored layout properties.
       *
       */
      clearLayoutProperties : function() {
        delete this.__layoutProperties;
      },
  
  
      /**
       * Should be executed on every change of layout properties.
       *
       * This also includes "virtual" layout properties like margin or align
       * when they have an effect on the parent and not on the widget itself.
       *
       * This method is always executed on the parent not on the
       * modified widget itself.
       *
       * @param props {Map?null} Optional map of known layout properties
       */
      updateLayoutProperties : function(props)
      {
        var layout = this._getLayout();
        if (layout)
        {
          // Verify values through underlying layout
          if (qx.core.Environment.get("qx.debug"))
          {
            if (props)
            {
              for (var key in props) {
                if (props[key] !== null) {
                  layout.verifyLayoutProperty(this, key, props[key]);
                }
              }
            }
          }
  
          // Precomputed and cached children data need to be
          // rebuild on upcoming (re-)layout.
          layout.invalidateChildrenCache();
        }
  
        qx.ui.core.queue.Layout.add(this);
      },
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        HIERARCHY SUPPORT
      ---------------------------------------------------------------------------
      */
  
      /**
       * Returns the application root
       *
       * @return {qx.ui.root.Abstract} The currently used root
       */
      getApplicationRoot : function() {
        return qx.core.Init.getApplication().getRoot();
      },
  
  
      /**
       * Get the items parent. Even if the item has been added to a
       * layout, the parent is always a child of the containing item. The parent
       * item may be <code>null</code>.
       *
       * @return {qx.ui.core.Widget|null} The parent.
       */
      getLayoutParent : function() {
        return this.$$parent || null;
      },
  
  
      /**
       * Set the parent
       *
       * @param parent {qx.ui.core.Widget|null} The new parent.
       */
      setLayoutParent : function(parent)
      {
        if (this.$$parent === parent) {
          return;
        }
  
        this.$$parent = parent || null;
        qx.ui.core.queue.Visibility.add(this);
      },
  
  
      /**
       * Whether the item is a root item and directly connected to
       * the DOM.
       *
       * @return {Boolean} Whether the item a root item
       */
      isRootWidget : function() {
        return false;
      },
  
  
      /**
       * Returns the root item. The root item is the item which
       * is directly inserted into an existing DOM node at HTML level.
       * This is often the BODY element of a typical web page.
       *
       * @return {qx.ui.core.Widget} The root item (if available)
       */
      _getRoot : function()
      {
        var parent = this;
  
        while (parent)
        {
          if (parent.isRootWidget()) {
            return parent;
          }
  
          parent = parent.$$parent;
        }
  
        return null;
      },
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        CLONE SUPPORT
      ---------------------------------------------------------------------------
      */
  
      // overridden
      clone : function()
      {
        var clone = this.base(arguments);
  
        var props = this.__layoutProperties;
        if (props) {
          clone.__layoutProperties = qx.lang.Object.clone(props);
        }
  
        return clone;
      }
    },
  
  
  
  
    /*
    *****************************************************************************
       DESTRUCTOR
    *****************************************************************************
    */
  
    destruct : function()
    {
      // remove dynamic theme listener
      if (qx.core.Environment.get("qx.dyntheme")) {
        qx.theme.manager.Meta.getInstance().removeListener(
          "changeTheme", this._onChangeTheme, this
        );
      }
      this.$$parent = this.$$subparent = this.__layoutProperties =
        this.__computedLayout = this.__userBounds = this.__sizeHint = null;
    }
  });
  