/* ************************************************************************

   qooxdoo - the new era of web development

   http://qooxdoo.org

   Copyright:
     2004-2008 1&1 Internet AG, Germany, http://www.1und1.de

   License:
     LGPL: http://www.gnu.org/licenses/lgpl.html
     EPL: http://www.eclipse.org/org/documents/epl-v10.php
     See the LICENSE file in the project's top-level directory for details.

   Authors:
     * Fabian Jakobs (fjakobs)

************************************************************************ */

/**
 * Container widget for internal frames (iframes) with qooxdoo scroll bar and
 * size hint support.
 *
 * An iframe can display any HTML page inside the widget. Note that custom
 * scroll bars do only work if the iframe's source points to the same domain
 * as the application.
 *
 * @childControl iframe {qx.ui.embed.Iframe} embedded iframe component
 * @childControl scrollbar-x {qx.ui.core.scroll.ScrollBar} horizontal scrollbar
 * @childControl scrollbar-y {qx.ui.core.scroll.ScrollBar} vertical scrollbar
 * @childControl corner {qx.ui.core.Widget} corner widget where no scrollbar is shown
 *
 * *Example*
 *
 * Here is a little example of how to use the widget:
 *
 * <pre class='javascript'>
 * var document = this.getRoot();
 * var iframe = new qx.ui.embed.ThemedIframe("frame.html");
 * document.add(iframe);
 * </pre>
 *
 *
 * *External Documentation*
 *
 * <a href='http://manual.qooxdoo.org/1.4/pages/widget/themediframe.html' target='_blank'>
 * Documentation of this widget in the qooxdoo manual.</a>
 */
