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)
  
  ************************************************************************ */
  
  /**
   * This class represents a scroll able pane. This means that this widget
   * may contain content which is bigger than the available (inner)
   * dimensions of this widget. The widget also offer methods to control
   * the scrolling position. It can only have exactly one child.
   */
  qx.Class.define("qx.ui.core.scroll.ScrollPane",
  {
    extend : qx.ui.core.Widget,
  
  
    /*
    *****************************************************************************
       CONSTRUCTOR
    *****************************************************************************
    */
  
    construct : function()
    {
      this.base(arguments);
  
      this.set({
        minWidth: 0,
        minHeight: 0
      });
  
      // Automatically configure a "fixed" grow layout.
      this._setLayout(new qx.ui.layout.Grow());
  
      // Add resize listener to "translate" event
      this.addListener("resize", this._onUpdate);
  
      var contentEl = this.getContentElement();
  
      // Synchronizes the DOM scroll position with the properties
      contentEl.addListener("scroll", this._onScroll, this);
  
      // Fixed some browser quirks e.g. correcting scroll position
      // to the previous value on re-display of a pane
      contentEl.addListener("appear", this._onAppear, this);
    },
  
  
  
  
    /*
    *****************************************************************************
       EVENTS
    *****************************************************************************
    */
  
    events :
    {
      /** Fired on resize of both the container or the content. */
      update : "qx.event.type.Event",
  
      /** Fired on scroll animation end invoked by 'scroll*' methods. */
      scrollAnimationEnd : "qx.event.type.Event"
    },
  
  
  
  
    /*
    *****************************************************************************
       PROPERTIES
    *****************************************************************************
    */
  
    properties :
    {
      /** The horizontal scroll position */
      scrollX :
      {
        check : "qx.lang.Type.isNumber(value)&&value>=0&&value<=this.getScrollMaxX()",
        apply : "_applyScrollX",
        transform: "_transformScrollX",
        event : "scrollX",
        init  : 0
      },
  
      /** The vertical scroll position */
      scrollY :
      {
        check : "qx.lang.Type.isNumber(value)&&value>=0&&value<=this.getScrollMaxY()",
        apply : "_applyScrollY",
        transform: "_transformScrollY",
        event : "scrollY",
        init  : 0
      }
    },
  
  
  
  
  
    /*
    *****************************************************************************
       MEMBERS
    *****************************************************************************
    */
  
    members :
    {
      __frame : null,
  
  
      /*
      ---------------------------------------------------------------------------
        CONTENT MANAGEMENT
      ---------------------------------------------------------------------------
      */
  
      /**
       * Configures the content of the scroll pane. Replaces any existing child
       * with the newly given one.
       *
       * @param widget {qx.ui.core.Widget?null} The content widget of the pane
       */
      add : function(widget)
      {
        var old = this._getChildren()[0];
        if (old)
        {
          this._remove(old);
          old.removeListener("resize", this._onUpdate, this);
        }
  
        if (widget)
        {
          this._add(widget);
          widget.addListener("resize", this._onUpdate, this);
        }
      },
  
  
      /**
       * Removes the given widget from the content. The pane is empty
       * afterwards as only one child is supported by the pane.
       *
       * @param widget {qx.ui.core.Widget?null} The content widget of the pane
       */
      remove : function(widget)
      {
        if (widget)
        {
          this._remove(widget);
          widget.removeListener("resize", this._onUpdate, this);
        }
      },
  
  
      /**
       * Returns an array containing the current content.
       *
       * @return {Object[]} The content array
       */
      getChildren : function() {
        return this._getChildren();
      },
  
  
  
      /*
      ---------------------------------------------------------------------------
        EVENT LISTENER
      ---------------------------------------------------------------------------
      */
  
      /**
       * Event listener for resize event of content and container
       *
       * @param e {Event} Resize event object
       */
      _onUpdate : function(e) {
        this.fireEvent("update");
      },
  
  
      /**
       * Event listener for scroll event of content
       *
       * @param e {qx.event.type.Event} Scroll event object
       */
      _onScroll : function(e)
      {
        var contentEl = this.getContentElement();
  
        this.setScrollX(contentEl.getScrollX());
        this.setScrollY(contentEl.getScrollY());
      },
  
  
      /**
       * Event listener for appear event of content
       *
       * @param e {qx.event.type.Event} Appear event object
       */
      _onAppear : function(e)
      {
        var contentEl = this.getContentElement();
  
        var internalX = this.getScrollX();
        var domX = contentEl.getScrollX();
  
        if (internalX != domX) {
          contentEl.scrollToX(internalX);
        }
  
        var internalY = this.getScrollY();
        var domY = contentEl.getScrollY();
  
        if (internalY != domY) {
          contentEl.scrollToY(internalY);
        }
      },
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        ITEM LOCATION SUPPORT
      ---------------------------------------------------------------------------
      */
  
      /**
       * Returns the top offset of the given item in relation to the
       * inner height of this widget.
       *
       * @param item {qx.ui.core.Widget} Item to query
       * @return {Integer} Top offset
       */
      getItemTop : function(item)
      {
        var top = 0;
  
        do
        {
          top += item.getBounds().top;
          item = item.getLayoutParent();
        }
        while (item && item !== this);
  
        return top;
      },
  
  
      /**
       * Returns the top offset of the end of the given item in relation to the
       * inner height of this widget.
       *
       * @param item {qx.ui.core.Widget} Item to query
       * @return {Integer} Top offset
       */
      getItemBottom : function(item) {
        return this.getItemTop(item) + item.getBounds().height;
      },
  
  
      /**
       * Returns the left offset of the given item in relation to the
       * inner width of this widget.
       *
       * @param item {qx.ui.core.Widget} Item to query
       * @return {Integer} Top offset
       */
      getItemLeft : function(item)
      {
        var left = 0;
        var parent;
  
        do
        {
          left += item.getBounds().left;
          parent = item.getLayoutParent();
          if (parent) {
            left += parent.getInsets().left;
          }
          item = parent;
        }
        while (item && item !== this);
  
        return left;
      },
  
  
      /**
       * Returns the left offset of the end of the given item in relation to the
       * inner width of this widget.
       *
       * @param item {qx.ui.core.Widget} Item to query
       * @return {Integer} Right offset
       */
      getItemRight : function(item) {
        return this.getItemLeft(item) + item.getBounds().width;
      },
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        DIMENSIONS
      ---------------------------------------------------------------------------
      */
  
      /**
       * The size (identical with the preferred size) of the content.
       *
       * @return {Map} Size of the content (keys: <code>width</code> and <code>height</code>)
       */
      getScrollSize : function() {
        return this.getChildren()[0].getBounds();
      },
  
  
  
  
  
  
      /*
      ---------------------------------------------------------------------------
        SCROLL SUPPORT
      ---------------------------------------------------------------------------
      */
  
      /**
       * The maximum horizontal scroll position.
       *
       * @return {Integer} Maximum horizontal scroll position.
       */
      getScrollMaxX : function()
      {
        var paneSize = this.getInnerSize();
        var scrollSize = this.getScrollSize();
  
        if (paneSize && scrollSize) {
          return Math.max(0, scrollSize.width - paneSize.width);
        }
  
        return 0;
      },
  
  
      /**
       * The maximum vertical scroll position.
       *
       * @return {Integer} Maximum vertical scroll position.
       */
      getScrollMaxY : function()
      {
        var paneSize = this.getInnerSize();
        var scrollSize = this.getScrollSize();
  
        if (paneSize && scrollSize) {
          return Math.max(0, scrollSize.height - paneSize.height);
        }
  
        return 0;
      },
  
  
      /**
       * Scrolls the element's content to the given left coordinate
       *
       * @param value {Integer} The vertical position to scroll to.
       * @param duration {Number?} The time in milliseconds the scroll to should take.
       */
      scrollToX : function(value, duration)
      {
        var max = this.getScrollMaxX();
  
        if (value < 0) {
          value = 0;
        } else if (value > max) {
          value = max;
        }
  
        this.stopScrollAnimation();
  
        if (duration) {
          var from = this.getScrollX();
          this.__frame = new qx.bom.AnimationFrame();
          this.__frame.on("end", function() {
            this.setScrollX(value);
            this.__frame = null;
            this.fireEvent("scrollAnimationEnd");
          }, this);
          this.__frame.on("frame", function(timePassed) {
            var newX = parseInt(timePassed/duration * (value - from) + from);
            this.setScrollX(newX);
          }, this);
          this.__frame.startSequence(duration);
  
        } else {
          this.setScrollX(value);
        }
      },
  
  
      /**
       * Scrolls the element's content to the given top coordinate
       *
       * @param value {Integer} The horizontal position to scroll to.
       * @param duration {Number?} The time in milliseconds the scroll to should take.
       */
      scrollToY : function(value, duration)
      {
        var max = this.getScrollMaxY();
  
        if (value < 0) {
          value = 0;
        } else if (value > max) {
          value = max;
        }
  
        this.stopScrollAnimation();
  
        if (duration) {
          var from = this.getScrollY();
          this.__frame = new qx.bom.AnimationFrame();
          this.__frame.on("end", function() {
            this.setScrollY(value);
            this.__frame = null;
            this.fireEvent("scrollAnimationEnd");
          }, this);
          this.__frame.on("frame", function(timePassed) {
            var newY = parseInt(timePassed/duration * (value - from) + from);
            this.setScrollY(newY);
          }, this);
          this.__frame.startSequence(duration);
  
        } else {
          this.setScrollY(value);
        }
      },
  
  
      /**
       * Scrolls the element's content horizontally by the given amount.
       *
       * @param x {Integer?0} Amount to scroll
       * @param duration {Number?} The time in milliseconds the scroll to should take.
       */
      scrollByX : function(x, duration) {
        this.scrollToX(this.getScrollX() + x, duration);
      },
  
  
      /**
       * Scrolls the element's content vertically by the given amount.
       *
       * @param y {Integer?0} Amount to scroll
       * @param duration {Number?} The time in milliseconds the scroll to should take.
       */
      scrollByY : function(y, duration) {
        this.scrollToY(this.getScrollY() + y, duration);
      },
  
  
      /**
       * If an scroll animation is running, it will be stopped with that method.
       */
      stopScrollAnimation : function() {
        if (this.__frame) {
          this.__frame.cancelSequence();
          this.__frame = null;
        }
      },
  
      /*
      ---------------------------------------------------------------------------
        PROPERTY APPLY ROUTINES
      ---------------------------------------------------------------------------
      */
  
      // property apply
      _applyScrollX : function(value) {
        this.getContentElement().scrollToX(value);
      },
  
      /**
       * Transform property
       *
       * @param value {Number} Value to transform
       * @return {Number} Rounded value
       */
      _transformScrollX: function(value) {
        return Math.round(value);
      },
      
  
      // property apply
      _applyScrollY : function(value) {
        this.getContentElement().scrollToY(value);
      },
  
      /**
       * Transform property
       *
       * @param value {Number} Value to transform
       * @return {Number} Rounded value
       */
      _transformScrollY: function(value) {
        return Math.round(value);
      }
    }
  });
  