Package com.sencha.gxt.widget.core.client.grid

Source Code of com.sencha.gxt.widget.core.client.grid.GridView$StateBundle

/**
* Sencha GXT 3.1.0-beta - Sencha for GWT
* Copyright(c) 2007-2014, Sencha, Inc.
* licensing@sencha.com
*
* http://www.sencha.com/products/gxt/license/
*/
package com.sencha.gxt.widget.core.client.grid;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Stack;
import java.util.logging.Logger;

import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.Cell.Context;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.EventTarget;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.event.shared.SimpleEventBus;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.ImportedWithPrefix;
import com.google.gwt.safecss.shared.SafeStyles;
import com.google.gwt.safecss.shared.SafeStylesBuilder;
import com.google.gwt.safecss.shared.SafeStylesUtils;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.safehtml.shared.SafeHtmlUtils;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.HasHorizontalAlignment.HorizontalAlignmentConstant;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.FocusImpl;
import com.sencha.gxt.core.client.GXT;
import com.sencha.gxt.core.client.GXTLogConfiguration;
import com.sencha.gxt.core.client.Style;
import com.sencha.gxt.core.client.ValueProvider;
import com.sencha.gxt.core.client.dom.DomHelper;
import com.sencha.gxt.core.client.dom.XDOM;
import com.sencha.gxt.core.client.dom.XElement;
import com.sencha.gxt.core.client.resources.CommonStyles;
import com.sencha.gxt.core.client.util.DelayedTask;
import com.sencha.gxt.core.client.util.Point;
import com.sencha.gxt.core.client.util.Size;
import com.sencha.gxt.core.client.util.Util;
import com.sencha.gxt.data.shared.ListStore;
import com.sencha.gxt.data.shared.SortDir;
import com.sencha.gxt.data.shared.SortInfo;
import com.sencha.gxt.data.shared.SortInfoBean;
import com.sencha.gxt.data.shared.Store.Record;
import com.sencha.gxt.data.shared.Store.StoreSortInfo;
import com.sencha.gxt.data.shared.event.StoreAddEvent;
import com.sencha.gxt.data.shared.event.StoreClearEvent;
import com.sencha.gxt.data.shared.event.StoreDataChangeEvent;
import com.sencha.gxt.data.shared.event.StoreFilterEvent;
import com.sencha.gxt.data.shared.event.StoreHandlers;
import com.sencha.gxt.data.shared.event.StoreRecordChangeEvent;
import com.sencha.gxt.data.shared.event.StoreRemoveEvent;
import com.sencha.gxt.data.shared.event.StoreSortEvent;
import com.sencha.gxt.data.shared.event.StoreUpdateEvent;
import com.sencha.gxt.messages.client.DefaultMessages;
import com.sencha.gxt.widget.core.client.Component;
import com.sencha.gxt.widget.core.client.ComponentHelper;
import com.sencha.gxt.widget.core.client.event.BodyScrollEvent;
import com.sencha.gxt.widget.core.client.event.CheckChangeEvent;
import com.sencha.gxt.widget.core.client.event.CheckChangeEvent.CheckChangeHandler;
import com.sencha.gxt.widget.core.client.event.ColumnMoveEvent;
import com.sencha.gxt.widget.core.client.event.ColumnWidthChangeEvent;
import com.sencha.gxt.widget.core.client.event.HeaderClickEvent;
import com.sencha.gxt.widget.core.client.event.HeaderClickEvent.HeaderClickHandler;
import com.sencha.gxt.widget.core.client.event.RefreshEvent;
import com.sencha.gxt.widget.core.client.event.SortChangeEvent;
import com.sencha.gxt.widget.core.client.grid.ColumnHeader.HeaderContextMenuFactory;
import com.sencha.gxt.widget.core.client.menu.CheckMenuItem;
import com.sencha.gxt.widget.core.client.menu.Item;
import com.sencha.gxt.widget.core.client.menu.Menu;
import com.sencha.gxt.widget.core.client.menu.MenuItem;

/**
* This class encapsulates the user interface of an {@link Grid}. Methods of this class may be used to access user
* interface elements to enable special display effects. Do not change the DOM structure of the user interface. </p>
* <p />
* This class does not provide ways to manipulate the underlying data. The data model of a Grid is held in a
* {@link ListStore}.
*/
public class GridView<M> {

  /**
   * Define the appearance of a Grid.
   */
  public interface GridAppearance {

    Element findCell(Element elem);

    Element findRow(Element elem);

    Element getRowBody(Element row);

    NodeList<Element> getRows(XElement parent);

    void onCellSelect(Element cell, boolean select);

    void onRowHighlight(Element row, boolean highlight);

    void onRowOver(Element row, boolean over);

    void onRowSelect(Element row, boolean select);

    /**
     * Renders the HTML markup for the widget.
     *
     * @param sb the safe html builder
     */
    void render(SafeHtmlBuilder sb);

    SafeHtml renderEmptyContent(String emptyText);

    GridStyles styles();

  }

  /**
   * Marker classes, used only to indicate state and landmark details in the dom. Not to be used directly to
   * style anything.
   * <p/>
   * Attached to a empty file as no real style should be set. This does not need to be
   * injected, and should not be extended.
   */
  @ImportedWithPrefix("grid")
  public interface GridStateStyles extends CssResource {
   
    /**
     * Marker class to indicate that the row is selected
     * @see com.sencha.gxt.theme.base.client.grid.CheckBoxColumnDefaultAppearance
     * @see com.sencha.gxt.theme.base.client.grid.GroupingViewDefaultAppearance
     * @see com.sencha.gxt.theme.base.client.grid.RowExpanderDefaultAppearance
     */
    String rowSelected();

    /**
     * Marker class to indicate that the cell is selected
     * @see CellSelectionModel
     */
    String cellSelected();

    /**
     * Marker class for the TD that wraps each cell in the grid.
     */
    String cell();

    /**
     * Marker class for the DIV that each cell is rendered into
     */
    String cellInner();

    /**
     * Marker class for each row in the grid
     */
    String  row();

    /**
     * Optional marker class for the DIV that surrounds each row's own individual TABLE.
     * @see GridView#setEnableRowBody(boolean)
     */
    String rowWrap();

    /**
     * Optional marker class for the TR that holds the extra row body.
     * @see GridView#setEnableRowBody(boolean)
     */
    String rowBodyRow();

    /**
     * Optional marker class for the DIV that holds the extra row body.
     * @see GridView#setEnableRowBody(boolean)
     */
    String rowBody();

  }
  interface StateBundle extends ClientBundle {
    @Source("GridStateStyles.css")
    GridStateStyles styles();
  }

  public interface GridStyles extends CssResource {

    String cell();

    String cellDirty();

    String cellInner();

    String noPadding();

    String columnLines();

    String dataTable();

    String headerRow();

    String row();

    String rowAlt();

    String rowBody();

    String rowDirty();

    String rowHighlight();

    String rowOver();

    String rowWrap();

    String empty();

    String footer();

    String grid();
  }

  public interface GridTemplates extends SafeHtmlTemplates {
    @Template("<table cellpadding=\"0\" cellspacing=\"0\" class=\"{0}\" style=\"{1};table-layout: fixed\"><tbody>{3}</tbody><tbody>{2}</tbody></table>")
    SafeHtml table(String classes, SafeStyles tableStyles, SafeHtml contents, SafeHtml sizingHeads);

    @Template("<td cellindex=\"{0}\" class=\"{1}\" style=\"{2}\"><div class=\"{3}\" style=\"{4}\">{5}</div></td>")
    SafeHtml td(int cellIndex, String cellClasses, SafeStyles cellStyles, String textClasses, SafeStyles textStyles,
        SafeHtml contents);

    @Template("<td cellindex=\"{0}\" class=\"{1}\" style=\"{2}\" rowspan=\"{3}\"><div class=\"{4}\">{5}</div></td>")
    SafeHtml tdRowSpan(int cellIndex, String classes, SafeStyles styles, int rowSpan, String cellInnerClasses, SafeHtml contents);

    @Template("<td cellindex=\"{0}\" class=\"{1}\" style=\"{2}\"><div class=\"{3}\" style=\"{4}\" unselectable=\"on\">{5}</div></td>")
    SafeHtml tdUnselectable(int cellIndex, String cellClasses, SafeStyles cellStyles, String textClasses,
        SafeStyles textStyles, SafeHtml contents);

    @Template("<td colspan=\"{0}\" class=\"{1}\"><div class=\"{2}\">{3}</div></td>")
    SafeHtml tdWrap(int colspan, String cellClasses, String textClasses, SafeHtml content);

    @Template("<td colspan=\"{0}\" class=\"{1}\"><div class=\"{2}\" unselectable=\"on\">{3}</div></td>")
    SafeHtml tdWrapUnselectable(int colspan, String cellClasses, String textClasses, SafeHtml content);

    @Template("<th class=\"{0}\" style=\"{1}\"></th>")
    SafeHtml th(String classes, SafeStyles cellStyles);

    @Template("<tr class=\"{0}\">{1}</tr>")
    SafeHtml tr(String classes, SafeHtml contents);
  }

  private static Logger logger = Logger.getLogger(GridView.class.getName());

  /**
   * Set to true to auto expand the columns to fit the grid when the grid is created.
   */
  protected boolean autoFill;

  /**
   * The content area inside the scroller.
   */
  protected XElement body;

  /**
   * A border width calculation to be applied for browsers that do not use the old IE box model.
   */
  protected int borderWidth = 2;

  /**
   * The grid's column model.
   */
  protected ColumnModel<M> cm;

  /**
   * The handler for column events such as move, width change and hide.
   */
  protected ColumnModelHandlers columnListener;

  /**
   * The main data table and body.
   */
  protected XElement dataTable, dataTableBody, dataTableSizingHead;

  protected int deferUpdateDelay = 500;

  protected boolean deferUpdates = false;

  /**
   * The list store that provides data for this grid view.
   */
  protected ListStore<M> ds;

  /**
   * The safe HTML value to display when the grid is empty (defaults to &nbsp;).
   */
  protected String emptyText = "&nbsp;";

  /**
   * True to enable a column spanning row body, as used by {@link RowExpander} (defaults to false).
   */
  protected boolean enableRowBody = false;

  protected XElement focusEl;

  protected final FocusImpl focusImpl = FocusImpl.getFocusImplForPanel();

  protected ColumnFooter<M> footer;