qx.Class.define("qx.ui.embed.ThemedIframe",
{
  extend : qx.ui.embed.AbstractIframe,
  include : qx.ui.core.scroll.MWheelHandling,

  construct : function(source)
  {
    this.base(arguments, source);

    // Create 'fixed' grid layout
    var grid = new qx.ui.layout.Grid();
    grid.setColumnFlex(0, 1);
    grid.setRowFlex(0, 1);
    this._setLayout(grid);

    this._showChildControl("iframe");
  },


  properties :
  {
    // overridden
    appearance :
    {
      refine : true,
      init : "scrollarea"
    },

    /**
     * The policy, when the horizontal scrollbar should be shown.
     * <ul>
     *   <li><b>auto</b>: Show scrollbar on demand</li>
     *   <li><b>on</b>: Always show the scrollbar</li>
     *   <li><b>off</b>: Never show the scrollbar</li>
     * </ul>
     */
    scrollbarX :
    {
      check : ["auto", "on", "off"],
      init : "auto",
      themeable : true,
      apply : "_updateScrollbars"
    },


    /**
     * The policy, when the horizontal scrollbar should be shown.
     * <ul>
     *   <li><b>auto</b>: Show scrollbar on demand</li>
     *   <li><b>on</b>: Always show the scrollbar</li>
     *   <li><b>off</b>: Never show the scrollbar</li>
     * </ul>
     */
    scrollbarY :
    {
      check : ["auto", "on", "off"],
      init : "auto",
      themeable : true,
      apply : "_updateScrollbars"
    },


    /**
     * Group property, to set the overflow of both scroll bars.
     */
    scrollbar : {
      group : [ "scrollbarX", "scrollbarY" ]
    }
  },


  members :
  {
    __iframeSize : null,
    __iframeObserverId : null,


    // overridden
    _getIframeElement : function() {
      return this.getChildControl("iframe").getContentElement();
    },


    // overridden
    _createChildControlImpl : function(id, hash)
    {
      var control;

      switch(id)
      {
        case "iframe":
          control = new qx.ui.embed.Iframe(this.getSource());
          control.addListener("load", this._onIframeLoad, this);
          control.addListener("resize", this._onIframeResize, this);
          this._add(control, {row: 0, column: 0});
          break;


        case "scrollbar-x":
          control = new qx.ui.core.scroll.ScrollBar("horizontal");
          control.setMinWidth(0);

          control.exclude();
          control.addListener("scroll", this._onScrollBarX, this);

          this._add(control, {row: 1, column: 0});
          break;


        case "scrollbar-y":
          control = new qx.ui.core.scroll.ScrollBar("vertical");
          control.setMinHeight(0);

          control.exclude();
          control.addListener("scroll", this._onScrollBarY, this);

          this._add(control, {row: 0, column: 1});
          break;


        case "corner":
          control = new qx.ui.core.Widget();
          control.setWidth(0);
          control.setHeight(0);
          control.exclude();

          this._add(control, {row: 1, column: 1});
          break;
      }

      return control || this.base(arguments, id);
    },


    /*
    ---------------------------------------------------------------------------
      EVENT LISTENERS
    ---------------------------------------------------------------------------
    */

    /**
     * Event handler for the iframe's load event
     */
    _onIframeLoad : function()
    {
      this._disableScollbars();

      var body = this._getIframeElement().getBody();
      if (body) {
        this._startIframeObserver();
        this._addWheelListener();
      }

      this.fireEvent("load");
    },


    /**
     * Event handler for resize event of the iframe widget.
     */
    _onIframeResize : function() {
      this._updateScrollbars();
    },


    /**
     * Hide all scrollbars and stop observing the iframe document
     */
    _disableScollbars : function()
    {
      this._excludeChildControl("scrollbar-x");
      this._excludeChildControl("scrollbar-y");
      this._excludeChildControl("corner");

      this._stopIframeObserver();
    },


    /**
     * Attach mouse wheel listener to the iframe
     */
    _addWheelListener : function()
    {
      try
      {
        var body = this._getIframeElement().getBody();
        qx.bom.Element.addListener(body, "mousewheel", this._onMouseWheel, this);
      } catch (e) {
        this._disableScollbars();
      }
    },


    /**
     * Start observing size changes of the iframe document
     */
    _startIframeObserver : function()
    {
      if (this.__iframeObserverId) {
        this._stopIframeObserver();
      }

      var idle = qx.event.Idle.getInstance();
      this.__iframeObserverId = idle.addListener("interval", this._onIframeObserverInterval, this);
    },


    /**
     * Stop observing size changes of the iframe document
     */
    _stopIframeObserver : function()
    {
      this.__iframeSize = null;

      if (!this.__iframeObserverId) {
        return;
      }

      var idle = qx.event.Idle.getInstance();
      idle.removeListenerById(this.__iframeObserverId);
    },


    /**
     * Event handler, which is called periodically to update the scroll bars
     */
    _onIframeObserverInterval : function()
    {
      var iframeSize = this._getIframeSize();
      if (!iframeSize)
      {
        this._disableScollbars();
        return;
      }

      if (
        this.__iframeSize &&
        iframeSize.width == this.__iframeSize.width &&
        iframeSize.height == this.__iframeSize.height
      ) {
        return;
      }

      this.__iframeSize = iframeSize;
      this._preventIframeScrolling();
      this._updateScrollbars();
    },


    /**
     * Try to hide native scrollbars in the iframe
     */
    _preventIframeScrolling : function()
    {
      try
      {
        var win = this._getIframeElement().getWindow();
        var doc = this._getIframeElement().getDocument();
        if (qx.bom.Document.isStandardMode(win)) {
          doc.documentElement.style.overflow = "hidden";
        } else {
          doc.body.style.overflow = "hidden";
        }
      } catch (e) {
        this._disableScollbars();
      }
    },


    /**
     * Recompute scrollbar visibility and positions based on the iframe's
     * document size
     */
    _updateScrollbars : function()
    {
      var iframeSize = this.__iframeSize;
      var paneSize = this.getChildControl("iframe").getBounds();
      var innerSize = this.getChildControl("iframe").getInnerSize();

      if (!iframeSize || !innerSize || !innerSize) {
        return;
      }

      var showX = false;
      var showY = false;

      var scrollbarX = this.getScrollbarX();
      var scrollbarY = this.getScrollbarY();

      if (scrollbarX === "auto" && scrollbarY === "auto")
      {
        // Check if the container is big enough to show
        // the full content.
        var showX = iframeSize.width > innerSize.width;
        var showY = iframeSize.height > innerSize.height;

        // Dependency check
        // We need a special intelligence here when only one
        // of the autosized axis requires a scrollbar
        // This scrollbar may then influence the need
        // for the other one as well.
        if ((showX || showY) && !(showX && showY))
        {
          if (showX) {
            showY = iframeSize.height > paneSize.height;
          } else if (showY) {
            showX = iframeSize.width > paneSize.width;
          }
        }
      }
      else
      {
        var showX = scrollbarX === "on";
        var showY = scrollbarY === "on";

        // Check auto values afterwards with already
        // corrected client dimensions
        if (iframeSize.width > (showX ? paneSize.width : innerSize.width) && scrollbarX === "auto") {
          showX = true;
        }

        if (iframeSize.height > (showX ? paneSize.height : innerSize.height) && scrollbarY === "auto") {
          showY = true;
        }
      }

      this._configureScrollbar(
        "scrollbar-x", showX,
        innerSize.width, iframeSize.width
      );
      this._configureScrollbar(
        "scrollbar-y", showY,
        innerSize.height, iframeSize.height
      );

      this._updateCornerWidget();
    },


    /**
     * Compute the size of the iframe body
     *
     * @return {Object|null} A map with the body size or <code>null</code>.
     */
    _getIframeSize : function()
    {
      try
      {
        var win = this._getIframeElement().getWindow();
        var frameSize = {
          width: qx.bom.Document.getWidth(win),
          height: qx.bom.Document.getHeight(win)
        }
        return frameSize;
      }
      catch (e)
      {
        return null;
      }
    },


    /**
     * Update visibility of the corner widget based on the visibility of the
     * scrollbars
     */
    _updateCornerWidget : function()
    {
      if (
        this._isChildControlVisible("scrollbar-x") &&
        this._isChildControlVisible("scrollbar-y")
      ) {
        this._showChildControl("corner");
      } else {
        this._excludeChildControl("corner");
      }
    },


    /**
     * Configures the given scollbar
     *
     * @param scrollbarId {String} child control id of the scrollbar to
     *   configure
     * @param show {Boolean} whether the scrollbar should be visible
     * @param containerSize {Integer} size of the container widget
     * @param contentSize {Integer} size of the iframe's document
     */
    _configureScrollbar : function(scrollbarId, show, containerSize, contentSize)
    {
      if (!show)
      {
        this._excludeChildControl(scrollbarId);
        return;
      }

      var bar = this._showChildControl(scrollbarId);
      if (containerSize >= contentSize)
      {
        bar.set({
          position: 0,
          maximum: contentSize,
          knobFactor: 1,
          enabled: false
        });
      }
      else
      {
        bar.setMaximum(1000000)
        bar.set({
          position: Math.min(bar.getPosition(), contentSize),
          maximum: contentSize - containerSize,
          knobFactor: containerSize / contentSize,
          enabled: true
        });
      }
    },


    /**
     * Event handler for the scroll event of the horizontal scrollbar
     *
     * @param e {qx.event.type.Data} The scroll event object
     */
    _onScrollBarX : function(e) {
      this.scrollToX(e.getData());
    },


    /**
     * Event handler for the scroll event of the vertical scrollbar
     *
     * @param e {qx.event.type.Data} The scroll event object
     */
    _onScrollBarY : function(e) {
      this.scrollToY(e.getData());
    },


    /**
     * Scrolls the iframe's content to the given left coordinate
     *
     * @param x {Integer} The vertical position to scroll to.
     */
    scrollToX : function(x)
    {
      try
      {
        var win = this._getIframeElement().getWindow();
        win.scroll(x, qx.bom.Viewport.getScrollTop(win));
      } catch (e) {
        this._disableScollbars();
      }
    },


    /**
     * Scrolls the iframe's content to the given top coordinate
     *
     * @param y {Integer} The horizontal position to scroll to.
     */
    scrollToY : function(y)
    {
      try
      {
        var win = this._getIframeElement().getWindow();
        win.scroll(qx.bom.Viewport.getScrollLeft(win), y);
      } catch (e) {
        this._disableScollbars();
      }
    }
  },


  destruct : function()
  {
    this._stopIframeObserver();
    this.__iframeSize = null;
  }
})