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

   qooxdoo - the new era of web development

   http://qooxdoo.org

   Copyright:
     2007 Derrell Lipman

   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:
     * Derrell Lipman (derrell)
     * David Perez Carmona (david-perez)

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

/**
 * A data cell renderer for the tree column of a simple tree
 *
 * This cell renderer has provisions for subclasses to easily extend the
 * appearance of the tree. If the tree should contain images, labels,
 * etc. before the indentation, the subclass should override the method
 * _addExtraContentBeforeIndentation(). Similarly, content can be added before
 * the icon by overriding _addExtraContentBeforeIcon(), and before the label
 * by overriding _addExtraContentBeforeLabel().
 *
 * Each of these overridden methods that calls _addImage() can provide, as
 * part of the map passed to _addImage(), a member called "tooltip" which
 * contains the tool tip to present when the mouse is hovered over the image.
 *
 * If this class is subclassed to form a new cell renderer, an instance of it
 * must be provided, via the 'custom' parameter, to the TreeVirtual
 * constructor.
 */
qx.Class.define("qx.ui.treevirtual.SimpleTreeDataCellRenderer",
{
  extend : qx.ui.table.cellrenderer.Abstract,


  construct : function()
  {
    var STDCR = qx.ui.treevirtual.SimpleTreeDataCellRenderer;

    // Begin preloading of the tree images, if not already requested.
    if (STDCR.__bVirgin)
    {
      STDCR.__preloadImages();
      STDCR.__bVirgin = false;
    }

    this.base(arguments);

    this.__am = qx.util.AliasManager.getInstance();
    this.__rm = qx.util.ResourceManager.getInstance();
    this.__tm = qx.theme.manager.Appearance.getInstance();

    // Base URL used for indentation
    this.BLANK = this.__rm.toUri(this.__am.resolve("static/blank.gif"));
  },


  statics :
  {
    /** File names of each of the tree icons */
    __icon : { },

    /** Whether we have not yet requested pre-loading of images */
    __bVirgin : true,

    /**
     * Request preloading of images so they appear immediately upon rendering
     */
    __preloadImages : function()
    {
      var STDCR = qx.ui.treevirtual.SimpleTreeDataCellRenderer;

      var ImageLoader = qx.io.ImageLoader;

      var am = qx.util.AliasManager.getInstance();
      var rm = qx.util.ResourceManager.getInstance();
      var tm = qx.theme.manager.Appearance.getInstance();

      var loadImage = function(f)
      {
        ImageLoader.load(rm.toUri(am.resolve(f)));
      };

      STDCR.__icon.line = tm.styleFrom("treevirtual-line");
      loadImage(STDCR.__icon.line.icon);

      STDCR.__icon.contract = tm.styleFrom("treevirtual-contract");
      loadImage(STDCR.__icon.contract.icon);

      STDCR.__icon.expand = tm.styleFrom("treevirtual-expand");
      loadImage(STDCR.__icon.expand.icon);

      STDCR.__icon.onlyContract = tm.styleFrom("treevirtual-only-contract");
      loadImage(STDCR.__icon.onlyContract.icon);

      STDCR.__icon.onlyExpand = tm.styleFrom("treevirtual-only-expand");
      loadImage(STDCR.__icon.onlyExpand.icon);

      STDCR.__icon.startContract = tm.styleFrom("treevirtual-start-contract");
      loadImage(STDCR.__icon.startContract.icon);

      STDCR.__icon.startExpand = tm.styleFrom("treevirtual-start-expand");
      loadImage(STDCR.__icon.startExpand.icon);

      STDCR.__icon.endContract = tm.styleFrom("treevirtual-end-contract");
      loadImage(STDCR.__icon.endContract.icon);

      STDCR.__icon.endExpand = tm.styleFrom("treevirtual-end-expand");
      loadImage(STDCR.__icon.endExpand.icon);

      STDCR.__icon.crossContract = tm.styleFrom("treevirtual-cross-contract");
      loadImage(STDCR.__icon.crossContract.icon);

      STDCR.__icon.crossExpand = tm.styleFrom("treevirtual-cross-expand");
      loadImage(STDCR.__icon.crossExpand.icon);

      STDCR.__icon.end = tm.styleFrom("treevirtual-end");
      loadImage(STDCR.__icon.end.icon);

      STDCR.__icon.cross = tm.styleFrom("treevirtual-cross");
      loadImage(STDCR.__icon.cross.icon);
    }
  },


  properties :
  {
    /**
     * Set whether lines linking tree children shall be drawn on the tree
     * if the theme supports tree lines.
     */
    useTreeLines :
    {
      check : "Boolean",
      init : true
    },

    /**
     * When true, exclude only the first-level tree lines, creating,
     * effectively, multiple unrelated root nodes.
     */
    excludeFirstLevelTreeLines :
    {
      check : "Boolean",
      init : false
    },

    /**
     * Set whether the open/close button should be displayed on a branch, even
     * if the branch has no children.
     */
    alwaysShowOpenCloseSymbol :
    {
      check : "Boolean",
      init : false
    }
  },




  /*
  *****************************************************************************
     MEMBERS
  *****************************************************************************
  */

  members :
  {
    __am : null,
    __tm : null,
    __rm : null,


    // overridden
    _onChangeTheme : function() {
      this.base(arguments);
      qx.ui.treevirtual.SimpleTreeDataCellRenderer.__preloadImages();
    },


    // overridden
    _getCellStyle : function(cellInfo)
    {
      var node = cellInfo.value;

      // Return the style for the div for the cell.  If there's cell-specific
      // style information provided, append it.
      var html =
        this.base(arguments, cellInfo) +
        (node.cellStyle ? node.cellStyle + ";" : "");
      return html;
    },

    // overridden
    _getContentHtml : function(cellInfo)
    {
      var html = "";

      // Horizontal position
      var pos = 0;

      // If needed, add extra content before indentation
      var extra = this._addExtraContentBeforeIndentation(cellInfo, pos);
      html += extra.html;
      pos = extra.pos;

      // Add the indentation (optionally with tree lines)
      var indentation = this._addIndentation(cellInfo, pos);
      html += indentation.html;
      pos = indentation.pos;

      // If needed, add extra content before icon
      extra = this._addExtraContentBeforeIcon(cellInfo, pos);
      html += extra.html;
      pos = extra.pos;

      // Add the node icon
      var icon = this._addIcon(cellInfo, pos);
      html += icon.html;
      pos = icon.pos;

      // If needed, add extra content before label
      extra = this._addExtraContentBeforeLabel(cellInfo, pos);
      html += extra.html;
      pos = extra.pos;

      // Add the node's label
      html += this._addLabel(cellInfo, pos);

      return html;
    },

    /**
     * Add an image to the tree.  This might be a visible icon or it may be
     * part of the indentation.
     *
     * @param imageInfo {Map}
     *   How to display the image.  It optionally includes any of the
     *   following:
     *   <dl>
     *     <dt>position {Map}</dt>
     *     <dd>
     *       If provided, a div is created to hold the image.  The div's top,
     *       right, bottom, left, width, and/or height may be specified with
     *       members of this map.  Each is expected to be an integer value.
     *     </dd>
     *     <dt>imageWidth, imageHeight</dt>
     *     <dd>
     *       The image's width and height.  These are used only if both are
     *       specified.
     *     </dd>
     *   </dl>
     *
     * @return {String}
     *   The html for this image, possibly with a surrounding div (see
     *   'position', above).
     */
    _addImage : function(imageInfo)
    {
      var html = [];

      // Resolve the URI
      var source = this.__rm.toUri(this.__am.resolve(imageInfo.url));

      // If we've been given positioning attributes, enclose image in a div
      if (imageInfo.position)
      {
        var pos = imageInfo.position;

        html.push('<div style="position:absolute;');

        if (qx.core.Environment.get("css.boxsizing"))
        {
          html.push(qx.bom.element.BoxSizing.compile("content-box"));
        }

        if (pos.top !== undefined)
        {
          html.push('top:' + pos.top + 'px;');
        }

        if (pos.right !== undefined)
        {
          html.push('right:' + pos.right + 'px;');
        }

        if (pos.bottom !== undefined)
        {
          html.push('bottom:' + pos.bottom + 'px;');
        }

        if (pos.left !== undefined)
        {
          html.push('left:' + pos.left + 'px;');
        }

        if (pos.width !== undefined)
        {
          html.push('width:' + pos.width + 'px;');
        }

        if (pos.height !== undefined)
        {
          html.push('height:' + pos.height + 'px;');
        }

        html.push('">');
      }

      // Don't use an image tag.  They render differently in Firefox and IE7
      // even if both are enclosed in a div specified as content box.  Instead,
      // add the image as the background image of a div.
      html.push('<div style="');
      html.push('background-image:url(' + source + ');');
      html.push('background-repeat:no-repeat;');

      if (imageInfo.imageWidth && imageInfo.imageHeight)
      {
        html.push(
          ';width:' +
          imageInfo.imageWidth +
          'px' +
          ';height:' +
          imageInfo.imageHeight +
          'px');
      }

      var tooltip = imageInfo.tooltip;

      if (tooltip != null)
      {
        html.push('" title="' + tooltip);
      }

      html.push('">&nbsp;</div>');

      if (imageInfo.position)
      {
        html.push('</div>');
      }

      return html.join("");
    },


    /**
     * Add the indentation for this node of the tree.
     *
     * The indentation optionally includes tree lines.  Whether tree lines are
     * used depends on (a) the properties 'useTreeLines' and
     * 'excludeFirstLevelTreelines' within this class; and (b) the widget
     * theme in use (some themes don't support tree lines).
     *
     * @param cellInfo {Map} The information about the cell.
     *   See {@link qx.ui.table.cellrenderer.Abstract#createDataCellHtml}.
     *
     * @param pos {Integer}
     *   The position from the left edge of the column at which to render this
     *   item.
     *
     * @return {Map}
     *   The returned map contains an 'html' member which contains the html for
     *   the indentation, and a 'pos' member which is the starting position
     *   plus the width of the indentation.
     */
    _addIndentation : function(cellInfo, pos)
    {
      var node = cellInfo.value;
      var imageData;
      var html = "";

      // Generate the indentation.  Obtain icon determination values once
      // rather than each time through the loop.
      var bUseTreeLines = this.getUseTreeLines();
      var bExcludeFirstLevelTreeLines = this.getExcludeFirstLevelTreeLines();
      var bAlwaysShowOpenCloseSymbol = this.getAlwaysShowOpenCloseSymbol();

      for (var i=0; i<node.level; i++)
      {
        imageData = this._getIndentSymbol(i, node, bUseTreeLines,
                                          bAlwaysShowOpenCloseSymbol,
                                          bExcludeFirstLevelTreeLines);

        var rowHeight = cellInfo.table.getRowHeight();

        html += this._addImage(
        {
          url         : imageData.icon,
          position    :
          {
            top         : 0 + (imageData.paddingTop || 0),
            left        : pos + (imageData.paddingLeft || 0),
            width       : rowHeight + 3,
            height      : rowHeight
          },
          imageWidth  : rowHeight,
          imageHeight : rowHeight
        });
        pos += rowHeight + 3;
      }

      return (
        {
          html : html,
          pos  : pos
        });
    },

    /**
     * Add the icon for this node of the tree.
     *
     * @param cellInfo {Map} The information about the cell.
     *   See {@link qx.ui.table.cellrenderer.Abstract#createDataCellHtml}.
     *
     * @param pos {Integer}
     *   The position from the left edge of the column at which to render this
     *   item.
     *
     * @return {Map}
     *   The returned map contains an 'html' member which contains the html for
     *   the icon, and a 'pos' member which is the starting position plus the
     *   width of the icon.
     */
    _addIcon : function(cellInfo, pos)
    {
      var node = cellInfo.value;

      // Add the node's icon
      var imageUrl = (node.bSelected ? node.iconSelected : node.icon);

      if (!imageUrl)
      {
        if (node.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.LEAF)
        {
          var o = this.__tm.styleFrom("treevirtual-file");
        }
        else
        {
          var states = { opened : node.bOpened };
          var o = this.__tm.styleFrom("treevirtual-folder", states);
        }

        imageUrl = o.icon;
      }

      var rowHeight = cellInfo.table.getRowHeight();

      var html = this._addImage(
      {
        url         : imageUrl,
        position    :
        {
          top         : 0,
          left        : pos,
          width       : rowHeight + 3,
          height      : rowHeight
        },
        imageWidth  : rowHeight,
        imageHeight : rowHeight
      });

      return (
        {
          html : html,
          pos  : pos + rowHeight + 3
        });
    },

    /**
     * Add the label for this node of the tree.
     *
     * @param cellInfo {Map} The information about the cell.
     *   See {@link qx.ui.table.cellrenderer.Abstract#createDataCellHtml}.
     *   Additionally, if defined, the labelSpanStyle member is used to apply
     *   style to the span containing the label.  (This member is for use by
     *   subclasses; it's not otherwise used by this class.)
     *
     * @param pos {Integer}
     *   The position from the left edge of the column at which to render this
     *   item.
     *
     * @return {String}
     *   The html for the label.
     */
    _addLabel : function(cellInfo, pos)
    {
      var node = cellInfo.value;
      var label = node.label;

      if (qx.core.Environment.get("qx.dynlocale")) {
        if (label && label.translate) {
          label = label.translate();
        }
      }

      // Add the node's label.  We calculate the "left" property with: each
      // tree line (indentation) icon is 19 pixels wide; the folder icon is 16
      // pixels wide, there are two pixels of padding at the left, and we want
      // 2 pixels between the folder icon and the label
      var html =
        '<div style="position:absolute;' +
        'left:' + pos + 'px;' +
        'top:0;' +
        (node.labelStyle ? node.labelStyle + ";" : "") +
        '">' +
        '<span' + (cellInfo.labelSpanStyle
                   ? 'style="' + cellInfo.labelSpanStyle + ';"'
                   : "") + '>' +
        label +
        '</span>' +
        '</div>';

      return html;
    },

    /**
     * Adds extra content just before the indentation.
     *
     * @param cellInfo {Map} The information about the cell.
     *      See {@link qx.ui.table.cellrenderer.Abstract#createDataCellHtml}.
     *
     * @param pos {Integer}
     *   The position from the left edge of the column at which to render this
     *   item.
     *
     * @return {Map}
     *   The returned map contains an 'html' member which contains the html for
     *   the indentation, and a 'pos' member which is the starting position
     *   plus the width of the indentation.
     */
    _addExtraContentBeforeIndentation : function(cellInfo, pos)
    {
      return { html: '', pos: pos };
    },

    /**
     * Adds extra content just before the icon.
     *
     * @param cellInfo {Map} The information about the cell.
     *      See {@link qx.ui.table.cellrenderer.Abstract#createDataCellHtml}.
     *
     * @param pos {Integer}
     *   The position from the left edge of the column at which to render this
     *   item.
     *
     * @return {Map}
     *   The returned map contains an 'html' member which contains the html for
     *   the indentation, and a 'pos' member which is the starting position
     *   plus the width of the indentation.
     */
    _addExtraContentBeforeIcon : function(cellInfo, pos)
    {
      return { html: '', pos: pos };
    },

    /**
     * Adds extra content just before the label.
     *
     * @param cellInfo {Map} The information about the cell.
     *      See {@link qx.ui.table.cellrenderer.Abstract#createDataCellHtml}.
     *
     * @param pos {Integer}
     *   The position from the left edge of the column at which to render this
     *   item.
     *
     * @return {Map}
     *   The returned map contains an 'html' member which contains the html for
     *   the indentation, and a 'pos' member which is the starting position
     *   plus the width of the indentation.
     */
    _addExtraContentBeforeLabel : function(cellInfo, pos)
    {
      return { html: '', pos: pos };
    },


    /**
     * Determine the symbol to use for indentation of a tree row, at a
     * particular column.  The indentation to use may be just white space or
     * may be a tree line.  Tree lines come in numerous varieties, so the
     * appropriate one is selected.
     *
     * @param column {Integer}
     *   The column of indentation being requested, zero-relative
     *
     * @param node {Node}
     *   The node being displayed in the row.  The properties of a node are
     *   described in {@link qx.ui.treevirtual.SimpleTreeDataModel}
     *
     * @param bUseTreeLines {Boolean}
     *   Whether to find an appropriate tree line icon, or simply provide
     *   white space.
     *
     * @param bAlwaysShowOpenCloseSymbol {Boolean}
     *   Whether to display the open/close icon for a node even if it has no
     *   children.
     *
     * @param bExcludeFirstLevelTreeLines {Boolean}
     *   If bUseTreeLines is enabled, then further filtering of the left-most
     *   tree line may be specified here.  If <i>true</i> then the left-most
     *   tree line, between top-level siblings, will not be displayed.
     *   If <i>false</i>, then the left-most tree line wiill be displayed
     *   just like all of the other tree lines.
     *
     * @return {Map} map of image properties.
     */
    _getIndentSymbol : function(column,
                                node,
                                bUseTreeLines,
                                bAlwaysShowOpenCloseSymbol,
                                bExcludeFirstLevelTreeLines)
    {
      var STDCR = qx.ui.treevirtual.SimpleTreeDataCellRenderer;

      // If we're in column 0 and excludeFirstLevelTreeLines is enabled, then
      // we treat this as if no tree lines were requested.
      if (column == 0 && bExcludeFirstLevelTreeLines)
      {
        bUseTreeLines = false;
      }

      // If we're not on the final column...
      if (column < node.level - 1)
      {
        // then return either a line or a blank icon, depending on
        // bUseTreeLines
        return (bUseTreeLines && ! node.lastChild[column]
                ? STDCR.__icon.line
                : { icon : this.BLANK });
      }

      var bLastChild = node.lastChild[node.lastChild.length - 1];

      // Is this a branch node that does not have the open/close button hidden?
      if (node.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH &&
          ! node.bHideOpenClose)
      {
        // Does this node have any children, or do we always want the
        // open/close symbol to be shown?
        if (node.children.length > 0 || bAlwaysShowOpenCloseSymbol)
        {
          // If we're not showing tree lines...
          if (!bUseTreeLines)
          {
            // ... then just use an expand or contract
            return (node.bOpened
                    ? STDCR.__icon.contract
                    : STDCR.__icon.expand);
          }

          // Are we looking at a top-level, first child of its parent?
          if (column == 0 && node.bFirstChild)
          {
            // Yup.  If it's also a last child...
            if (bLastChild)
            {
              // ... then use no tree lines.
              return (node.bOpened
                      ? STDCR.__icon.onlyContract
                      : STDCR.__icon.onlyExpand);
            }
            else
            {
              // otherwise, use descender lines but no ascender.
              return (node.bOpened
                      ? STDCR.__icon.startContract
                      : STDCR.__icon.startExpand);
            }
          }

          // It's not a top-level, first child.  Is this the last child of its
          // parent?
          if (bLastChild)
          {
            // Yup.  Return an ending expand or contract.
            return (node.bOpened
                    ? STDCR.__icon.endContract
                    : STDCR.__icon.endExpand);
          }

          // Otherwise, return a crossing expand or contract.
          return (node.bOpened
                  ? STDCR.__icon.crossContract
                  : STDCR.__icon.crossExpand);
        }
      }

      // This node does not have any children.  Return an end or cross, if
      // we're using tree lines.
      if (bUseTreeLines)
      {
        // If this is a child of the root node...
        if (node.parentNodeId == 0)
        {
          // If this is the only child...
          if (bLastChild && node.bFirstChild)
          {
            // ... then return a blank.
            return { icon : this.BLANK };
          }

          // Otherwise, if this is the last child...
          if (bLastChild)
          {
            // ... then return an end line.
            return STDCR.__icon.end;
          }

          // Otherwise if this is the first child and is a branch...
          if (node.bFirstChild &&
              node.type == qx.ui.treevirtual.SimpleTreeDataModel.Type.BRANCH)
          {
            // ... then return a start line.
            return (node.bOpened
                    ? STDCR.__icon.startContract
                    : STDCR.__icon.startExpand);
          }
        }

        // If this is a last child, return and ending line; otherwise cross.
        return (bLastChild
                ? STDCR.__icon.end
                : STDCR.__icon.cross);
      }

      return { icon : this.BLANK };
    }
  },

  destruct : function() {
    this.__am = this.__rm = this.__tm = null;
  }
});