  protected boolean focusConstrainScheduled = false;
  protected boolean forceFit;
  protected Grid<M> grid;
  private ColumnHeader<M> header;
  protected int headerColumnIndex;
  protected boolean headerDisabled;
  /**
   * The inner head element.
   */
  protected XElement headerElem;
  protected int lastViewWidth;
  protected StoreHandlers<M> listener;
  protected Element overRow;
  protected boolean preventScrollToTopOnRefresh;
  /**
   * The scrollable area that contains the main body.
   */
  protected XElement scroller;
  protected int scrollOffset = XDOM.getScrollBarWidth();
  protected boolean selectable = false;

  protected SortInfo sortState;
  protected int splitterWidth = 5;
  protected final GridStateStyles states = GWT.<StateBundle>create(StateBundle.class).styles();
  protected StoreSortInfo<M> storeSortInfo;
  protected GridStyles styles;
  protected GridTemplates tpls = GWT.create(GridTemplates.class);
  protected String unselectable = CommonStyles.get().unselectableSingle();

  // we first render grid with a vbar, and remove as needed
  protected boolean vbar = true;
  protected GridViewConfig<M> viewConfig;
  private DelayedTask addTask = new DelayedTask() {

    @Override
    public void onExecute() {
      calculateVBar(false);
      refreshFooterData();
    }

  };
  private boolean adjustForHScroll = true;
  private GridAppearance appearance;
  private ColumnConfig<M, ?> autoExpandColumn;
  private int autoExpandMax = 500;

  private int autoExpandMin = 25;
  private HandlerRegistration cmHandlerRegistration;
  /**
   * True to show vertical column lines between cells.
   */
  private boolean columnLines;

  /**
   * True to enable the grid to be focused (defaults to true).
   */
  private boolean focusEnabled = true;

  private DelayedTask removeTask = new DelayedTask() {

    @Override
    public void onExecute() {
      calculateVBar(false);
      applyEmptyText();
      refreshFooterData();
      processRows(0, false);
    }

  };

  /**
   * The numbers of rows the first column should span if row bodies are enabled (defaults to 1).
   */
  private int rowBodyRowSpan = 1;

  private boolean showDirtyCells = true;
  private HandlerRegistration storeHandlerRegistration;
  private boolean stripeRows;

  private boolean trackMouseOver = true;
  private SimpleEventBus eventBus;

  /**
   * Creates a new grid view.
   */
  public GridView() {
    this(GWT.<GridAppearance> create(GridAppearance.class));
  }

  /**
   * Creates a new grid view.
   *
   * @param appearance the grid appearance
   */
  public GridView(GridAppearance appearance) {
    this.appearance = appearance;

    this.styles = appearance.styles();
  }

  /**
   * Adds a handler to receive specific events from this object. Only subclasses typically should need to call this
   * directly.
   *
   * @param type the type of event to listen for
   * @param handler the handler to call when the event occurs
   * @param <H> the handler type
   * @return the handler registration, enabling the handler to be removed when not needed
   */
  public <H extends EventHandler> HandlerRegistration addHandler(GwtEvent.Type<H> type, H handler) {
    return ensureHandlers().addHandlerToSource(type, this, handler);
  }

  /**
   * Ensured the current row and column is visible.
   *
   * @param row the row index
   * @param col the column index
   * @param hscroll true to scroll horizontally if needed
   * @return the calculated point
   */
  public Point ensureVisible(int row, int col, boolean hscroll) {
    if (grid == null || !grid.isViewReady() || row < 0 || row > ds.size()) {
      return null;
    }

    if (col == -1) {
      col = 0;
    }

    Element rowEl = getRow(row);
    Element cellEl = null;
    if (!(!hscroll && col == 0)) {
      while (cm.isHidden(col)) {
        col++;
      }
      cellEl = getCell(row, col);

    }

    if (rowEl == null) {
      return null;
    }

    Element c = scroller;

    int ctop = 0;
    Element p = rowEl, stope = grid.getElement();
    while (p != null && p != stope) {
      ctop += p.getOffsetTop();
      p = p.getOffsetParent();
    }
    ctop -= headerElem.getOffsetHeight();

    int cbot = ctop + rowEl.getOffsetHeight();

    int ch = c.getOffsetHeight();
    int stop = c.getScrollTop();
    int sbot = stop + ch;

    // EXTGWT-2791
    int adj = GXT.isIE10() ? 1 : 0;
    if (ctop < stop) {
      c.setScrollTop(ctop + adj);
    } else if (cbot > sbot) {
      if ((getTotalWidth() > scroller.getWidth(false) - scrollOffset)) {
        cbot += scrollOffset;
      }
      c.setScrollTop((cbot -= ch) + adj);
    }

    if (hscroll && cellEl != null) {
      int cleft = cellEl.getOffsetLeft();
      int cright = cleft + cellEl.getOffsetWidth();
      int sleft = c.getScrollLeft();
      int sright = sleft + c.getOffsetWidth();
      if (cleft < sleft) {
        c.setScrollLeft(cleft);
      } else if (cright > sright) {
        c.setScrollLeft(cright - scroller.getComputedWidth());
      }
    }

    return cellEl != null ? cellEl.<XElement>cast().getXY() : new Point(c.getAbsoluteLeft() + c.getScrollLeft(), rowEl.<XElement>cast().getY());
  }

  /**
   * Returns the cell.
   *
   * @param elem the cell element or a child element
   * @return the cell element
   */
  public Element findCell(Element elem) {
    if (elem == null) {
      return null;
    }
    return appearance.findCell(elem);
  }

  /**
   * Returns the cell index.
   *
   * @param elem the cell or child element
   * @param requiredStyle an optional required style name
   * @return the cell index or -1 if not found
   */
  public int findCellIndex(Element elem, String requiredStyle) {
    Element cell = findCell(elem);
    if (cell != null && (requiredStyle == null || elem.hasClassName(requiredStyle))) {
      String index = cell.getAttribute("cellindex");
      return index.equals("") ? -1 : Integer.parseInt(index);
    }
    return -1;
  }

  /**
   * Returns the row element.
   *
   * @param elem the row element or any child element
   * @return the matching row element
   */
  public Element findRow(Element elem) {
    if (elem == null) {
      return null;
    }
    return appearance.findRow(elem);
  }

  /**
   * Returns the row index.
   *
   * @param elem the row or child of the row element
   * @return the index
   */
  public int findRowIndex(Element elem) {
    Element r = findRow(elem);
    if (r != null) {
      return r.getPropertyInt("rowindex");
    }
    return -1;
  }

  /**
   * Fires the given event from this object as its source.
   *
   * @param event the event to fire
   */
  public void fireEvent(GwtEvent<?> event) {
    if (eventBus != null) {
      eventBus.fireEventFromSource(event, this);
    }
  }

  /**
   * Focuses the grid.
   */
  public void focus() {
    if (GXT.isGecko()) {
      Scheduler.get().scheduleDeferred(new ScheduledCommand() {
        @Override
        public void execute() {
          if (focusEl != null) {
            focusImpl.focus(focusEl);
          }
        }
      });
    } else if (focusEl != null) {
      focusImpl.focus(focusEl);
    }
  }

  /**
   * Focus the cell and scrolls into view.
   *
   * @param rowIndex the row index
   * @param colIndex the column index
   * @param hscroll true to scroll horizontally
   */
  public void focusCell(int rowIndex, int colIndex, boolean hscroll) {
    Point xy = ensureVisible(rowIndex, colIndex, hscroll);
    if (xy != null) {
      focusEl.setXY(xy);
      if (focusEnabled) {
        focus();
      }
    }
  }

  /**
   * Focus the row and scrolls into view.
   *
   * @param rowIndex the row index
   */
  public void focusRow(int rowIndex) {
    focusCell(rowIndex, 0, false);
  }

  /**
   * Returns the grid appearance.
   *
   * @return the grid appearance
   */
  public GridAppearance getAppearance() {
    return appearance;
  }

  /**
   * Returns the auto expand column id.
   *
   * @return the auto expand column id
   */
  public ColumnConfig<M, ?> getAutoExpandColumn() {
    return autoExpandColumn;
  }

  /**
   * Returns the auto expand maximum width.
   *
   * @return the max width in pixels
   */
  public int getAutoExpandMax() {
    return autoExpandMax;
  }

  /**
   * Returns the auto expand minimum width.
   *
   * @return the minimum width in pixels
   */
  public int getAutoExpandMin() {
    return autoExpandMin;
  }

  /**
   * Returns the body element.
   *
   * @return the body element
   */
  public XElement getBody() {
    return body;
  }

  /**
   * Returns the grid's &lt;TD> HtmlElement at the specified coordinates.
   *
   * @param row the row index in which to find the cell
   * @param col the column index of the cell
   * @return the &lt;TD> at the specified coordinates
   */
  public Element getCell(int row, int col) {
    Element rowEl = getRow(row);
    if (rowEl == null || !rowEl.hasChildNodes() || col < 0) {
      return null;
    } else if (!enableRowBody) {
      return (Element) rowEl.getChildNodes().getItem(col);
    } else {
      return (Element) rowEl.getFirstChildElement().getFirstChildElement().getFirstChildElement().getFirstChildElement().getNextSiblingElement().getFirstChildElement().getChild(
          col);
    }
  }

  /**
   * Returns the editor parent element.
   *
   * @return the editor element
   */
  public Element getEditorParent() {
    return scroller;
  }

  /**
   * Returns the empty text.
   *
   * @return the empty text
   */
  public String getEmptyText() {
    return emptyText;
  }

  /**
   * Returns the grid's column header.
   *
   * @return the header
   */
  public ColumnHeader<M> getHeader() {
    return header;
  }

  /**
   * Return the &lt;TR> HtmlElement which represents a Grid row for the specified index.
   *
   * @param row the row index
   * @return the &lt;TR> element
   */
  public Element getRow(int row) {
    if (row < 0) {
      return null;
    }
    return getRows().getItem(row);
  }

  /**
   * Return the &lt;TR> HtmlElement which represents a Grid row for the specified model.
   *
   * @param m the model
   * @return the &lt;TR> element
   */
  public Element getRow(M m) {
    return getRow(ds.indexOf(m));
  }

  public Element getRowBody(Element row) {
    return appearance.getRowBody(row);
  }

  /**
   * Returns the number of rows the first column should span when row bodies have been enabled.
   *
   * @return the rowspan
   */
  public int getRowBodyRowSpan() {
    return rowBodyRowSpan;
  }

  /**
   * Returns the scroll element.
   *
   * @return the scroll element
   */
  public XElement getScroller() {
    return scroller;
  }

  /**
   * Returns the current scroll state.
   *
   * @return the scroll state
   */
  public Point getScrollState() {
    return new Point(scroller.getScrollLeft(), scroller.getScrollTop());
  }

  /**
   * Returns the grid's sort information.
   *
   * @return the grid's sort information (or null if the grid is not sorted).
   */
  public StoreSortInfo<M> getSortState() {
    if (ds.getSortInfo().size() > 0) {
      return ds.getSortInfo().get(0);
    }
    return null;
  }

  /**
   * @return The CssResource instance used to denote structural or stateful details about the grid.
   */
  public GridStateStyles getStateStyles() {
    return states;
  }

  /**
   * Returns the view config.
   *
   * @return the view config
   */
  public GridViewConfig<M> getViewConfig() {
    return viewConfig;
  }

  /**
   * Returns true if the grid width will be adjusted based on visibility of horizontal scroll bar.
   *
   * @return true if adjusting
   */
  public boolean isAdjustForHScroll() {
    return adjustForHScroll;
  }

  /**
   * Returns true if auto fill is enabled.
   *
   * @return true for auto fill
   */
  public boolean isAutoFill() {
    return autoFill;
  }

  /**
   * Returns true if column lines are enabled.
   *
   * @return true if column lines are enabled
   */
  public boolean isColumnLines() {
    return columnLines;
  }

  /**
   * Returns true if rows are updated deferred on updates.
   *
   * @return true if updates deferred
   */
  public boolean isDeferUpdates() {
    return deferUpdates;
  }

  /**
   * Returns true if row bodies are enabled.
   *
   * @return true for row bodies
   */
  public boolean isEnableRowBody() {
    return enableRowBody;
  }

  /**
   * Returns true if force fit is enabled.
   *
   * @return true for force fit
   */
  public boolean isForceFit() {
    return forceFit;
  }

  /**
   * Returns true if the given element is selectable.
   *
   * @param target the element to check
   * @return true if the given element is selectable
   */
  public boolean isSelectableTarget(Element target) {
    if (target == null) {
      return false;
    }

    String tag = target.getTagName();
    if ("input".equalsIgnoreCase(tag) || "textarea".equalsIgnoreCase(tag)) {
      return false;
    }

    int colIndex = findCellIndex(target, null);
    Element cellParent = getCell(findRowIndex(target), colIndex);

    com.google.gwt.cell.client.Cell<?> cell = grid.getColumnModel().getCell(colIndex);
    if (cell != null && cellParent != null && cellParent.isOrHasChild(target) && cell.handlesSelection()) {
      return false;
    }
    return true;
  }

  /**
   * Returns true if dirty cell markers are enabled.
   *
   * @return true of dirty cell markers
   */
  public boolean isShowDirtyCells() {
    return showDirtyCells;
  }

  /**
   * Returns true if sorting is enabled.
   *
   * @return true for sorting
   */
  public boolean isSortingEnabled() {
    return !headerDisabled;
  }

  /**
   * Returns true if row striping is enabled.
   *
   * @return the strip row state
   */
  public boolean isStripeRows() {
    return stripeRows;
  }

  /**
   * Returns true if rows are highlighted on mouse over.
   *
   * @return the track mouse state
   */
  public boolean isTrackMouseOver() {
    return trackMouseOver;
  }

  /**
   * Lays out the grid view, adjusting the header and footer width and accounting for force fit and auto fill settings.
   */
  public void layout() {
    layout(false);
  }

  /**
   * Rebuilds the grid using its current configuration and data.
   *
   * @param headerToo true to refresh the header
   */
  public void refresh(boolean headerToo) {
    if (grid != null && grid.isViewReady()) {

      if (!preventScrollToTopOnRefresh) {
        scrollToTop();
      }

      if (GXT.isIE()) {
        dataTableBody.removeChildren();
        dataTableSizingHead.removeChildren();
      } else {
        dataTableBody.setInnerHTML("");
        dataTableSizingHead.setInnerHTML("");
      }

      DomHelper.insertHtml("afterBegin", dataTableSizingHead, renderHiddenHeaders(getColumnWidths()).asString());
      DomHelper.insertHtml("afterBegin", dataTableBody, renderRows(0, -1).asString());

      dataTable.getStyle().setWidth(getTotalWidth(), Unit.PX);

      if (headerToo) {
        sortState = null;

        header.refresh();

        header.setEnableColumnResizing(grid.isColumnResize());
        header.setEnableColumnReorder(grid.isColumnReordering());
      }

      processRows(0, true);

      if (footer != null) {
        ComponentHelper.doDetach(footer);
        footer.getElement().removeFromParent();
      }
      if (cm.getAggregationRows().size() > 0) {
        footer = new ColumnFooter<M>(grid, cm);
        renderFooter();
        if (grid.isAttached()) {
          ComponentHelper.doAttach(footer);
        }
      }

      calculateVBar(true);

      updateHeaderSortState();

      applyEmptyText();

      grid.getElement().repaint();

      grid.fireEvent(new RefreshEvent());
    }
  }

  /**
   * Scrolls the grid to the top.
   */
  public void scrollToTop() {
    scroller.setScrollTop(0);
    scroller.setScrollLeft(0);
  }

  /**
   * True to adjust the grid width when the horizontal scrollbar is hidden and visible (defaults to true).
   *
   * @param adjustForHScroll true to adjust for horizontal scroll bar
   */
  public void setAdjustForHScroll(boolean adjustForHScroll) {
    this.adjustForHScroll = adjustForHScroll;
  }

  /**
   * The id of a column in this grid that should expand to fill unused space (pre-render). This id can not be 0.
   *
   * @param autoExpandColumn the auto expand column
   */
  public void setAutoExpandColumn(ColumnConfig<M, ?> autoExpandColumn) {
    this.autoExpandColumn = autoExpandColumn;
  }

  /**
   * The maximum width the autoExpandColumn can have (if enabled) (defaults to 500, pre-render).
   *
   * @param autoExpandMax the auto expand max
   */
  public void setAutoExpandMax(int autoExpandMax) {
    this.autoExpandMax = autoExpandMax;
  }

  /**
   * The minimum width the autoExpandColumn can have (if enabled)(pre-render).
   *
   * @param autoExpandMin the auto expand min width
   */
  public void setAutoExpandMin(int autoExpandMin) {
    this.autoExpandMin = autoExpandMin;
  }

  /**
   * True to auto expand the columns to fit the grid <b>when the grid is created</b>.
   *
   * @param autoFill true to expand
   */
  public void setAutoFill(boolean autoFill) {
    this.autoFill = autoFill;
  }

  /**
   * True to enable column separation lines (defaults to false).
   *
   * @param columnLines true to enable column separation lines
   */
  public void setColumnLines(boolean columnLines) {
    this.columnLines = columnLines;
  }

  /**
   * True to update rows deferred (defaults to false).
   *
   * @param deferUpdates true to update deferred
   */
  public void setDeferUpdates(boolean deferUpdates) {
    this.deferUpdates = deferUpdates;
  }

  /**
   * Default text to display in the grid body when no rows are available (defaults to '').
   *
   * @param emptyText the empty text
   */
  public void setEmptyText(String emptyText) {
    this.emptyText = emptyText;
  }

  /**
   * True to enable a column spanning row body, as used by {@link RowExpander} (defaults to false).
   *
   * @param enableRowBody true to enable row bodies
   */
  public void setEnableRowBody(boolean enableRowBody) {
    this.enableRowBody = enableRowBody;
  }

  /**
   * True to auto expand/contract the size of the columns to fit the grid width and prevent horizontal scrolling (defaults to false).
   *
   * @param forceFit true to force fit
   */
  public void setForceFit(boolean forceFit) {
    this.forceFit = forceFit;
    if (forceFit) {
      lastViewWidth = -1;
    }
  }

  /**
   * Sets the rowspan the first column should span when row bodies have been enabled (defaults to 1).
   *
   * @param rowBodyRowSpan the rowspan
   */
  public void setRowBodyRowSpan(int rowBodyRowSpan) {
    this.rowBodyRowSpan = rowBodyRowSpan;
  }

  /**
   * True to display a red triangle in the upper left corner of any cells which are "dirty" as defined by any existing
   * records in the data store (defaults to true).
   *
   * @param showDirtyCells true to display the dirty flag
   */
  public void setShowDirtyCells(boolean showDirtyCells) {
    this.showDirtyCells = showDirtyCells;
  }

  /**
   * True to allow column sorting when the user clicks a column (defaults to true).
   *
   * @param sortable true for sortable columns
   */
  public void setSortingEnabled(boolean sortable) {
    this.headerDisabled = !sortable;
  }

  /**
   * True to stripe the rows (defaults to false).
   *
   * @param stripeRows true to strip rows
   */
  public void setStripeRows(boolean stripeRows) {
    this.stripeRows = stripeRows;
  }

  /**
   * True to highlight rows when the mouse is over (defaults to true).
   *
   * @param trackMouseOver true to highlight rows on mouse over
   */
  public void setTrackMouseOver(boolean trackMouseOver) {
    this.trackMouseOver = trackMouseOver;
  }

  /**
   * Sets the view config.
   *
   * @param viewConfig the view config
   */
  public void setViewConfig(GridViewConfig<M> viewConfig) {
    this.viewConfig = viewConfig;
  }

  protected void adjustColumnWidths(int[] columnWidths) {
    int clen = cm.getColumnCount();

    NodeList<Element> tables = scroller.select("." + appearance.styles().dataTable());

    for (int t = 0, len = tables.getLength(); t < len; t++) {
      XElement table = tables.getItem(t).cast();

      table.getStyle().setWidth(getTotalWidth(), Unit.PX);

      NodeList<Element> ths = getTableHeads(table);
      if (ths == null) {
        continue;
      }

      for (int i = 0; i < ths.getLength(); i++) {
        ths.getItem(i).getStyle().setPropertyPx("width", cm.isHidden(i) ? 0 : columnWidths[i]);
      }
    }

    for (int i = 0; i < clen; i++) {
      header.updateColumnWidth(i, columnWidths[i]);
      if (footer != null) {
        footer.updateColumnWidth(i, columnWidths[i]);
      }
    }

    header.adjustColumnWidths(columnWidths);

    // safari cell widths incorrect
    if (GXT.isSafari()) {
      repaintGrid();
    }
  }

  /**
   * Invoked after the view element is first attached, performs steps that require that the view element is attached.
   */
  protected void afterRender() {
    DomHelper.insertHtml("afterBegin", dataTableBody, renderRows(0, -1).asString());

    dataTable.getStyle().setWidth(getTotalWidth(), Unit.PX);

    processRows(0, true);

    // overflow: hidden not working on render
    // alignment issues with some browsers
    if (GXT.isSafari() || GXT.isChrome() || GXT.isIE()) {
      repaintGrid();
    }

    if (footer != null && grid.getLazyRowRender() > 0) {
      footer.refresh();
    }

    int sh = scroller.getComputedHeight();
    int dh = body.getComputedHeight();
    boolean vbar = dh < sh;
    if (vbar) {
      this.vbar = !vbar;
      lastViewWidth = -1;
      layout();
    }

    applyEmptyText();
  }

  /**
   * Applies the empty text, normalizing it to non-breaking space if necessary, then displaying it if the grid is empty.
   */
  protected void applyEmptyText() {
    if (emptyText == null) {
      emptyText = "&nbsp;";
    }
    if (!hasRows()) {
      if (GXT.isIE()) {
        dataTableBody.removeChildren();
      } else {
        dataTableBody.setInnerHTML("");
      }

      SafeHtml con = appearance.renderEmptyContent(emptyText);
      con = tpls.tr("", tpls.tdWrap(cm.getColumnCount(), "", styles.empty(), con));
      DomHelper.append(dataTableBody, con.asString());
    }
  }

  /**
   * Expands the column that was specified (via {@link #setAutoExpandColumn}) as the column in this grid that should
   * expand to fill unused space.
   *
   * @param preventUpdate true to update the column model width without updating the displayed width.
   */
  protected void autoExpand(boolean preventUpdate) {
    if (!cm.isUserResized() && getAutoExpandColumn() != null) {
      int tw = cm.getTotalWidth(false);
      int aw = grid.getOffsetWidth(true) - getScrollAdjust();
      if (tw != aw) {
        int ci = cm.indexOf(getAutoExpandColumn());
        assert ci != Style.DEFAULT : "auto expand column not found";
        if (cm.isHidden(ci)) {
          return;
        }
        int currentWidth = cm.getColumnWidth(ci);
        int cw = Math.min(Math.max(((aw - tw) + currentWidth), getAutoExpandMin()), getAutoExpandMax());
        if (cw != currentWidth) {
          cm.setColumnWidth(ci, cw, true);

          if (!preventUpdate) {
            updateColumnWidth(ci, cw);
          }
        }
      }
    }
  }

  /**
   * Determines whether the need for a vertical scroll bar has changed and if so updates the display.
   *
   * @param force true to force the display to update regardless of whether a change has occurred.
   */
  protected void calculateVBar(boolean force) {
    if (force) {
      resize();
    }
    int sh = scroller.getComputedHeight();
    int dh = body.getComputedHeight();
    boolean vbar = dh > sh;
    if (force || this.vbar != vbar) {
      this.vbar = vbar;
      lastViewWidth = -1;
      layout(true);
    }
  }

  /**
   * Creates a context menu for the given column, including sort menu items and column visibility sub-menu.
   *
   * @param colIndex the column index
   * @return the context menu for the given column
   */
  protected Menu createContextMenu(final int colIndex) {
    final Menu menu = new Menu();

    if (cm.isSortable(colIndex)) {
      MenuItem item = new MenuItem();
      item.setText(DefaultMessages.getMessages().gridView_sortAscText());
      item.setIcon(header.getAppearance().sortAscendingIcon());
      item.addSelectionHandler(new SelectionHandler<Item>() {

        @Override
        public void onSelection(SelectionEvent<Item> event) {
          doSort(colIndex, SortDir.ASC);

        }
      });
      menu.add(item);

      item = new MenuItem();
      item.setText(DefaultMessages.getMessages().gridView_sortDescText());
      item.setIcon(header.getAppearance().sortDescendingIcon());
      item.addSelectionHandler(new SelectionHandler<Item>() {

        @Override
        public void onSelection(SelectionEvent<Item> event) {
          doSort(colIndex, SortDir.DESC);

        }
      });
      menu.add(item);
    }

    MenuItem columns = new MenuItem();
    columns.setText(DefaultMessages.getMessages().gridView_columnsText());
    columns.setIcon(header.getAppearance().columnsIcon());
    columns.setData("gxt-columns", "true");

    final Menu columnMenu = new Menu();

    int cols = cm.getColumnCount();
    for (int i = 0; i < cols; i++) {
      ColumnConfig<M, ?> config = cm.getColumn(i);
      // ignore columns with no header text
      if (!hasHeaderValue(i)) {
        continue;
      }
      final int fcol = i;
      final CheckMenuItem check = new CheckMenuItem();
      check.setHideOnClick(false);
      check.setHTML(cm.getColumnHeader(i));
      check.setChecked(!cm.isHidden(i));

      if (!config.isHideable()) {
        check.disable();
      }

      check.addCheckChangeHandler(new CheckChangeHandler<CheckMenuItem>() {

        @Override
        public void onCheckChange(CheckChangeEvent<CheckMenuItem> event) {
          cm.setHidden(fcol, !cm.isHidden(fcol));
          restrictMenu(cm, columnMenu);

        }
      });
      columnMenu.add(check);
    }

    restrictMenu(cm, columnMenu);
    columns.setEnabled(columnMenu.getWidgetCount() > 0);
    columns.setSubMenu(columnMenu);
    menu.add(columns);
    return menu;
  }

  /**
   * Helper method that creates a StoreSortInfo from the given ColumnConfig and sort direction. This will use the
   * provided {@link Comparator}, if any, otherwise will fall back to assuming that the data in the column is
   * {@link Comparable}.
   *
   * @param column the column config
   * @param sortDir the sort direction
   * @return the new store sort info instance
   */
  protected <V> StoreSortInfo<M> createStoreSortInfo(ColumnConfig<M, V> column, SortDir sortDir) {
    if (column.getComparator() == null) {
      // These casts can fail, but in dev mode the exception will be caught by
      // the try/catch in doSort, unless there are no items in the Store
      @SuppressWarnings({"unchecked", "rawtypes"})
      ValueProvider<M, Comparable> vp = (ValueProvider) column.getValueProvider();
      @SuppressWarnings("unchecked")
      StoreSortInfo<M> s = new StoreSortInfo<M>(ds.wrapRecordValueProvider(vp), sortDir);
      return s;
    } else {
      return new StoreSortInfo<M>(ds.wrapRecordValueProvider(column.getValueProvider()), column.getComparator(),
          sortDir);
    }
  }

  /**
   * Attaches ancillary widgets such as the header and footer to the grid.
   */
  protected void doAttach() {
    ComponentHelper.doAttach(header);
    ComponentHelper.doAttach(footer);
  }

  /**
   * Detaches ancillary widgets such as the header and footer from the grid.
   */
  protected void doDetach() {
    ComponentHelper.doDetach(header);
    ComponentHelper.doDetach(footer);
  }

  /**
   * Renders the grid view into safe HTML.
   *
   * @param cs the column attributes required for rendering
   * @param rows the data models for the rows to be rendered
   * @param startRow the index of the first row in <code>rows</code>
   */
  protected SafeHtml doRender(List<ColumnData> cs, List<M> rows, int startRow) {
    final int colCount = cm.getColumnCount();
    final int last = colCount - 1;

    int[] columnWidths = getColumnWidths();

    // root builder
    SafeHtmlBuilder buf = new SafeHtmlBuilder();

    final SafeStyles rowStyles = SafeStylesUtils.fromTrustedString("width: " + getTotalWidth() + "px;");

    final String unselectableClass = unselectable;
    final String rowAltClass = styles.rowAlt();
    final String rowDirtyClass = styles.rowDirty();

    final String cellClass = styles.cell() + " " + states.cell();
    final String cellInnerClass = styles.cellInner() + " " + states.cellInner();
    final String cellFirstClass = "x-grid-cell-first";
    final String cellLastClass = "x-grid-cell-last";
    final String cellDirty = styles.cellDirty();

    final String rowWrap = styles.rowWrap() + " " + states.rowWrap();
    final String rowBody = styles.rowBody() + " " + states.rowBody();
    final String rowBodyRow = states.rowBodyRow();

    // loop over all rows
    for (int j = 0; j < rows.size(); j++) {
      M model = rows.get(j);

      ListStore<M>.Record r = ds.hasRecord(model) ? ds.getRecord(model) : null;

      int rowBodyColSpanCount = colCount;
      if (enableRowBody) {
        for (ColumnConfig<M, ?> c : cm.getColumns()) {
          if (c instanceof RowExpander) {
            rowBodyColSpanCount--;
          }
        }
      }

      int rowIndex = (j + startRow);

      String rowClasses = styles.row() + " " + states.row();

      if (!selectable) {
        rowClasses += " " + unselectableClass;
      }
      if (isStripeRows() && ((rowIndex + 1) % 2 == 0)) {
        rowClasses += " " + rowAltClass;
      }

      if (showDirtyCells && r != null && r.isDirty()) {
        rowClasses += " " + rowDirtyClass;
      }

      if (viewConfig != null) {
        rowClasses += " " + viewConfig.getRowStyle(model, rowIndex);
      }

      SafeHtmlBuilder trBuilder = new SafeHtmlBuilder();

      // loop each cell per row
      for (int i = 0; i < colCount; i++) {
        SafeHtml rv = getRenderedValue(rowIndex, i, model, r);
        ColumnConfig<M, ?> columnConfig = cm.getColumn(i);
        ColumnData columnData = cs.get(i);

        String cellClasses = cellClass;
        if (i == 0) {
          cellClasses += " " + cellFirstClass;
        } else if (i == last) {
          cellClasses += " " + cellLastClass;
        }

        String cellInnerClasses = cellInnerClass;
        if (columnConfig.getColumnTextClassName() != null) {
          cellInnerClasses += " " + columnConfig.getColumnTextClassName();
        }
        if (!columnConfig.isCellPadding()) {
          cellInnerClasses += " " + styles.noPadding();
        }

        if (columnData.getClassNames() != null) {
          cellClasses += " " + columnData.getClassNames();
        }

        if (columnConfig.getCellClassName() != null) {
          cellClasses += " " + columnConfig.getCellClassName();
        }

        if (showDirtyCells && r != null && r.getChange(columnConfig.getValueProvider()) != null) {
          cellClasses += " " + cellDirty;
        }

        if (viewConfig != null) {
          cellClasses += " " + viewConfig.getColStyle(model, cm.getValueProvider(i), rowIndex, i);
        }

        final SafeStyles cellStyles = columnData.getStyles();

        final SafeHtml tdContent;
        if (enableRowBody && i == 0) {
          tdContent = tpls.tdRowSpan(i, cellClasses, cellStyles, rowBodyRowSpan, cellInnerClasses, rv);
        } else {
          if (!selectable && GXT.isIE()) {
            tdContent = tpls.tdUnselectable(i, cellClasses, cellStyles, cellInnerClasses,
                columnConfig.getColumnTextStyle(), rv);
          } else {
            tdContent = tpls.td(i, cellClasses, cellStyles, cellInnerClasses, columnConfig.getColumnTextStyle(), rv);
          }

        }
        trBuilder.append(tdContent);
      }

      if (enableRowBody) {
        String cls = styles.dataTable() + " x-grid-resizer";

        SafeHtmlBuilder sb = new SafeHtmlBuilder();
        sb.append(tpls.tr("", trBuilder.toSafeHtml()));
        sb.appendHtmlConstant("<tr class='" + rowBodyRow + "'><td colspan=" + rowBodyColSpanCount + "><div class='"
            + rowBody + "'></div></td></tr>");

        SafeHtml tdWrap = null;
        if (!selectable && GXT.isIE()) {
          tdWrap = tpls.tdWrapUnselectable(colCount, "", rowWrap,
              tpls.table(cls, rowStyles, sb.toSafeHtml(), renderHiddenHeaders(columnWidths)));
        } else {
          tdWrap = tpls.tdWrap(colCount, "", rowWrap,
              tpls.table(cls, rowStyles, sb.toSafeHtml(), renderHiddenHeaders(columnWidths)));
        }
        buf.append(tpls.tr(rowClasses, tdWrap));

      } else {
        buf.append(tpls.tr(rowClasses, trBuilder.toSafeHtml()));
      }

    }
    // end row loop
    return buf.toSafeHtml();

  }

  /**
   * Defaults to assume one sort at a time.
   *
   * @param colIndex the column to sort
   * @param sortDir the sort direction
   */
  protected void doSort(int colIndex, SortDir sortDir) {
    ColumnConfig<M, ?> column = cm.getColumn(colIndex);
    if (!isRemoteSort()) {
      ds.clearSortInfo();

      StoreSortInfo<M> s = createStoreSortInfo(column, sortDir);

      if (sortDir == null && storeSortInfo != null && storeSortInfo.getValueProvider().getPath().equals(column.getValueProvider().getPath())) {
        s.setDirection(storeSortInfo.getDirection() == SortDir.ASC ? SortDir.DESC : SortDir.ASC);
      } else if (sortDir == null) {
        s.setDirection(SortDir.ASC);
      }

      if (GWT.isProdMode()) {
        ds.addSortInfo(s);
      } else {
        try {
          // addSortInfo will apply its sort when called, which might trigger an
          // exception if the column passed in's data isn't Comparable
          ds.addSortInfo(s);
        } catch (ClassCastException ex) {
          GWT.log("Column can't be sorted " + column.getValueProvider().getPath()
              + " is not Comparable, and no Comparator was set for that column. ", ex);
          throw ex;
        }
      }

    } else {
      ValueProvider<? super M, ?> vp = column.getValueProvider();

      SortInfoBean bean = new SortInfoBean(vp, sortDir);

      if (sortDir == null && sortState != null && vp.getPath().equals(sortState.getSortField())) {
        bean.setSortDir(sortState.getSortDir() == SortDir.ASC ? SortDir.DESC : SortDir.ASC);

      } else if (sortDir == null) {
        bean.setSortDir(SortDir.ASC);
      }

      grid.getLoader().clearSortInfo();
      grid.getLoader().addSortInfo(bean);
      grid.getLoader().load();
    }
  }

  /**
   * Distribute the width of the columns amongst the available grid width as required by {@link #setAutoFill(boolean)}
   * and {@link #setForceFit(boolean)} .
   *
   * @param preventRefresh true to perform calculations and update column models but do not update display
   * @param onlyExpand unused in <code>GridView</code> implementation
   * @param omitColumn index of column to exclude from operation
   */
  // TODO: Consider removing unused parameter onlyExpand or adding support for
  // it
  protected void fitColumns(boolean preventRefresh, boolean onlyExpand, int omitColumn) {
    int tw = getTotalWidth();
    int aw = grid.getElement().getWidth(true) - getScrollAdjust();
    if (aw <= 0) {
      aw = grid.getElement().getComputedWidth();
    }

    if (aw < 20 || aw > 2000) { // not initialized, so don't screw up the
      // default widths
      return;
    }

    int extra = (int) aw - tw;

    if (extra == 0) {
      return;
    }

    int colCount = cm.getColumnCount();
    Stack<Integer> cols = new Stack<Integer>();
    int width = 0;
    int w;

    for (int i = 0; i < colCount; i++) {
      w = cm.getColumnWidth(i);
      if (!cm.isHidden(i) && !cm.isFixed(i) && i != omitColumn) {
        cols.push(i);
        cols.push(w);
        width += w;
      }
    }

    double frac = ((double) (extra)) / width;
    while (cols.size() > 0) {
      w = cols.pop();
      int i = cols.pop();
      int ww = Math.max(header.getMinColumnWidth(), (int) Math.floor(w + w * frac));
      cm.setColumnWidth(i, ww, true);
    }

    tw = getTotalWidth();
    if (tw > aw) {
      width = 0;
      for (int i = 0; i < colCount; i++) {
        w = cm.getColumnWidth(i);
        if (!cm.isHidden(i) && !cm.isFixed(i) && w > header.getMinColumnWidth()) {
          cols.push(i);
          cols.push(w);
          width += w;
        }
      }
      frac = ((double) (aw - tw)) / width;
      while (cols.size() > 0) {
        w = cols.pop();
        int i = cols.pop();
        int ww = Math.max(header.getMinColumnWidth(), (int) Math.floor(w + w * frac));
        cm.setColumnWidth(i, ww, true);
      }
    }

    if (!preventRefresh) {
      updateAllColumnWidths();
    }
  }

  /**
   * Gets the properties required for rendering the columns.
   *
   * @return a list of the grid's column properties
   */
  protected List<ColumnData> getColumnData() {
    int colCount = cm.getColumnCount();
    List<ColumnData> cs = new ArrayList<ColumnData>();
    for (int i = 0; i < colCount; i++) {
      ColumnData data = new ColumnData();
      data.setStyles(getColumnStyle(i, false));
      cs.add(data);
    }
    return cs;
  }

  /**
   * Returns the CSS styles for the given column.
   *
   * @param colIndex the column index
   * @param isHeader true to include the column header styles
   * @return the styles
   */
  protected SafeStyles getColumnStyle(int colIndex, boolean isHeader) {
    SafeStylesBuilder builder = new SafeStylesBuilder();
    if (!isHeader) {
      SafeStyles columnStyles = cm.getColumnStyles(colIndex);
      if (columnStyles != null) {
        builder.append(columnStyles);
      }
    }

    HorizontalAlignmentConstant align = cm.getColumnAlignment(colIndex);
    if (align != null) {
      builder.append(SafeStylesUtils.fromTrustedString("text-align:" + align.getTextAlignString() + ";"));
    }
    return builder.toSafeStyles();
  }

  /**
   * Returns the width of the given column
   *
   * @param col the column index
   * @return the column width
   */
  protected int getColumnWidth(int col) {
    return cm.getColumnWidth(col);
  }

  protected int[] getColumnWidths() {
    int colCount = cm.getColumnCount();
    int[] columnWidths = new int[colCount];
    for (int i = 0; i < colCount; i++) {
      columnWidths[i] = getColumnWidth(i);
    }
    return columnWidths;
  }

  /**
   * Returns the offset width of the grid including the total visible column width and the amount required or reserved
   * for the vertical scroll bar.
   *
   * @return the grid's offset width
   */
  protected int getOffsetWidth() {
    return (getTotalWidth() + getScrollAdjust());
  }

  /**
   * Renders the value of a cell into safe HTML.
   *
   * @param rowIndex the row index
   * @param colIndex the column index
   * @param m the data model
   * @param record the optional {@link Record} for this row (may be null)
   * @return the safe HTML representing the cell
   */
  protected <N> SafeHtml getRenderedValue(int rowIndex, int colIndex, M m, ListStore<M>.Record record) {
    ValueProvider<? super M, N> valueProvider = cm.getValueProvider(colIndex);
    N val = null;
    if (record != null) {
      val = record.getValue(valueProvider);
    } else {
      val = valueProvider.getValue(m);
    }
    Cell<N> r = cm.getCell(colIndex);
    if (r != null) {
      SafeHtmlBuilder sb = new SafeHtmlBuilder();
      r.render(new Context(rowIndex, colIndex, ds.getKeyProvider().getKey(m)), val, sb);
      return sb.toSafeHtml();
    }

    String text = null;
    if (val != null) {
      text = val.toString();
    }
    return Util.isEmptyString(text) ? SafeHtmlUtils.fromTrustedString("&#160;") : SafeHtmlUtils.fromString(text);
  }

  /**
   * Returns the HTML elements representing the body of the table.
   *
   * @return the HTML elements representing the rows in the table (empty if the table has no rows)
   */
  protected NodeList<Element> getRows() {
    if (!hasRows()) {
      return JavaScriptObject.createArray().cast();
    }
    return appearance.getRows(body);
  }

  /**
   * Returns the number of pixels required or reserved for the vertical scroll bar.
   *
   * @return the nominal width of the vertical scroll bar
   */
  protected int getScrollAdjust() {
    return adjustForHScroll ? (scroller != null ? (vbar ? scrollOffset + 1 : 2) : scrollOffset) : scrollOffset;
  }

  /**
   * The total width of the visible columns in the grid (for the width including the vertical scroll bar, see
   * {@link #getOffsetWidth()}.
   *
   * @return the total width of the columns in the grid.
   */
  protected int getTotalWidth() {
    return cm.getTotalWidth();
  }

  /**
   * Handles browser events of interest to the grid view. The default implementation for {@link GridView} includes
   * support for mouse-over tracking (see {@link GridView#setTrackMouseOver(boolean)} and scroll bar synchronization.
   *
   * @param event the browser event
   */
  protected void handleComponentEvent(Event event) {
    Element row = Element.is(event.getEventTarget()) ? findRow((Element) event.getEventTarget().cast()) : null;

    switch (event.getTypeInt()) {
      case Event.ONMOUSEMOVE:

        if (overRow != null && row == null) {
          onRowOut(overRow);
        } else if (row != null && overRow != row) {
          if (overRow != null) {
            onRowOut(overRow);
          }
          onRowOver(row);
        }

        break;

      case Event.ONMOUSEOVER:
        EventTarget from = event.getRelatedEventTarget();
        if (from == null || (Element.is(from) && !grid.getElement().isOrHasChild(Element.as(from)))) {
          Element r = null;
          if (Element.is(event.getEventTarget())) {
            r = findRow(Element.as(event.getEventTarget()));
          }
          if (r != null) {
            onRowOver(r);
          }
        }
        break;
      case Event.ONMOUSEOUT:
        EventTarget to = event.getRelatedEventTarget();
        if (to == null || (Element.is(to) && !grid.getElement().isOrHasChild(Element.as(to)))) {
          if (overRow != null) {
            onRowOut(overRow);
          }
        }
        break;
      case Event.ONMOUSEDOWN:
        onMouseDown(event);
        break;
      case Event.ONSCROLL:
        if (scroller.isOrHasChild(Element.as(event.getEventTarget()))) {
          syncScroll();
        }
        break;
    }
    if (!trackMouseOver && overRow != null) {
      trackMouseOver = true;
      onRowOut(overRow);
      trackMouseOver = false;
    }

    if (event.getTypeInt() == Event.ONSCROLL) {
      if (scroller.isOrHasChild(Element.as(event.getEventTarget()))) {
        syncScroll();
      }
    }
  }

  /**
   * Returns true if the grid has rows.
   *
   * @return true if the grid has rows.
   */
  protected boolean hasRows() {
    if (dataTable == null || dataTableBody == null || dataTableBody.getChildCount() == 0) {
      return false;
    }

    Element emptyRowElement = dataTableBody.getFirstChildElement();
    if (emptyRowElement == null) {
      return false;
    }
    emptyRowElement = emptyRowElement.getFirstChildElement();
    if (emptyRowElement == null) {
      return false;
    }
    emptyRowElement = emptyRowElement.getFirstChildElement();
    if (emptyRowElement == null) {
      return false;
    }
    return !emptyRowElement.getClassName().equals(styles.empty());
  }

  /**
   * Initializes the view.
   *
   * @param grid the grid
   */
  protected void init(final Grid<M> grid) {
    this.grid = grid;
    this.cm = grid.getColumnModel();
    selectable = grid.isAllowTextSelection();

    initListeners();

    grid.getElement().addClassName(appearance.styles().grid());

    grid.getElement().setClassName(styles.columnLines(), columnLines);

    initData(grid.getStore(), cm);
    initUI(grid);

    initHeader();

    grid.addHeaderClickHandler(new HeaderClickHandler() {
      @Override
      public void onHeaderClick(HeaderClickEvent event) {
        GridView.this.onHeaderClick(event.getColumnIndex());
      }
    });

    header.addResizeHandler(new ResizeHandler() {
      @Override
      public void onResize(ResizeEvent event) {
        resize(); // updates scroller
      }
    });

    if (cm.getAggregationRows().size() > 0) {
      footer = new ColumnFooter<M>(grid, cm);
    }

    renderUI();
    grid.sinkEvents(Event.ONCLICK | Event.ONDBLCLICK | Event.MOUSEEVENTS);
  }

  public void setColumnHeader(ColumnHeader<M> columnHeader) {
    // check if we've been assigned a grid instance yet via init, if so, we've already wired up and
    // attached our own header instance
    if (grid != null) {
      throw new IllegalStateException("Can't set a new ColumnHeader after the grid has been rendered");
    }
    this.header = columnHeader;
  }

  /**
   * Initializes the column header and saves reference for future use, creating one if it hasn't yet been set
   */
  protected void initHeader() {
    if (header == null) {
      header = new ColumnHeader<M>(grid, cm);
    }
    header.setMenuFactory(new HeaderContextMenuFactory() {
      @Override
      public Menu getMenuForColumn(int columnIndex) {
        return createContextMenu(columnIndex);
      }
    });
    header.setSplitterWidth(splitterWidth);
  }

  /**
   * Initializes the data.
   *
   * @param ds the data store
   * @param cm the column model
   */
  protected void initData(ListStore<M> ds, ColumnModel<M> cm) {
    if (storeHandlerRegistration != null) {
      storeHandlerRegistration.removeHandler();
      storeHandlerRegistration = null;
    }
    if (ds != null) {
      storeHandlerRegistration = ds.addStoreHandlers(listener);
    }
    this.ds = ds;

    if (cmHandlerRegistration != null) {
      cmHandlerRegistration.removeHandler();
      cmHandlerRegistration = null;
    }
    if (cm != null) {
      cmHandlerRegistration = cm.addColumnModelHandlers(columnListener);
    }
    this.cm = cm;

    if (this.header != null) {
      this.header.setColumnModel(cm);
    }
  }

  /**
   * Collects references to the HTML elements of the grid view and saves them in instance variables for future
   * reference.
   */
  protected void initElements() {
    NodeList<Node> cs = grid.getElement().getChildNodes();

    // headerWrap = (XElement) cs.getItem(0);
    // headerInner = headerWrap.getFirstChildElement().cast();

    scroller = (XElement) cs.getItem(1);
    scroller.addEventsSunk(Event.ONSCROLL);

    if (forceFit) {
      scroller.getStyle().setOverflowX(Overflow.HIDDEN);
    }

    body = scroller.getFirstChildElement().cast();

    dataTable = body.getFirstChildElement().cast();
    dataTableSizingHead = dataTable.getFirstChildElement().cast();
    dataTableBody = dataTableSizingHead.getNextSiblingElement().cast();

    focusEl = (XElement) scroller.appendChild(focusImpl.createFocusable());
    focusEl.addClassName(CommonStyles.get().noFocusOutline());
    if (focusEl.hasChildNodes()) {
      focusEl.getFirstChildElement().addClassName(CommonStyles.get().noFocusOutline());
      com.google.gwt.dom.client.Style focusElStyle = focusEl.getFirstChildElement().getStyle();
      focusElStyle.setBorderWidth(0, Unit.PX);
      focusElStyle.setFontSize(1, Unit.PX);
      focusElStyle.setPropertyPx("lineHeight", 1);
    }
    focusEl.setLeft(0);
    focusEl.setTop(0);
    focusEl.makePositionable(true);
    focusEl.addEventsSunk(Event.FOCUSEVENTS);
  }

  /**
   * Creates the grid view listeners, including {@link StoreHandlers} and {@link ColumnModelHandlers}, and saves
   * references for future use.
   */
  protected void initListeners() {
    listener = new StoreHandlers<M>() {

      @Override
      public void onAdd(StoreAddEvent<M> event) {
        GridView.this.onAdd(event.getItems(), event.getIndex());

      }

      @Override
      public void onClear(StoreClearEvent<M> event) {
        GridView.this.onClear(event);

      }

      @Override
      public void onDataChange(StoreDataChangeEvent<M> event) {
        GridView.this.onDataChanged(event);

      }

      @Override
      public void onFilter(StoreFilterEvent<M> event) {
        GridView.this.onDataChanged(null);
      }

      @Override
      public void onRecordChange(StoreRecordChangeEvent<M> event) {
        GridView.this.onUpdate(ds, Collections.singletonList(event.getRecord().getModel()));
      }

      @Override
      public void onRemove(StoreRemoveEvent<M> event) {
        GridView.this.onRemove(event.getItem(), event.getIndex(), false);

      }

      @Override
      public void onSort(StoreSortEvent<M> event) {
        GridView.this.onDataChanged(null);

      }

      @Override
      public void onUpdate(StoreUpdateEvent<M> event) {
        GridView.this.onUpdate(ds, event.getItems());
      }

    };

    columnListener = new ColumnModelHandlers() {
      @Override
      public void onColumnHeaderChange(ColumnHeaderChangeEvent event) {
        GridView.this.onHeaderChange(event.getIndex(), cm.getColumnHeader(event.getIndex()));

      }

      @Override
      public void onColumnHiddenChange(ColumnHiddenChangeEvent event) {
        GridView.this.onHiddenChange(cm, event.getIndex(), cm.isHidden(event.getIndex()));

      }

      @Override
      public void onColumnMove(ColumnMoveEvent event) {
        GridView.this.onColumnMove(event.getIndex());

      }

      @Override
      public void onColumnWidthChange(ColumnWidthChangeEvent event) {
        GridView.this.onColumnWidthChange(event.getIndex(), cm.getColumnWidth(event.getIndex()));

      }
    };
  }

  /**
   * Invoked to perform additional initialization of the grid view's user interface, after the data has been
   * initialized, the default implementation for {@link GridView} does nothing.
   *
   * @param grid the grid for this grid view
   */
  protected void initUI(final Grid<M> grid) {

  }

  /**
   * Inserts the given rows (already present in the grid's list store) into the grid view.
   *
   * @param firstRow the first row index
   * @param lastRow the last row index
   * @param isUpdate true if update to existing rows
   */
  protected void insertRows(int firstRow, int lastRow, boolean isUpdate) {
    if (lastRow < firstRow) {
      return;
    }

    if (!hasRows()) {
      if (GXT.isIE()) {
        dataTableBody.removeChildren();
      } else {
        dataTableBody.setInnerHTML("");
      }
    }

    SafeHtml html = renderRows(firstRow, lastRow);
    XElement before = getRow(firstRow).cast();

    if (before != null) {
      DomHelper.insertBefore(before, html.asString());
    } else {
      DomHelper.insertHtml("beforeEnd", dataTableBody, html.asString());
    }
    if (!isUpdate) {
      processRows(firstRow, false);
    }
  }

  /**
   * Return true if configured for remote sorting.
   *
   * @return if configured for remote sorting.
   */
  protected boolean isRemoteSort() {
    return grid.getLoader() != null && grid.getLoader().isRemoteSort();
  }

  /**
   * Lays out the grid view, adjusting the header and footer width and accounting for force fit and auto fill settings.
   *
   * @param skipResize true to skip resizing of the grid view
   */
  protected void layout(boolean skipResize) {
    if (body == null) {
      return;
    }

    XElement c = grid.getElement();
    Size csize = c.getStyleSize();

    if (GXTLogConfiguration.loggingIsEnabled()) {
      logger.finest("layout() " + csize);
    }

    int vw = csize.getWidth();
    if (vw < 10 || (csize.getHeight() < 20 && !grid.isHideHeaders())) {
      return;
    }

    if (!skipResize) {
      resize();
    }

    if (forceFit || autoFill) {
      if (lastViewWidth != vw) {
        fitColumns(false, false, -1);
        header.updateTotalWidth(getOffsetWidth(), getTotalWidth());
        if (footer != null) {
          footer.updateTotalWidth(getOffsetWidth(), getTotalWidth());
        }
        lastViewWidth = vw;
      }
    } else {
      autoExpand(false);
      header.updateTotalWidth(getOffsetWidth(), getTotalWidth());
      if (footer != null) {
        footer.updateTotalWidth(getOffsetWidth(), getTotalWidth());
      }
      syncHeaderScroll();
    }

  }

  /**
   * Invoked after the grid has been hidden, the default implementation for {@link GridView} does nothing.
   */
  protected void notifyHide() {
  }

  /**
   * Invoked after the grid has been shown, the default implementation for {@link GridView} does nothing.
   */
  protected void notifyShow() {
  }

  /**
   * Handles adding new data models to the store.
   *
   * @param models the new data models
   * @param index the index of the first model
   */
  protected void onAdd(List<M> models, int index) {
    if (grid != null && grid.isViewReady()) {
      insertRows(index, index + (models.size() - 1), false);
      addTask.delay(10);
    }
  }

  /**
   * Handles the clearing of the selection for the given cell.
   *
   * @param row the row index
   * @param col the cell index
   */
  protected void onCellDeselect(int row, int col) {
    Element cell = getCell(row, col);
    if (cell != null) {
      appearance.onCellSelect(cell, false);
      cell.removeClassName(states.cellSelected());
    }
  }

  /**
   * Handles selecting the given cell.
   *
   * @param row the row index
   * @param col the cell index
   */
  protected void onCellSelect(int row, int col) {
    Element cell = getCell(row, col);
    if (cell != null) {
      appearance.onCellSelect(cell, true);
      cell.addClassName(states.cellSelected());
    }
  }

  /**
   * Handles clearing the store.
   *
   * @param se the event that cleared the store
   */
  protected void onClear(StoreClearEvent<M> se) {
    refresh(false);
  }

  /**
   * Handles the click event, the default implementation for {@link GridView} does nothing.
   *
   * @param ce the click event
   */
  protected void onClick(Event ce) {

  }

  /**
   * Handles the column move request.
   *
   * @param newIndex the destination column index
   */
  protected void onColumnMove(int newIndex) {
    boolean pScroll = preventScrollToTopOnRefresh;
    preventScrollToTopOnRefresh = true;
    refresh(true);
    preventScrollToTopOnRefresh = pScroll;
  }

  /**
   * Handles a change to the column model width (see {@link ColumnModel#setColumnWidth(int, int)});
   *
   * @param column the index of the column
   * @param width the new width
   */
  protected void onColumnWidthChange(int column, int width) {
    if (forceFit) {
      fitColumns(false, false, column);
      header.updateTotalWidth(getOffsetWidth(), getTotalWidth());
    } else {
      updateColumnWidth(column, width);
      header.updateTotalWidth(getOffsetWidth(), getTotalWidth());
      if (GXT.isIE()) {
        syncHeaderScroll();
      }
    }
  }

  /**
   * Handles a change in the data in the store, including changes to the filter or sort state.
   *
   * @param se the change (may be null)
   */
  protected void onDataChanged(StoreDataChangeEvent<M> se) {
    if (!grid.viewReady) return;
    refresh(false);
    if (grid != null && grid.isLoadMask()) {
      if (grid.isEnabled()) {
        grid.unmask();
      } else {
        grid.mask();
      }
    }
  }

  /**
   * Handles a change in the safe HTML that represents the header (see
   * {@link ColumnModel#setColumnHeader(int, SafeHtml)}).
   *
   * @param column the column index
   * @param text the new safe HTML
   */
  protected void onHeaderChange(int column, SafeHtml text) {
    header.setHeader(column, text);
  }

  /**
   * Handles a header click event.
   *
   * @param column the column that was clicked
   */
  protected void onHeaderClick(int column) {
    this.headerColumnIndex = column;
    if (!headerDisabled && cm.isSortable(column)) {
      doSort(column, null);
    }
  }

  /**
   * Handles a change in the column model's hidden state (see {@link ColumnModel#setHidden(int, boolean)}).
   *
   * @param cm the column model
   * @param col the column index
   * @param hidden true if the column is hidden
   */
  protected void onHiddenChange(ColumnModel<M> cm, int col, boolean hidden) {
    updateColumnHidden(col, hidden);
  }

  /**
   * Handles a request to change the highlight state of a row.
   *
   * @param rowIndex the row index
   * @param highlight true to highlight the row
   */
  protected void onHighlightRow(int rowIndex, boolean highlight) {
    Element row = getRow(rowIndex);
    if (row != null) {
      appearance.onRowHighlight(row, highlight);
    }
  }

  /**
   * Invoked when a mouse down event occurs, the default implementation for {@link GridView} does nothing.
   */
  protected void onMouseDown(Event ge) {

  }

  /**
   * Called with key down is pressed while on last row.
   *
   * @param index the index of the last row
   */
  protected void onNoNext(int index) {

  }

  /**
   * Called when key up is pressed while on first row.
   */
  protected void onNoPrev() {

  }

  /**
   * Handles removing a data model from the store.
   *
   * @param m the data model
   * @param index the row index
   * @param isUpdate true to indicate an update an existing row
   */
  protected void onRemove(M m, int index, boolean isUpdate) {
    if (grid != null && grid.isViewReady()) {
      removeRow(index);
      if (!isUpdate) {
        removeTask.delay(10);
      } else {
        removeTask.delay(0);
      }
      constrainFocusElement();
    }
  }

  /**
   * Handles clearing the selection on a row.
   *
   * @param rowIndex the row index
   */
  protected void onRowDeselect(int rowIndex) {
    Element row = getRow(rowIndex);
    if (row != null) {
      appearance.onRowSelect(row, false);
      appearance.onRowHighlight(row, false);
      row.removeClassName(states.rowSelected());
    }
  }

  /**
   * Handles moving the mouse off a row.
   *
   * @param row the HTML element for the row
   */
  protected void onRowOut(Element row) {
    if (isTrackMouseOver()) {
      if (overRow != null && overRow != row) {
        appearance.onRowOver(overRow, false);
      }
      appearance.onRowOver(row, false);
      overRow = null;
    }
  }

  /**
   * Handles moving the mouse onto a row.
   *
   * @param row the HTML element for the row
   */
  protected void onRowOver(Element row) {
    if (isTrackMouseOver()) {
      appearance.onRowOver(row, true);
      overRow = row;
    }
  }

  /**
   * Handles setting the selection on a row.
   *
   * @param rowIndex the row index
   */
  protected void onRowSelect(int rowIndex) {
    Element row = getRow(rowIndex);
    if (row != null) {
      onRowOut(row);
      appearance.onRowSelect(row, true);
      row.addClassName(states.rowSelected());
    }
  }

  /**
   * Handles an update to data in the store.
   *
   * @param store the store
   * @param models the updated data
   */
  protected void onUpdate(final ListStore<M> store, final List<M> models) {
    if (!deferUpdates) {
      for (M m : models) {
        refreshRow(store.indexOf(m));
      }
    } else {
      Timer t = new Timer() {
        @Override
        public void run() {
          for (M m : models) {
            refreshRow(store.indexOf(m));
            grid.getSelectionModel().onUpdate(m);
          }
        }
      };
      t.schedule(deferUpdateDelay);
    }
  }

  /**
   * Makes a pass through the rows in the grid to finalize the appearance, the default implementation in
   * {@link GridView} assigns the row index property and stripes the rows (if striping is enabled).
   *
   * @param startRow the row index
   * @param skipStripe true to prevent striping (striping is always prevented if {@link GridView#isStripeRows()} returns
   *          false).
   */
  protected void processRows(int startRow, boolean skipStripe) {
    if (ds.size() < 1) {
      return;
    }
    skipStripe = skipStripe || !isStripeRows();
    NodeList<Element> rows = getRows();
    String cls = styles.rowAlt();
    for (int i = startRow, len = rows.getLength(); i < len; i++) {
      Element row = rows.getItem(i);
      row.setPropertyInt("rowindex", i);
      if (!skipStripe) {
        boolean isAlt = (i + 1) % 2 == 0;
        boolean hasAlt = row.getClassName() != null && row.getClassName().indexOf(cls) != -1;
        if (isAlt == hasAlt) {
          continue;
        }
        if (isAlt) {
          row.addClassName(cls);
        } else {
          row.removeClassName(cls);
        }
      }
    }
  }

  /**
   * Refreshes the displayed content for the given row.
   *
   * @param row the row index
   */
  protected void refreshRow(int row) {
    if (grid != null && grid.isViewReady()) {
      M m = ds.get(row);
      if (m != null) {
        // do not change focus on refresh
        // handles situation with changing cell value with field binding
        focusEnabled = false;

        insertRows(row, row, true);
        getRow(row).setPropertyInt("rowindex", row);
        onRemove(m, row + 1, true);

        focusEnabled = true;
      }
    }
  }

  /**
   * Removes the given row.
   *
   * @param row the row index
   */
  protected void removeRow(int row) {
    Element r = getRow(row);
    if (r != null) {
      r.removeFromParent();
    }
  }

  /**
   * Renders the footer.
   */
  protected void renderFooter() {
    footer.setAllowTextSelection(false);

    grid.getElement().appendChild(footer.getElement());
    footer.refresh();
  }

  /**
   * Renders the header.
   */
  protected void renderHeader() {
    headerElem = header.getElement();
    grid.getElement().insertFirst(headerElem);
    header.refresh();
    if (grid.isHideHeaders()) {
      headerElem.setVisible(false);
    }
  }

  /**
   * Renders the hidden TH elements that keep the column widths.
   *
   * @param columnWidths the column widths
   * @return markup representing the hidden table header elements
   */
  protected SafeHtml renderHiddenHeaders(int[] columnWidths) {
    SafeHtmlBuilder heads = new SafeHtmlBuilder();
    for (int i = 0; i < columnWidths.length; i++) {
      int w = cm.isHidden(i) ? 0 : columnWidths[i];
      SafeStylesBuilder builder = new SafeStylesBuilder();
      builder.appendTrustedString("height: 0px;");
      builder.appendTrustedString("width:" + w + "px;");
      heads.append(tpls.th("", builder.toSafeStyles()));
    }
    return tpls.tr(appearance.styles().headerRow(), heads.toSafeHtml());
  }

  /**
   * Renders the grid's rows.
   *
   * @param startRow the index in the store of the first row to render
   * @param endRow the index of the last row to render (may be -1 to indicate all rows)
   * @return safe HTML representing the rendered rows
   */
  protected SafeHtml renderRows(int startRow, int endRow) {
    if (ds.size() < 1) {
      return SafeHtmlUtils.EMPTY_SAFE_HTML;
    }

    List<ColumnData> cs = getColumnData();

    if (endRow == -1) {
      endRow = ds.size() - 1;
    }

    List<M> rs = ds.subList(startRow, ++endRow);
    return doRender(cs, rs, startRow);
  }

  /**
   * Responsible for rendering all aspects of the grid view.
   */
  protected void renderUI() {
    renderHeader();

    initElements();

    DomHelper.insertHtml("afterBegin", dataTableSizingHead, renderHiddenHeaders(getColumnWidths()).asString());

    header.setEnableColumnResizing(grid.isColumnResize());
    header.setEnableColumnReorder(grid.isColumnReordering());

    if (footer != null) {
      renderFooter();
    }

    updateHeaderSortState();
  }

  protected void repaintGrid() {
    dataTable.getStyle().setProperty("display", "block");

    Scheduler.get().scheduleFinally(new ScheduledCommand() {

      @Override
      public void execute() {
        dataTable.getStyle().clearDisplay();
      }
    });
  }

  /**
   * Resizes the grid view, adjusting the scroll bars and accounting for the footer height (if any).
   */
  protected void resize() {
    if (body == null) {
      return;
    }

    Size csize = grid.getElement().getStyleSize();

    if (GXTLogConfiguration.loggingIsEnabled()) {
      logger.finest("resize() " + csize);
    }

    int vw = csize.getWidth();
    int vh = 0;
    if (vw < 10 || csize.getHeight() < 22) {
      return;
    }

    if (grid.isAutoHeight()) {
      scroller.setWidth(vw);
    }

    int hdHeight = headerElem.getHeight(false);
    vh = csize.getHeight() - hdHeight;

    if (footer != null) {
      vh -= footer.getOffsetHeight();
    }

    if (!grid.isAutoHeight()) {
      scroller.setSize(vw, vh);
    }

    if (headerElem != null) {
      headerElem.setWidth(vw);
    }
    if (footer != null) {
      footer.setWidth(vw);
    }

    constrainFocusElement();
  }

  /**
   * Restores the scroll state.
   *
   * @param state the state as returned from a previous call to {@link #getScrollState()}.
   */
  protected void restoreScroll(Point state) {
    if (state.getY() < scroller.getWidth(false)) {
      scroller.setScrollLeft(state.getX());
    }
    if (state.getX() < scroller.getHeight(false)) {
      scroller.setScrollTop(state.getY());
    }
  }

  /**
   * Synchronizes the header position (and footer, if present) with the horizontal scroll bar.
   */
  protected void syncHeaderScroll() {
    int sl = scroller.getScrollLeft();
    headerElem.setScrollLeft(sl);
    // second time for IE (1/2 time first fails, other browsers ignore)
    headerElem.setScrollLeft(sl);

    if (footer != null) {
      footer.getElement().setScrollLeft(sl);
      footer.getElement().setScrollLeft(sl);
    }
  }

  /**
   * Synchronizes the grid scroll bars.
   */
  protected void syncScroll() {
    syncHeaderScroll();
    int scrollLeft = scroller.getScrollLeft();
    int scrollTop = scroller.getScrollTop();
    constrainFocusElement();
    grid.fireEvent(new BodyScrollEvent(scrollLeft, scrollTop));
  }

  /**
   * Invoked after all column widths have been updated, the default implementation for {@link GridView} does nothing.
   */
  protected void templateOnAllColumnWidthsUpdated(int[] columnWidths, int tw) {

  }

  /**
   * Invoked after the hidden column status been updated, the default implementation for {@link GridView} does nothing.
   */
  protected void templateOnColumnHiddenUpdated(int col, boolean hidden, int tw) {
    // template method
  }

  /**
   * Invoked after a column width has been updated, the default implementation for {@link GridView} does nothing.
   */
  protected void templateOnColumnWidthUpdated(int col, int w, int tw) {
    // template method
  }

  /**
   * Synchronizes the displayed width of each column with the defined width of each column from its column model.
   */
  protected void updateAllColumnWidths() {
    int tw = getTotalWidth();
    int clen = cm.getColumnCount();

    int[] columnWidths = new int[clen];
    for (int i = 0; i < clen; i++) {
      columnWidths[i] = cm.isHidden(i) ? 0 : getColumnWidth(i);
    }

    adjustColumnWidths(columnWidths);

    templateOnAllColumnWidthsUpdated(columnWidths, tw);
  }

  /**
   * Updates the row width and cell display properties to hide or show the given column.
   *
   * @param index the column index
   * @param hidden true to hide the column
   */
  protected void updateColumnHidden(int index, boolean hidden) {
    int tw = getTotalWidth();

    header.updateColumnHidden(index, hidden);
    if (footer != null) {
      footer.updateTotalWidth(getOffsetWidth(), tw);
      footer.updateColumnHidden(index, hidden);
    }

    NodeList<Element> tables = scroller.select("." + appearance.styles().dataTable());

    for (int t = 0, len = tables.getLength(); t < len; t++) {
      XElement table = tables.getItem(t).cast();

      table.getStyle().setWidth(getTotalWidth(), Unit.PX);

      NodeList<Element> ths = getTableHeads(table);
      if (ths == null) {
        continue;
      }

      if (index < ths.getLength()) {
        ths.getItem(index).getStyle().setPropertyPx("width", hidden ? 0 : getColumnWidth(index));
      }
    }

    dataTable.getStyle().setWidth(tw, Unit.PX);

    // cell widths incorrect
    if (GXT.isIE() || GXT.isSafari()) {
      repaintGrid();
    }

    lastViewWidth = -1;
    layout();

    templateOnColumnHiddenUpdated(index, hidden, tw);
  }

  /**
   * Updates the column width to the given value, which should have previously been stored in the column model.
   *
   * @param col the column index
   * @param width the width of the column
   */
  protected void updateColumnWidth(int col, int width) {
    int tw = getTotalWidth();
    int w = getColumnWidth(col);

    header.updateTotalWidth(-1, tw);
    header.updateColumnWidth(col, width);

    if (footer != null) {
      footer.updateTotalWidth(getOffsetWidth(), tw);
      footer.updateColumnWidth(col, width);
    }

    int clen = cm.getColumnCount();
    int[] columnWidths = new int[clen];
    for (int i = 0; i < clen; i++) {
      columnWidths[i] = cm.isHidden(i) ? 0 : getColumnWidth(i);
    }
    adjustColumnWidths(columnWidths);

    templateOnColumnWidthUpdated(col, w, tw);
  }

  /**
   * Update the header to reflect any changes in the sort state.
   */
  protected void updateHeaderSortState() {
    if (!isRemoteSort()) {
      StoreSortInfo<M> info = getSortState();
      if (info != null) {
        ValueProvider<? super M, ?> vp = info.getValueProvider();
        if (vp != null) {
          String p = vp.getPath();
          if (p != null && !"".equals(p)) {
            ColumnConfig<M, ?> config = cm.findColumnConfig(p);
            if (config != null) {
              if (storeSortInfo == null || (!p.equals(storeSortInfo.getPath()))
                  || storeSortInfo.getDirection() != info.getDirection()) {
                int index = cm.indexOf(config);
                if (index != -1) {
                  updateSortIcon(index, info.getDirection());
                }
                grid.fireEvent(new SortChangeEvent(new SortInfoBean(info.getPath(), info.getDirection())));
              }

            }
          }
          storeSortInfo = info;
        }

      }
    } else {
      List<? extends SortInfo> infos = grid.getLoader().getSortInfo();
      if (infos.size() > 0) {
        SortInfo info = infos.get(0);
        String p = info.getSortField();
        if (p != null && !"".equals(p)) {
          ColumnConfig<M, ?> config = cm.findColumnConfig(p);
          if (config != null) {
            if (sortState == null || (!sortState.getSortField().equals(p))
                || sortState.getSortDir() != info.getSortDir()) {
              int index = cm.indexOf(config);
              if (index != -1) {
                updateSortIcon(index, info.getSortDir());
              }
              grid.fireEvent(new SortChangeEvent(info));
            }
          }
          sortState = info;
        }
      }
    }
  }

  /**
   * Updates the sort icon for the given column and sort direction.
   *
   * @param colIndex the column index
   * @param dir the sort direction
   */
  protected void updateSortIcon(int colIndex, SortDir dir) {
    header.updateSortIcon(colIndex, dir);
  }

  private void constrainFocusElement() {
    if (!focusConstrainScheduled) {
      focusConstrainScheduled = true;
      Scheduler.get().scheduleFinally(new ScheduledCommand() {
        @Override
        public void execute() {
          focusConstrainScheduled = false;
          int scrollLeft = scroller.getScrollLeft();
          int scrollTop = scroller.getScrollTop();
          int left = scroller.getWidth(true) / 2 + scrollLeft;
          int top = scroller.getHeight(true) / 2 + scrollTop;
          focusEl.setLeftTop(left, top);
        }
      });
    }
  }

  private SimpleEventBus ensureHandlers() {
    return eventBus == null ? eventBus = new SimpleEventBus() : eventBus;
  }

  private NodeList<Element> getTableHeads(Element table) {
    // tbody
    table = table.getFirstChildElement();
    if (table == null) return null;

    // tr
    table = table.getFirstChildElement();
    if (table == null) return null;

    return table.getChildNodes().cast();
  }

  private void refreshFooterData() {
    if (footer != null) {
      footer.refresh();
    }
  }

  private void restrictMenu(ColumnModel<M> cm, Menu columns) {
    int count = 0;
    for (int i = 0, len = cm.getColumnCount(); i < len; i++) {
      if (hasHeaderValue(i)) {
        ColumnConfig<M, ?> cc = cm.getColumn(i);
        if (cc.isHidden() || !cc.isHideable()) {
          continue;
        }
        count++;
      }
    }
   
    if (count == 1) {
      for (Widget item : columns) {
        CheckMenuItem ci = (CheckMenuItem) item;
        if (ci.isChecked()) {
          ci.disable();
        }
      }
    } else {
      for (int i = 0, len = columns.getWidgetCount(); i < len; i++) {
        Widget item = columns.getWidget(i);
        ColumnConfig<M, ?> config = cm.getColumn(i);
        if (item instanceof Component && config.isHideable()) {
          ((Component) item).enable();
        }
      }
    }
  }

  private boolean hasHeaderValue(int columnIndex) {
    return cm.getColumnHeader(columnIndex) != null && !cm.getColumnHeader(columnIndex).asString().equals("") ;
  }

}
TOP

Related Classes of com.sencha.gxt.widget.core.client.grid.GridView$StateBundle

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.