org.openelis.ui.widget.table.Table.java Source code

Java tutorial

Introduction

Here is the source code for org.openelis.ui.widget.table.Table.java

Source

/**
 * Exhibit A - UIRF Open-source Based Public Software License.
 * 
 * The contents of this file are subject to the UIRF Open-source Based Public
 * Software License(the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * openelis.uhl.uiowa.edu
 * 
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 * 
 * The Original Code is OpenELIS code.
 * 
 * The Initial Developer of the Original Code is The University of Iowa.
 * Portions created by The University of Iowa are Copyright 2006-2008. All
 * Rights Reserved.
 * 
 * Contributor(s): ______________________________________.
 * 
 * Alternatively, the contents of this file marked "Separately-Licensed" may be
 * used under the terms of a UIRF Software license ("UIRF Software License"), in
 * which case the provisions of a UIRF Software License are applicable instead
 * of those above.
 */
package org.openelis.ui.widget.table;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.logging.Logger;

import org.openelis.ui.common.Util;
import org.openelis.ui.common.data.QueryData;
import org.openelis.ui.messages.Messages;
import org.openelis.ui.resources.TableCSS;
import org.openelis.ui.widget.Balloon;
import org.openelis.ui.widget.Balloon.Options;
import org.openelis.ui.widget.Balloon.Placement;
import org.openelis.ui.widget.CSSUtils;
import org.openelis.ui.widget.CheckBox;
import org.openelis.ui.widget.HasBalloon;
import org.openelis.ui.widget.HasExceptions;
import org.openelis.ui.widget.Queryable;
import org.openelis.ui.widget.ScreenWidgetInt;
import org.openelis.ui.widget.table.event.BeforeCellEditedEvent;
import org.openelis.ui.widget.table.event.BeforeCellEditedHandler;
import org.openelis.ui.widget.table.event.BeforeRowAddedEvent;
import org.openelis.ui.widget.table.event.BeforeRowAddedHandler;
import org.openelis.ui.widget.table.event.BeforeRowDeletedEvent;
import org.openelis.ui.widget.table.event.BeforeRowDeletedHandler;
import org.openelis.ui.widget.table.event.CellClickedEvent;
import org.openelis.ui.widget.table.event.CellClickedHandler;
import org.openelis.ui.widget.table.event.CellDoubleClickedEvent;
import org.openelis.ui.widget.table.event.CellEditedEvent;
import org.openelis.ui.widget.table.event.CellEditedHandler;
import org.openelis.ui.widget.table.event.CellMouseOverEvent;
import org.openelis.ui.widget.table.event.FilterEvent;
import org.openelis.ui.widget.table.event.FilterHandler;
import org.openelis.ui.widget.table.event.HasBeforeCellEditedHandlers;
import org.openelis.ui.widget.table.event.HasBeforeRowAddedHandlers;
import org.openelis.ui.widget.table.event.HasBeforeRowDeletedHandlers;
import org.openelis.ui.widget.table.event.HasCellClickedHandlers;
import org.openelis.ui.widget.table.event.HasCellEditedHandlers;
import org.openelis.ui.widget.table.event.HasFilterHandlers;
import org.openelis.ui.widget.table.event.HasRowAddedHandlers;
import org.openelis.ui.widget.table.event.HasRowDeletedHandlers;
import org.openelis.ui.widget.table.event.HasUnselectionHandlers;
import org.openelis.ui.widget.table.event.RowAddedEvent;
import org.openelis.ui.widget.table.event.RowAddedHandler;
import org.openelis.ui.widget.table.event.RowDeletedEvent;
import org.openelis.ui.widget.table.event.RowDeletedHandler;
import org.openelis.ui.widget.table.event.UnselectionEvent;
import org.openelis.ui.widget.table.event.UnselectionHandler;

import com.allen_sauer.gwt.dnd.client.drop.DropController;
import com.google.gwt.core.shared.GWT;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.FocusEvent;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.logical.shared.BeforeSelectionEvent;
import com.google.gwt.event.logical.shared.BeforeSelectionHandler;
import com.google.gwt.event.logical.shared.HasBeforeSelectionHandlers;
import com.google.gwt.event.logical.shared.HasSelectionHandlers;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.HasValue;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.IsWidget;
import com.google.gwt.user.client.ui.LayoutPanel;
import com.google.gwt.user.client.ui.RequiresResize;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Widget;

/**
 * This class is used by screens and widgets such as AutoComplete and Dropdown
 * to display information in a Table grid format
 * 
 * @author tschmidt
 * 
 */
public class Table extends FocusPanel implements ScreenWidgetInt, Queryable, HasBeforeSelectionHandlers<Integer>,
        HasSelectionHandlers<Integer>, HasUnselectionHandlers<Integer>, HasBeforeCellEditedHandlers, RequiresResize,
        HasCellEditedHandlers, HasBeforeRowAddedHandlers, HasRowAddedHandlers, HasBeforeRowDeletedHandlers,
        HasRowDeletedHandlers, HasCellClickedHandlers, HasValue<ArrayList<? extends Row>>, HasExceptions, Focusable,
        FocusHandler, HasFilterHandlers, HasBalloon {

    /**
     * Cell that is currently being edited.
     */
    protected int editingRow = -1, editingCol = -1;

    /**
     * Table dimensions
     */
    protected int rowHeight, visibleRows = 10, viewWidth = -1, totalColumnWidth;

    /**
     * Model used by the Table
     */
    protected ArrayList<Row> model, modelView, modelSort;
    protected HashMap<Object, RowIndexes> rowIndex;

    protected Timer balloonTimer;

    /**
     * Columns used by the Table
     */
    protected ArrayList<Column> columns;

    /**
     * List of selected Rows by index in the table
     */
    protected ArrayList<Integer> selections = new ArrayList<Integer>(5);

    /**
     * Exception lists for the table
     */
    protected HashMap<Row, HashMap<Integer, ArrayList<Exception>>> endUserExceptions, validateExceptions;

    /**
     * Table state values
     */
    protected boolean enabled, multiSelect, editing, hasFocus, queryMode, hasHeader, hasMenu, unitTest,
            fixScrollBar = true, ctrlDefault;

    /**
     * Enum representing the state of when the scroll bar should be shown.
     */
    public enum Scrolling {
        ALWAYS, AS_NEEDED, NEVER
    };

    /**
     * Fields to hold state of whether the scroll bars are shown
     */
    protected Scrolling verticalScroll, horizontalScroll;

    /**
     * Reference to the View composite for this widget.
     */
    protected ViewInt view;

    /**
     * Arrays for determining relative X positions for columns
     */
    protected short[] xForColumn, columnForX;

    /**
     * Drag and Drop controllers
     */
    protected TableDragController dragController;
    protected TableDropController dropController;

    protected HandlerRegistration visibleHandler;

    protected CellTipProvider tipProvider;

    protected Options toolTip;

    /**
     * Indicates direction for the Sort
     */
    public static final int SORT_ASCENDING = 1, SORT_DESCENDING = -1;

    protected Logger logger = Logger.getLogger("Widget");

    protected Table source = this;

    protected int tipRow, tipCol;

    public static class Builder {
        final int visibleRows;
        int rowHeight = 16;
        Integer width;
        boolean multiSelect, hasHeader, hasMenu, fixScroll = true;
        Scrolling verticalScroll = Scrolling.ALWAYS;
        Scrolling horizontalScroll = Scrolling.ALWAYS;
        ArrayList<Column> columns = new ArrayList<Column>(5);

        public Builder(int visibleRows) {
            this.visibleRows = visibleRows;
        }

        public Builder rowHeight(int rowHeight) {
            this.rowHeight = rowHeight;
            return this;
        }

        public Builder multiSelect(boolean multiSelect) {
            this.multiSelect = multiSelect;
            return this;
        }

        public Builder verticalScroll(Scrolling vertical) {
            this.verticalScroll = vertical;
            return this;
        }

        public Builder horizontalScroll(Scrolling horizontal) {
            this.horizontalScroll = horizontal;
            return this;
        }

        public Builder hasHeader(boolean hasHeader) {
            this.hasHeader = hasHeader;
            return this;
        }

        public Builder hasMenu(boolean hasMenu) {
            this.hasMenu = hasMenu;
            return this;
        }

        public Builder column(Column col) {
            columns.add(col);
            return this;
        }

        public Builder width(Integer width) {
            this.width = width;
            return this;
        }

        public Builder fixScrollbar(boolean fixScroll) {
            this.fixScroll = fixScroll;
            return this;
        }

        public Table build() {
            return new Table(this);
        }
    }

    public Table() {
        rowHeight = 16;
        fixScrollBar = true;
        multiSelect = false;
        columns = new ArrayList<Column>(5);
        view = new StaticView(this);
        setWidget(view);
        setKeyHandling();
    }

    public Table(Builder builder) {

        rowHeight = builder.rowHeight;
        visibleRows = builder.visibleRows;
        multiSelect = builder.multiSelect;
        verticalScroll = builder.verticalScroll;
        horizontalScroll = builder.horizontalScroll;
        fixScrollBar = builder.fixScroll;
        hasHeader = builder.hasHeader;
        hasMenu = builder.hasMenu;
        view = new StaticView(this);
        setWidget(view);

        if (builder.width != null)
            setWidth(builder.width.intValue());

        setColumns(builder.columns);
        setKeyHandling();
    }

    public void setInfiniteView() {
        view = new InfiniteView(this);
        setWidget(view);
    }

    private void setKeyHandling() {
        /*
         * This Handler takes care of all key events on the table when editing
         * and when only selection is on
         */
        addDomHandler(new KeyDownHandler() {
            public void onKeyDown(KeyDownEvent event) {
                int row, col, keyCode;

                if (!isEnabled())
                    return;
                keyCode = event.getNativeEvent().getKeyCode();
                row = editingRow;
                col = editingCol;

                if (isEditing() && getColumnAt(col).getCellEditor().ignoreKey(keyCode))
                    return;

                switch (keyCode) {
                case (KeyCodes.KEY_TAB):

                    // Ignore if no cell is currently being edited
                    if (!editing)
                        break;

                    // Tab backwards if shift pressed otherwise tab forward
                    if (!event.isShiftKeyDown()) {
                        while (true) {
                            col++;
                            if (col >= getColumnCount()) {
                                col = 0;
                                row++;
                                if (row >= getRowCount()) {
                                    setFocus(true);
                                    break;
                                }

                            }
                            if (startEditing(row, col, event.getNativeEvent())) {
                                event.preventDefault();
                                event.stopPropagation();
                                break;
                            }
                        }
                    } else {
                        while (true) {
                            col--;
                            if (col < 0) {
                                col = getColumnCount() - 1;
                                row--;
                                if (row < 0) {
                                    setFocus(true);
                                    break;
                                }
                            }
                            if (startEditing(row, col, event.getNativeEvent())) {
                                event.preventDefault();
                                event.stopPropagation();
                                break;
                            }
                        }
                    }

                    break;
                case (KeyCodes.KEY_DOWN):
                    // If Not editing select the next row below the current
                    // selection
                    if (!isEditing()) {
                        if (isAnyRowSelected()) {
                            row = getSelectedRow();
                            while (true) {
                                row++;
                                if (row >= getRowCount())
                                    break;

                                selectRowAt(row, event.getNativeEvent());

                                if (isRowSelected(row))
                                    break;
                            }
                        }
                        break;
                    }
                    // If editing set focus to the same col cell in the next
                    // selectable row below
                    while (true) {
                        row++;
                        if (row >= getRowCount())
                            break;
                        if (startEditing(row, col, event.getNativeEvent())) {
                            event.stopPropagation();
                            event.preventDefault();
                            break;
                        }
                    }
                    break;
                case (KeyCodes.KEY_UP):
                    // If Not editing select the next row above the current
                    // selection
                    if (!isEditing()) {
                        if (isAnyRowSelected()) {
                            row = getSelectedRow();
                            while (true) {
                                row--;
                                if (row < 0)
                                    break;

                                selectRowAt(row, event.getNativeEvent());

                                if (isRowSelected(row))
                                    break;
                            }
                        }
                        break;
                    }
                    // If editing set focus to the same col cell in the next
                    // selectable row above
                    while (true) {
                        row--;
                        if (row < 0)
                            break;
                        if (startEditing(row, col, event.getNativeEvent())) {
                            event.stopPropagation();
                            event.preventDefault();
                            break;
                        }
                    }
                    break;
                case (KeyCodes.KEY_ENTER):
                    // If editing just finish and return
                    if (isEditing()) {
                        finishEditing();
                        return;
                    }

                    if (getRowCount() == 0)
                        return;

                    // If not editing and a row is selected, focus on first
                    // editable cell
                    if (!isAnyRowSelected())
                        row = 0;
                    else
                        row = getSelectedRow();
                    col = 0;
                    while (col < getColumnCount()) {
                        if (startEditing(row, col, event.getNativeEvent()))
                            break;
                        col++;
                    }
                    break;
                }
            }
        }, KeyDownEvent.getType());

        addDomHandler(new BlurHandler() {
            @Override
            public void onBlur(BlurEvent event) {
                //removeStyleName(UIResources.INSTANCE.text().Focus());
            }
        }, BlurEvent.getType());

        addDomHandler(new FocusHandler() {
            @Override
            public void onFocus(FocusEvent event) {
                //addStyleName(UIResources.INSTANCE.text().Focus());
            }
        }, FocusEvent.getType());
    }

    // ********* Table Definition Methods *************
    /**
     * Returns the currently used Row Height for the table layout
     */
    public int getRowHeight() {
        return rowHeight;
    }

    public int getCellHeight() {
        return view.rowHeight();
    }

    /**
     * Sets the Row Height to be used in the table layout.
     * 
     * @param rowHeight
     */
    public void setRowHeight(int rowHeight) {
        this.rowHeight = rowHeight;
        //view.firstAttach = true;
        layout();
    }

    /**
     * Returns how many physical rows are used in the table layout.
     * 
     * @return
     */
    public int getVisibleRows() {
        return visibleRows;
    }

    /**
     * Sets how many physical rows are used in the table layout.
     * 
     * @param visibleRows
     */
    public void setVisibleRows(int visibleRows) {
        this.visibleRows = visibleRows;
        layout();
    }

    /**
     * Returns the data model currently being displayed by this table. The
     * return value is parameterized so specific models can be used that extend
     * the basic Row such as Item in AutoCompete and Dropdown
     * 
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends Row> ArrayList<T> getModel() {
        return (ArrayList<T>) model;
    }

    /**
     * Sets the data model to be displayed by this table. The model parameter is
     * parameterized so specific models can be used that extend the basic Row
     * such as Item in AutoCompete and Dropdown
     * 
     * @param model
     */
    @SuppressWarnings("unchecked")
    public void setModel(ArrayList<? extends Row> model) {
        finishEditing();
        unselectAll();

        this.model = (ArrayList<Row>) model;
        modelView = this.model;
        rowIndex = null;

        checkExceptions();

        // Clear any filter choices that may have been in force before model
        // changed
        for (Column col : columns) {
            if (col.getFilter() != null)
                col.getFilter().unselectAll();
        }

        if (queryMode) {
            renderView(-1, 1);
        } else {
            ((StaticView) view).bulkRender();

            if (endUserExceptions != null)
                ((StaticView) view).bulkExceptions(endUserExceptions);
        }

    }

    /**
     * This method will pull all filters in force from the columns and apply
     * them to the table model.
     */
    public void applyFilters() {
        ArrayList<Filter> filters;

        filters = new ArrayList<Filter>();
        for (Column col : columns) {
            if (col.getFilter() != null && col.isFiltered)
                filters.add(col.getFilter());
        }

        applyFilters(filters);

        fireFilterEvent();
    }

    /**
     * This method will filter the table by the filter list that is passed as
     * param
     */
    public void applyFilters(ArrayList<Filter> filters) {
        boolean include;

        if (model == null)
            return;

        finishEditing();

        /*
         * if no filters are in force revert modelView back to model and return;
         */
        if (filters == null || filters.size() == 0) {
            modelView = model;
            rowIndex = null;
            ((StaticView) view).bulkRender();

            if (hasExceptions())
                ((StaticView) view).bulkExceptions(endUserExceptions);

            if (isAnyRowSelected()) {
                for (Integer index : selections)
                    ((StaticView) view).applySelectionStyle(index);
            }

            return;
        }

        /*
         * Reset the modelView and the rowIndex hash
         */
        modelView = new ArrayList<Row>();
        rowIndex = new HashMap<Object, RowIndexes>();
        for (int i = 0; i < model.size(); i++)
            rowIndex.put(model.get(i), new RowIndexes(i, -1));

        /*
         * Run through model and filter out rows
         */
        for (int i = 0; i < model.size(); i++) {
            include = true;
            for (Filter filter : filters) {
                if (filter != null && filter.isFilterSet()
                        && !filter.include(model.get(i).getCell(filter.getColumn()))) {
                    include = false;
                    break;
                }
            }
            if (include) {
                modelView.add(model.get(i));
                rowIndex.get(model.get(i)).view = modelView.size() - 1;
            }
        }

        /*
         * If no rows were filtered reset the modelView back to model
         */
        if (modelView.size() == model.size()) {
            modelView = model;
            rowIndex = null;
        }

        // if ( !scrollToVisible(0))
        ((StaticView) view).bulkRender();

        if (hasExceptions())
            ((StaticView) view).bulkExceptions(endUserExceptions);

        if (isAnyRowSelected()) {
            for (Integer index : selections)
                ((StaticView) view).applySelectionStyle(convertModelIndexToView(index));
        }
    }

    /**
     * This method will take the passed view index and return the corresponding
     * original model index of the row.
     * 
     * @param index
     * @return
     */
    public int convertViewIndexToModel(int index) {
        int i = index;

        if (rowIndex != null && index >= 0)
            i = rowIndex.get(modelView.get(index)).model;

        return i;
    }

    public int convertModelIndexToView(int modelIndex) {
        if (rowIndex != null && modelIndex >= 0) {
            return convertModelIndexToView(model.get(modelIndex));
        }
        return modelIndex;
    }

    /**
     * This method will take the passed model index of a row and return the
     * corresponding view index for the row. If the model row is currently not
     * in the view then the a value of -1 will be returned.
     * 
     * @param modelIndex
     * @return
     */
    public int convertModelIndexToView(Row row) {
        int i = -1;
        RowIndexes rowInd;

        if (rowIndex != null) {
            rowInd = rowIndex.get(row);
            if (rowInd != null) {
                i = rowInd.view;
            }
        } else {
            i = model.indexOf(row);
        }

        return i;
    }

    /**
     * This method will adjust the RowIndexes when a row is added to or removed
     * from the table when a view is applied.
     * 
     * @param modelIndex
     * @param row
     * @param adj
     */
    private void adjustRowIndexes(int modelIndex, int row, int adj) {
        RowIndexes r;

        if (rowIndex == null)
            return;

        for (int i = row; i < modelView.size(); i++)
            rowIndex.get(modelView.get(i)).view += adj;

        for (int i = modelIndex; i < model.size(); i++) {
            r = rowIndex.get(model.get(i));
            if (r != null)
                r.model += adj;
        }

        for (int i = 0; i < selections.size(); i++) {
            if (selections.get(i) >= row)
                selections.set(i, selections.get(i) + adj);
        }
    }

    /**
     * This method will apply the passed sort and sort direction passed to the
     * table model.
     * 
     * @param sort
     * @param desc
     */
    public void applySort(int col, int dir, Comparator<? super Row> comp) {
        /*
         * Setup the modelView as its own object if not already
         */
        if (modelView == model) {
            modelView = new ArrayList<Row>();
            rowIndex = new HashMap<Object, RowIndexes>();
            for (int i = 0; i < model.size(); i++) {
                modelView.add(model.get(i));
                rowIndex.put(model.get(i), new RowIndexes(i, -1));
            }
        }

        Collections.sort(modelView, new Sort<Row>(col, dir, comp));

        /*
         * Set the view index of the hash based on the sort
         */
        for (int i = 0; i < modelView.size(); i++)
            rowIndex.get(modelView.get(i)).view = i;

        ((StaticView) view).bulkRender();

        if (hasExceptions())
            ((StaticView) view).bulkExceptions(endUserExceptions);

        if (isAnyRowSelected()) {
            for (Integer index : selections)
                ((StaticView) view).applySelectionStyle(convertModelIndexToView(index));
        }
    }

    /**
     * Returns the current size of the held model. Returns zero if a model has
     * not been set.
     * 
     * @return
     */
    public int getRowCount() {
        if (modelView == null)
            return 0;

        try {
            return modelView.size();
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * Used to determine the table has more than one row currently selected.
     * 
     * @return
     */
    public boolean isMultipleRowsSelected() {
        return selections.size() > 1;
    }

    /**
     * Used to determine if the table currently allows multiple selection.
     * 
     * @return
     */
    public boolean isMultipleSelectionAllowed() {
        return multiSelect;
    }

    /**
     * Used to put the table into Multiple Selection mode.
     * 
     * @param multiSelect
     */
    public void setAllowMultipleSelection(boolean multiSelect) {
        this.multiSelect = multiSelect;
    }

    /**
     * Returns the current Vertical Scrollbar view rule set.
     * 
     * @return
     */
    public Scrolling getVerticalScroll() {
        return verticalScroll;
    }

    /**
     * Sets the current Vertical Scrollbar view rule.
     * 
     * @param verticalScroll
     */
    public void setVerticalScroll(String verticalScroll) {
        this.verticalScroll = Scrolling.valueOf(verticalScroll);
        layout();

    }

    /**
     * Returns the current Horizontal Scrollbar view rule set
     * 
     * @return
     */
    public Scrolling getHorizontalScroll() {
        return horizontalScroll;
    }

    /**
     * Sets the current Horizontal Scrollbar view rule.
     * 
     * @param horizontalScroll
     */
    public void setHorizontalScroll(String horizontalScroll) {
        this.horizontalScroll = Scrolling.valueOf(horizontalScroll);
        layout();
    }

    /**
     * Sets a flag to set the size of the table to always set room aside for
     * scrollbars defaults to true
     * 
     * @param fixScrollBar
     */
    public void setFixScrollbar(boolean fixScrollBar) {
        this.fixScrollBar = fixScrollBar;
    }

    /**
     * Returns the flag indicating if the table reserves space for the scrollbar
     * 
     * @return
     */
    public boolean getFixScrollbar() {
        return fixScrollBar;
    }

    /**
     * Sets the width of the table view
     * 
     * @param width
     */
    public void setWidth(int width) {
        this.viewWidth = width;
        layout();

    }

    /**
     * Method overridden from Composite to call setWidth(int) so that the width
     * can be adjusted.
     */
    @Override
    public void setWidth(String width) {
        setWidth(Util.stripUnits(width));
    }

    /**
     * Returns the currently set view width for the Table
     * 
     * @return
     */
    public int getWidth() {
        return viewWidth;
    }

    /**
     * Returns the view width of the table minus the the width of the scrollbar
     * if the scrollbar is visible or if space has been reserved for it
     * 
     * @return
     */
    protected int getWidthWithoutScrollbar() {
        if (viewWidth < 0 && totalColumnWidth == 0 && getParent() != null) {
            if (getParent() instanceof LayoutPanel)
                return ((LayoutPanel) getParent()).getWidgetContainerElement(this).getOffsetWidth()
                        - CSSUtils.getAddedBorderWidth(getElement());
            else
                return getParent().getOffsetWidth() - CSSUtils.getAddedBorderWidth(getElement());
        }
        return (viewWidth == -1 ? totalColumnWidth : viewWidth) - CSSUtils.getAddedBorderWidth(getElement());
    }

    /**
     * Returns the width of the all the column widths added together which is
     * the physical width of the table
     * 
     * @return
     */
    public int getTotalColumnWidth() {
        return totalColumnWidth;
    }

    /**
     * Returns the number of columns used in this Table
     * 
     * @return
     */
    public int getColumnCount() {
        if (columns != null)
            return columns.size();
        return 0;
    }

    /**
     * Returns the column at the passed index
     * 
     * @param index
     * @return
     */
    public Column getColumnAt(int index) {
        return columns.get(index);
    }

    /**
     * Returns column by the name passed
     * 
     * @param index
     * @return
     */
    public int getColumnByName(String name) {
        for (int i = 0; i < columns.size(); i++) {
            if (columns.get(i).name.equals(name))
                return i;
        }
        return -1;
    }

    /**
     * This method can be used to determine the index of the column in the
     * display of the table
     * 
     * @param col
     * @return
     */
    public int getColumn(Column col) {
        return columns.indexOf(col);
    }

    /**
     * This method will replace the column at the passed index into the table
     * 
     * @param index
     * @param col
     */
    public void setColumnAt(int index, Column col) {
        col.setTable(this);
        columns.set(index, col);
        layout();
    }

    /**
     * This method will return the widget used to render/edit the cell contents
     * from the cell definition of the column.
     * 
     * @param index
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends Widget> T getColumnWidget(int index) {
        return (T) (index > -1 ? getColumnAt(index).getCellEditor().getWidget() : null);
    }

    /**
     * This method will return the column used in the table by it's name
     * 
     * @param name
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends Widget> T getColumnWidget(String name) {
        return (T) getColumnWidget(getColumnByName(name));
    }

    /**
     * Returns the X coordinate on the Screen of the Column passed.
     * 
     * @param index
     * @return
     */
    protected int getXForColumn(int index) {
        if (xForColumn != null && index >= 0 && index < xForColumn.length)
            return xForColumn[index];
        return -1;
    }

    /**
     * Returns the Column for the current mouse x position passed in the header
     * 
     * @param x
     * @return
     */
    protected int getColumnForX(int x) {
        if (columnForX != null && x >= 0 && x < columnForX.length)
            return columnForX[x];
        return -1;
    }

    /**
     * Sets whether the table as a header or not.
     */
    public void setHeader(boolean hasHeader) {
        this.hasHeader = hasHeader;
    }

    /**
     * Used to determine if table has header
     * 
     * @return
     */
    public boolean hasHeader() {
        return hasHeader;
    }

    public void setMenu(boolean hasMenu) {
        this.hasMenu = hasMenu;
    }

    public boolean hasMenu() {
        return hasMenu;
    }

    /**
     * Sets the list columns to be used by this Table
     * 
     * @param columns
     */
    public void setColumns(ArrayList<Column> columns) {
        this.columns = columns;

        if (columns != null) {
            for (Column column : columns)
                column.setTable(this);
        }

        layout();
    }

    /**
     * Creates and Adds a Column at the end of the column list with passed name
     * and header label in the params.
     * 
     * @param name
     *        Name of the column for reference
     * @param label
     *        Label used in Table header.
     * @return The newly created and added column
     */
    public Column addColumn(String name, String label) {
        return addColumnAt(columns.size(), name, label, 75);
    }

    /**
     * Creates and adds a new column to the table.
     * 
     * @return
     */
    public Column addColumn() {
        return addColumn("", "");
    }

    public void addColumn(Column col) {
        addColumnAt(columns.size(), col);
    }

    /**
     * Creates and inserts a new Column int the table at the specified index
     * using the name and label passed.
     * 
     * @param index
     *        Index in the Column list where to insert the new Column
     * @param name
     *        Name used in the Column as a reference to the Column.
     * @param label
     *        Label used in the Table header.
     * @return The newly created and added Column.
     */
    public Column addColumnAt(int index, String name, String label, int width) {
        Column column;

        column = new Column.Builder(width).name(name).label(label).build();
        addColumnAt(index, column);
        column.setTable(this);
        return column;
    }

    /**
     * Creates and adds a new Column at passed index
     * 
     * @param index
     *        Index in the Column list where to insert the new Column.
     * @return The newly created and added column.
     */
    public Column addColumnAt(int index) {
        return addColumnAt(index, "", "", 75);
    }

    public void addColumnAt(int index, Column column) {
        columns.add(index, column);
        column.setTable(this);
        if (model != null) {
            for (Row row : model)
                row.cells.add(index, null);
        }
        computeColumnsWidth();
        view.addColumn(index);
    }

    /**
     * Removes the column in the table and passed index.
     * 
     * @param index
     */
    public Column removeColumnAt(int index) {
        Column col;

        col = columns.remove(index);
        if (model != null) {
            for (Row row : model)
                row.cells.remove(index);
        }
        computeColumnsWidth();
        view.removeColumn(index);

        return col;
    }

    /**
     * Removes all columns from the table.
     */
    public void removeAllColumns() {
        columns.clear();
        layout();
    }

    /**
     * Creates a new blank Row and adds it to the bottom of the Table model.
     * 
     * @return
     */
    public <T extends Row> T addRow() {
        return addRow(getRowCount(), null);
    }

    /**
     * Creates a new blank Row and inserts it in the table model at the passed
     * index.
     * 
     * @param index
     * @return
     */
    public <T extends Row> T addRowAt(int index) {
        return addRow(index, null);
    }

    /**
     * Adds the passed Row to the end of the Table model.
     * 
     * @param row
     * @return
     */
    public <T extends Row> T addRow(T row) {
        return addRow(getRowCount(), row);
    }

    /**
     * Adds the passed Row into the Table model at the passed index.
     * 
     * @param index
     * @param row
     * @return
     */
    public <T extends Row> T addRowAt(int index, T row) {
        return (T) addRow(index, row);
    }

    /**
     * Private method called by all public addRow methods to handle event firing
     * and add the new row to the model.
     * 
     * @param index
     *        Index where the new row is to be added.
     * @param row
     *        Will be null if a Table should create a new blank Row to add
     *        otherwise the passed Row will be added.
     * @return Will return null if this action is canceled by a
     *         BeforeRowAddedHandler, otherwise the newly created Row will be
     *         returned or if a Row is passed to the method it will echoed back.
     */
    @SuppressWarnings("unchecked")
    private <T extends Row> T addRow(int index, T row) {
        int modelIndex;

        finishEditing();

        if (row == null)
            row = (T) new Row(columns.size());

        if (!fireBeforeRowAddedEvent(index, row))
            return null;

        /* if a model has not been set need to create an empty model */
        if (model == null)
            setModel(new ArrayList<Row>());

        /* Add row to model and then to view */
        if (rowIndex != null) {
            modelIndex = convertViewIndexToModel(index);
            model.add(modelIndex, row);
            rowIndex.put(row, new RowIndexes(modelIndex, index));
            adjustRowIndexes(modelIndex + 1, index, 1);
        }

        modelView.add(index, row);

        view.addRow(index);

        fireRowAddedEvent(index, row);

        return row;

    }

    /**
     * Method will delete a row from the model at the specified index and
     * refersh the view.
     * 
     * @param index
     * @return
     */
    public <T extends Row> T removeRowAt(int index) {
        int modelIndex;
        T row;

        finishEditing();

        unselectRowAt(index);

        row = getRowAt(index);

        if (!fireBeforeRowDeletedEvent(index, row))
            return null;

        if (balloonTimer != null)
            balloonTimer.cancel();

        if (rowIndex != null) {
            modelIndex = convertViewIndexToModel(index);
            model.remove(modelIndex);
            rowIndex.remove(row);
            adjustRowIndexes(modelIndex, index + 1, -1);
        }
        modelView.remove(index);

        view.removeRow(index);

        if (endUserExceptions != null) {
            endUserExceptions.remove(row);
            if (endUserExceptions.size() == 0)
                endUserExceptions = null;
        }

        if (validateExceptions != null) {
            validateExceptions.remove(row);
            if (validateExceptions.size() == 0)
                validateExceptions = null;
        }

        fireRowDeletedEvent(index, row);

        return row;
    }

    /**
     * Set the model for this table to null and redraws
     */
    public void removeAllRows() {
        finishEditing();
        unselectAll();
        model = null;
        modelView = null;
        rowIndex = null;
        view.removeAllRows();
        clearExceptions();
    }

    /**
     * Returns the Row at the specified index in the model
     * 
     * @param row
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T extends Row> T getRowAt(int row) {
        if (row < 0 || row >= getRowCount())
            return null;
        return (T) modelView.get(row);
    }

    // ************ Selection Methods ***************

    /**
     * Returns an array of indexes of the currently selected row
     */
    public Integer[] getSelectedRows() {
        return selections.toArray(new Integer[] {});
    }

    public void setCtrlKeyDefault(boolean ctrl) {
        this.ctrlDefault = ctrl;
    }

    /**
     * Selects the row at the passed index. Selection can be canceled by a
     * BeforeSelecionHandler. If selection is allowed, then a SelectionEvent
     * will be fired to all registered handlers, and the selected row will be
     * scrolled in the visible view.
     * 
     * @param index
     */
    public void selectRowAt(int index) {
        selectRowAt(index, null);
    }

    /**
     * Selects the row at the passed index. Selection can be canceled by a
     * BeforeSelecionHandler. If selection is allowed, then a SelectionEvent
     * will be fired to all registered handlers, and the selected row will be
     * scrolled in the visible view. If the table allows multiple selection the
     * row will be added to the current list of selections.
     * 
     * @param index
     */
    protected void selectRowAt(int row, NativeEvent event) {

        if (row < 0) {
            unselectAll();
            return;
        }

        /*
         * If multiple selection is allowed check event for ctrl or shift keys.
         * If none apply the logic will fall throw to normal selection.
         */
        if (isMultipleSelectionAllowed()) {
            if (ctrlDefault || (event != null && Event.getTypeInt(event.getType()) == Event.ONCLICK)) {
                multiSelect(row, event);
                return;
            }
        }

        if (isRowSelected(row))
            return;

        if (event == null || fireBeforeSelectionEvent(row)) {
            unselectAll();

            finishEditing();

            selections.add(row);

            view.applySelectionStyle(row);

            if (event != null)
                fireSelectionEvent(row);

            scrollToVisible(row);
        }

    }

    private void multiSelect(int row, NativeEvent event) {
        int startSelect, endSelect, minSelected, maxSelected, i;
        boolean ctrlKey, shiftKey, selected = false;

        startSelect = row;
        endSelect = row;

        ctrlKey = ctrlDefault ? ctrlDefault : event.getCtrlKey();
        shiftKey = event != null ? event.getShiftKey() : false;

        if (ctrlKey) {
            if (isRowSelected(row)) {
                unselectRowAt(row, event);
                return;
            }
        } else if (shiftKey) {
            if (!isAnyRowSelected()) {
                startSelect = 0;
                endSelect = row;
            } else {
                Collections.sort(selections);
                minSelected = Collections.min(selections);
                maxSelected = Collections.max(selections);
                if (minSelected > row) {
                    startSelect = row;
                    endSelect = minSelected;
                } else if (row > maxSelected) {
                    startSelect = maxSelected;
                    endSelect = row;
                } else {
                    i = 0;
                    while (selections.get(i + 1) < row)
                        i++;
                    startSelect = selections.get(i);
                    endSelect = row;
                }
            }
            unselectAll(event);
        } else
            unselectAll(event);

        for (i = startSelect; i <= endSelect && i > -1; i++) {
            if (!selections.contains(i)) {
                if (event == null || fireBeforeSelectionEvent(i)) {

                    selected = true;

                    finishEditing();

                    selections.add(i);

                    view.applySelectionStyle(i);

                    if (event != null)
                        fireSelectionEvent(i);
                }
            }
        }

        if (selected)
            scrollToVisible(endSelect);
    }

    /**
     * This method will select all rows in the table if the table allows
     * Multiple Selection at the time it is called. No selections events will be
     * fired.
     */
    public void selectAll() {
        if (isMultipleSelectionAllowed()) {
            selections = new ArrayList<Integer>();
            for (int i = 0; i < getRowCount(); i++) {
                selections.add(i);
                ((StaticView) view).applySelectionStyle(i);
            }
        }
    }

    /**
     * Unselects the row from the selection list. This method does nothing if
     * the passed index is not currently a selected row, otherwise the row will
     * be unselected.
     * 
     * @param index
     */
    public void unselectRowAt(int index) {
        unselectRowAt(index, null);
    }

    /**
     * Unselects the row from the selection list. This method does nothing if
     * the passed index is not currently a selected row, otherwise the row will
     * be unselected and an UnselectEvent will be fired to all registered
     * handlers
     * 
     * @param index
     */
    protected void unselectRowAt(int index, NativeEvent event) {

        if (selections.contains(index)) {
            finishEditing();
            if (event != null)
                fireUnselectEvent(index);
            selections.remove(new Integer(index));
            view.applyUnselectionStyle(index);
        }
    }

    public void unselectAll() {
        unselectAll(null);
    }

    /**
     * Clears all selections from the table.
     */
    protected void unselectAll(NativeEvent event) {
        int count = selections.size();
        for (int i = 0; i < count; i++)
            unselectRowAt(selections.get(0), event);

    }

    /**
     * Returns the selected index of the first row selected
     * 
     * @return
     */
    public int getSelectedRow() {
        return selections.size() > 0 ? selections.get(0) : -1;
    }

    /**
     * Used to determine if the passed row index is currently in the selection
     * list.
     * 
     * @param index
     * @return
     */
    public boolean isRowSelected(int index) {
        return selections.contains(index);
    }

    /**
     * Used to determine if any row in the table is selected
     * 
     * @return
     */
    public boolean isAnyRowSelected() {
        return selections.size() > 0;
    }

    // ********* Event Firing Methods ********************

    /**
     * Private method that will fire a BeforeSelectionEvent for the passed
     * index. Returns false if the selection is canceled by registered handler
     * and true if the selection is allowed.
     */
    private boolean fireBeforeSelectionEvent(int index) {
        BeforeSelectionEvent<Integer> event = null;

        if (!queryMode)
            event = BeforeSelectionEvent.fire(this, index);

        return event == null || !event.isCanceled();
    }

    /**
     * Private method that will fire a SelectionEvent for the passed index to
     * notify all registered handlers that row at the passed index was selected.
     * Returns true as a default.
     * 
     * @param index
     * @return
     */
    private boolean fireSelectionEvent(int index) {

        if (!queryMode)
            SelectionEvent.fire(this, index);

        return true;
    }

    /**
     * Private method that will fire an UnselectionEvent for the passed index.
     * Returns false if the unselection was canceled by a registered handler and
     * true if the unselection is allowed.
     * 
     * @param index
     * @return
     */
    private void fireUnselectEvent(int index) {

        if (!queryMode)
            UnselectionEvent.fire(this, index);
    }

    /**
     * Private method that will fire a BeforeCellEditedEvent for a cell in the
     * table. Returns false if the cell editing is canceled by a registered
     * handler and true if the user is allowed to edit the cell.
     * 
     * @param row
     * @param col
     * @param val
     * @return
     */
    private boolean fireBeforeCellEditedEvent(int row, int col, Object val) {
        BeforeCellEditedEvent event = null;

        if (!queryMode)
            event = BeforeCellEditedEvent.fire(this, row, col, val);

        return event == null || !event.isCancelled();
    }

    /**
     * Private method that will fire a CellEditedEvent after the value of a cell
     * is changed by a user input. Returns true as default.
     * 
     * @param index
     * @return
     */
    private boolean fireCellEditedEvent(int row, int col) {

        if (!queryMode)
            CellEditedEvent.fire(this, row, col);

        return true;
    }

    /**
     * Private method that fires a BeforeRowAddedEvent for the passed index and
     * Row. Returns false if the addition is canceled by a registered handler
     * and true if the addition is allowed.
     * 
     * @param index
     * @param row
     * @return
     */
    private boolean fireBeforeRowAddedEvent(int index, Row row) {
        BeforeRowAddedEvent event = null;

        if (!queryMode)
            event = BeforeRowAddedEvent.fire(this, index, row);

        return event == null || !event.isCancelled();
    }

    /**
     * Private method that fires a RowAddedEvent for the passed index and Row to
     * all registered handlers. Returns true as a default.
     * 
     * @param index
     * @param row
     * @return
     */
    private boolean fireRowAddedEvent(int index, Row row) {

        if (!queryMode)
            RowAddedEvent.fire(this, index, row);

        return true;

    }

    /**
     * Private method that fires a BeforeRowDeletedEvent for the passed index
     * and Row. Returns false if the deletion is canceled by a registered
     * handler and true if the deletion is allowed.
     * 
     * @param index
     * @param row
     * @return
     */
    private boolean fireBeforeRowDeletedEvent(int index, Row row) {
        BeforeRowDeletedEvent event = null;

        if (!queryMode)
            event = BeforeRowDeletedEvent.fire(this, index, row);

        return event == null || !event.isCancelled();
    }

    /**
     * Private method that fires a RowDeletedEvent for the passed index and Row
     * to all registered handlers. Returns true as a default.
     * 
     * @param index
     * @param row
     * @return
     */
    private boolean fireRowDeletedEvent(int index, Row row) {

        if (!queryMode)
            RowDeletedEvent.fire(this, index, row);

        return true;
    }

    protected boolean fireCellClickedEvent(int row, int col, boolean ctrlKey, boolean shiftKey) {
        CellClickedEvent event = null;

        if (!queryMode)
            event = CellClickedEvent.fire(this, row, col, ctrlKey, shiftKey);

        return event == null || !event.isCancelled();

    }

    protected void fireCellDoubleClickedEvent(int row, int col) {
        if (!queryMode)
            CellDoubleClickedEvent.fire(this, row, col);
    }

    /**
     * Fires a Filter event after this table has been filtered and the new model
     * is displayed.
     */
    protected void fireFilterEvent() {
        FilterEvent.fire(this);
    }

    // ********* Edit Table Methods *******************
    /**
     * Used to determine if a cell is currently being edited in the Table
     */
    public boolean isEditing() {
        return editing;
    }

    /**
     * Sets the value of a cell in Table model.
     * 
     * @param row
     * @param col
     * @param value
     */
    public <T> void setValueAt(int row, int col, T value) {
        Column column;
        ArrayList<Exception> exceptions;

        finishEditing();
        modelView.get(row).setCell(col, value);

        column = getColumnAt(col);

        exceptions = column.getCellRenderer().validate(value);

        if (!queryMode) {
            if (column.isRequired() && value == null) {
                if (exceptions == null)
                    exceptions = new ArrayList<Exception>();
                exceptions.add(new Exception(Messages.get().exc_fieldRequired()));
            }
        }

        setValidateException(row, col, exceptions);

        refreshCell(row, col);
    }

    /**
     * Sets a row in the model at the passed index and refreshes the view.
     * 
     * @param index
     * @param row
     */
    public <T extends Row> void setRowAt(int index, T row) {
        finishEditing();
        modelView.set(index, row);
        renderView(index, index);
    }

    /**
     * Returns the value of a cell in the model.
     * 
     * @param row
     * @param col
     * @return
     */
    public <T> T getValueAt(int row, int col) {
        if (modelView == null || row >= modelView.size())
            return null;
        return (T) modelView.get(row).getCell(col);
    }

    /**
     * Method to put a cell into edit mode. If a cell can not be edited than
     * false will be returned
     * 
     * @param row
     * @param col
     * @return
     */
    public boolean startEditing(int row, int col) {
        return startEditing(row, col, null);
    }

    /**
     * Method that sets focus to a cell in the Table and readies it for user
     * input. event is passed to this method by view clickhandler to be able to
     * check for multiple selection logic
     * 
     * @param row
     * @param col
     * @return
     */
    @SuppressWarnings("rawtypes")
    protected boolean startEditing(final int row, final int col, final NativeEvent event) {

        /*
         * Return out if the table is not enable or the passed cell is already
         * being edited
         */
        if (!isEnabled() || (row == editingRow && col == editingCol)) {
            if (columns.get(col).getCellEditor() instanceof CheckBoxCell
                    && Event.getTypeInt(event.getType()) == Event.ONCLICK)
                ClickEvent.fireNativeEvent(event, ((CheckBox) getColumnWidget(col)).getCheck());
            return false;
        }

        finishEditing();

        selectRowAt(row, event);

        // Check if the row was able to be selected, if not return.
        if (!isRowSelected(row))
            return false;

        // Check if column is editable otherwise return false
        if (!getColumnAt(col).hasEditor())
            return false;

        // Fire before cell edited event to allow user the chance to cancel
        if (!fireBeforeCellEditedEvent(row, col, getValueAt(row, col)))
            return false;

        /*
         * Set editing attribute values.
         */
        editingRow = row;
        editingCol = col;
        editing = true;

        view.startEditing(row, col, getValueAt(row, col), event);

        return true;
    }

    public void finishEditing() {
        finishEditing(true);
    }

    /**
     * Method called to complete editing of any cell in the table. Method does
     * nothing a cell is not currently being edited.
     */
    public void finishEditing(boolean keepFocus) {
        Object newValue, oldValue;
        int row, col;

        /*
         * Return out if not currently editing.
         */
        if (!editing)
            return;

        /*
         * Reset editing attribute values
         */
        editing = false;
        row = editingRow;
        col = editingCol;
        editingRow = -1;
        editingCol = -1;

        /*
         * Retrieve new value form cell editor, store value in the model, and
         * render the cell
         */
        newValue = view.finishEditing(row, col);
        oldValue = getValueAt(row, col);
        setValueAt(row, col, newValue);
        // modelView.get(row).setCell(col, newValue);
        // refreshCell(row, col);

        /*
         * fire a cell edited event if the value of the cell was changed
         */
        if (Util.isDifferent(newValue, oldValue)) {
            fireCellEditedEvent(row, col);
        }

        /*
         * Call setFocus(true) so that the KeyHandler will receive events when
         * no cell is being edited
         */
        if (keepFocus)
            setFocus(true);
    }

    /**
     * Returns the current row where cell is being edited
     * 
     * @return
     */
    public int getEditingRow() {
        return editingRow;
    }

    /**
     * Returns the current column where cell is being edited
     * 
     * @return
     */
    public int getEditingCol() {
        return editingCol;
    }

    // ********* Draw Scroll Methods ****************
    /**
     * Scrolls the table in the required direction to make sure the passed index
     * is visible. Or if the index passed is in the view range refresh the row
     * to make sure that the latest data is shown (i.e. row Added before scroll
     * size is hit).
     */
    public boolean scrollToVisible(int index) {
        return view.scrollToVisible(index);
    }

    /**
     * Method to scroll the table by the specified number of rows. A negative
     * value will cause the table to scroll up and a positive to scroll down.
     * 
     * @param rows
     */
    public void scrollBy(int rows) {
        view.scrollBy(rows);
    }

    /**
     * Redraws the table when any part of its physical definition is changed.
     */
    public void layout() {
        computeColumnsWidth();
        view.layout();
    }

    /**
     * Method called when a column width has been set to resize the table
     * columns
     */
    protected void resize() {
        computeColumnsWidth();

        if (!isAttached()) {
            layout();
            return;
        }

        finishEditing();

        if (hasHeader)
            view.getHeader().resize();

        view.resize();
    }

    /**
     * Method will have to view re-compute its visible rows and refresh the view
     * 
     * @param startR
     * @param endR
     */
    protected void renderView(int startR, int endR) {
        view.adjustScrollBarHeight();
        view.renderView(startR, endR);
    }

    /**
     * Method computes the XForColumn and ColumForX arrays and set the
     * totoalColumnWidth
     */
    protected void computeColumnsWidth() {
        int from, to;

        //
        // compute total width
        //
        totalColumnWidth = 0;
        int xmark = 0;
        xForColumn = new short[getColumnCount()];
        for (int i = 0; i < getColumnCount(); i++) {
            if (getColumnAt(i).isDisplayed()) {
                xForColumn[i] = (short) xmark;
                xmark += getColumnAt(i).getWidth();
                totalColumnWidth += getColumnAt(i).getWidth();
            }
        }
        //
        // mark the array
        //
        from = 0;
        columnForX = new short[xmark];
        for (int i = 0; i < getColumnCount(); i++) {
            if (getColumnAt(i).isDisplayed()) {
                to = from + getColumnAt(i).getWidth();
                while (from < to && from + 1 < xmark)
                    columnForX[from++] = (short) i;
            }
        }
    }

    /**
     * redraws data in the cell passed
     * 
     * @param row
     * @param col
     */
    protected void refreshCell(int row, int col) {
        view.renderCell(row, col);
    }

    // ************* Implementation of ScreenWidgetInt *************

    /**
     * Sets whether this table allows selection
     */
    public void setEnabled(boolean enabled) {
        this.enabled = enabled;

    }

    /**
     * Used to determine if the table is enabled for selection.
     */
    public boolean isEnabled() {
        return enabled;
    }

    /**
     * Sets the Focus style to the Table
     */
    public void addFocusStyle(String style) {
        addStyleName(style);
    }

    /**
     * Removes the Focus style from the Table
     */
    public void removeFocusStyle(String style) {
        removeStyleName(style);
    }

    public void onFocus(FocusEvent event) {
        /*
         * Widget focused;
         * 
         * focused = ((ScreenPanel)event.getSource()).getFocused();
         * 
         * if(focused == null ||
         * !DOM.isOrHasChild(getElement(),focused.getElement()))
         * finishEditing(false);
         */
    }

    // ********** Implementation of Queryable *******************
    /**
     * Returns a list of QueryData objects for all Columns in the table that
     * have values and will participate in the query.
     */
    public Object getQuery() {
        ArrayList<QueryData> qds;
        QueryData qd;

        if (!queryMode)
            return null;

        qds = new ArrayList<QueryData>();

        for (int i = 0; i < getColumnCount(); i++) {
            qd = (QueryData) getValueAt(0, i);
            if (qd != null) {
                qd.setKey(getColumnAt(i).name);
                qds.add(qd);
            }
        }
        return qds.toArray(new QueryData[] {});
    }

    /**
     * Stub method for Queryable method
     */
    public void setQuery(QueryData query) {
        // Do nothing
    }

    /**
     * Puts the table into and out of query mode.
     */
    public void setQueryMode(boolean query) {

        ArrayList<Row> model;
        Row row;

        if (query == queryMode)
            return;

        this.queryMode = query;
        if (query) {
            model = new ArrayList<Row>();
            row = new Row(getColumnCount());
            model.add(row);
            setModel(model);
        } else
            setModel(null);
    }

    /**
     * Method to determine if Table is in QueryMode
     * 
     * @return
     */
    public boolean getQueryMode() {
        return queryMode;
    }

    /**
     * Stub method from Queryable Interface
     */
    public void validateQuery() {

    }

    /**
     * Method used to determine if widget is currently in Query mode
     */
    public boolean isQueryMode() {
        return queryMode;
    }

    /**
     * Convenience method to check if a widget has exceptions so we do not need
     * to go through the cost of merging the logical and validation exceptions
     * in the getExceptions method.
     * 
     * @return
     */
    public boolean hasExceptions(int row, int col) {
        Row key;

        key = getRowAt(row);
        return (endUserExceptions != null
                && (endUserExceptions.containsKey(key) && endUserExceptions.get(key).containsKey(col)))
                || (validateExceptions != null
                        && (validateExceptions.containsKey(key) && validateExceptions.get(key).containsKey(col)));
    }

    public <T extends Row> void addException(T row, int col, Exception error) {
        ArrayList<Exception> exceptions;
        int r;

        exceptions = getEndUserExceptionList(row, col);

        if (exceptions.contains(error))
            return;

        exceptions.add(error);

        if (model == null)
            return;

        if (rowIndex != null && rowIndex.containsKey(row)) {
            r = rowIndex.get(row).view;
        } else {
            r = model.indexOf(row);
        }
        view.renderView(r, r);
    }

    /**
     * Adds a manual Exception to the widgets exception list.
     */
    public void addException(int row, int col, Exception error) {
        ArrayList<Exception> exceptions;

        exceptions = getEndUserExceptionList(getRowAt(row), col);

        if (exceptions.contains(error))
            return;

        exceptions.add(error);

        renderView(row, row);
    }

    /**
     * Method to add a validation exception to the passed cell.
     * 
     * @param row
     * @param col
     * @param error
     */
    protected void setValidateException(int rw, int col, ArrayList<Exception> errors) {

        HashMap<Integer, ArrayList<Exception>> cellExceptions = null;
        HashMap<Integer, ArrayList<Exception>> rowExceptions;
        Row row;

        row = getRowAt(rw);

        // If hash is null and errors are passed as null, nothing to reset so
        // return
        if (validateExceptions == null && (errors == null || errors.isEmpty()))
            return;

        // If hash is not null, but errors passed is null then make sure the
        // passed cell entry removed
        if (validateExceptions != null && (errors == null || errors.isEmpty())) {
            if (validateExceptions.containsKey(row)) {
                rowExceptions = validateExceptions.get(row);
                rowExceptions.remove(col);
                if (rowExceptions.isEmpty())
                    validateExceptions.remove(row);
            }
            return;
        }

        // If list is null we need to create the Hash to add the errors
        if (validateExceptions == null) {
            validateExceptions = new HashMap<Row, HashMap<Integer, ArrayList<Exception>>>();
            cellExceptions = new HashMap<Integer, ArrayList<Exception>>();

            validateExceptions.put(row, cellExceptions);
        }

        if (cellExceptions == null) {
            if (!validateExceptions.containsKey(row)) {
                cellExceptions = new HashMap<Integer, ArrayList<Exception>>();
                validateExceptions.put(row, cellExceptions);
            } else
                cellExceptions = validateExceptions.get(row);
        }

        cellExceptions.put(col, errors);
    }

    /**
     * Gets the ValidateExceptions list to be displayed on the screen.
     */
    public ArrayList<Exception> getValidateExceptions(int row, int col) {
        if (validateExceptions != null) {
            if (validateExceptions.containsKey(getRowAt(row)))
                return validateExceptions.get(getRowAt(row)).get(col);
        }
        return null;
    }

    /**
     * Method used to get the set list of user exceptions for a cell.
     * 
     * @param row
     * @param col
     * @return
     */
    public ArrayList<Exception> getEndUserExceptions(int row, int col) {
        if (endUserExceptions != null) {
            if (endUserExceptions.containsKey(getRowAt(row)))
                return endUserExceptions.get(getRowAt(row)).get(col);
        }
        return null;
    }

    /**
     * Clears all manual and validate exceptions from the widget.
     */
    public void clearExceptions() {
        if (endUserExceptions != null || validateExceptions != null) {
            endUserExceptions = null;
            validateExceptions = null;
            view.renderExceptions(-1, -1);
        }
    }

    public void clearEndUserExceptions() {
        if (endUserExceptions != null) {
            endUserExceptions = null;
            view.renderExceptions(-1, -1);
        }
    }

    public void clearValidateExceptions() {
        if (validateExceptions != null) {
            validateExceptions = null;
            view.renderExceptions(-1, -1);
        }
    }

    public void clearExceptions(Row row, int col) {
        if (rowIndex != null && rowIndex.containsKey(row))
            clearExceptions(rowIndex.get(row).model, col);
        else
            clearExceptions(model.indexOf(row), col);
    }

    /**
     * Clears all exceptions from the table cell passed
     * 
     * @param row
     * @param col
     */
    public void clearExceptions(int row, int col) {
        HashMap<Integer, ArrayList<Exception>> cellExceptions = null;
        Row key;

        key = getRowAt(row);
        if (endUserExceptions != null) {
            cellExceptions = endUserExceptions.get(key);
            if (cellExceptions != null) {
                cellExceptions.remove(col);
                if (cellExceptions.size() == 0)
                    endUserExceptions.remove(key);
            }
        }

        if (validateExceptions != null) {
            cellExceptions = validateExceptions.get(key);
            if (cellExceptions != null) {
                cellExceptions.remove(col);
                if (cellExceptions.size() == 0)
                    validateExceptions.remove(key);
            }
        }

        view.renderExceptions(row, row);

    }

    public <T extends Row> void clearEndUserExceptions(T row, int col) {

        if (rowIndex != null && rowIndex.containsKey(row))
            clearEndUserExceptions(rowIndex.get(row).model, col);
        else
            clearEndUserExceptions(model.indexOf(row), col);
    }

    /**
     * Clears all exceptions from the table cell passed
     * 
     * @param row
     * @param col
     */
    public void clearEndUserExceptions(int row, int col) {
        HashMap<Integer, ArrayList<Exception>> cellExceptions = null;
        Row key;

        key = getRowAt(row);
        if (endUserExceptions != null) {
            cellExceptions = endUserExceptions.get(key);
            if (cellExceptions != null) {
                cellExceptions.remove(col);
                if (cellExceptions.size() == 0)
                    endUserExceptions.remove(key);
            }
        }

        view.renderExceptions(row, row);

    }

    /**
     * Method will get the list of the exceptions for a cell and will create a
     * new list if no exceptions are currently on the cell.
     * 
     * @param row
     * @param col
     * @return
     */
    private ArrayList<Exception> getEndUserExceptionList(Row row, int col) {
        HashMap<Integer, ArrayList<Exception>> cellExceptions = null;
        ArrayList<Exception> list = null;

        if (endUserExceptions == null)
            endUserExceptions = new HashMap<Row, HashMap<Integer, ArrayList<Exception>>>();

        cellExceptions = endUserExceptions.get(row);

        if (cellExceptions == null) {
            cellExceptions = new HashMap<Integer, ArrayList<Exception>>();
            endUserExceptions.put(row, cellExceptions);
        }

        list = cellExceptions.get(col);

        if (list == null) {
            list = new ArrayList<Exception>();
            cellExceptions.put(col, list);
        }

        return list;

    }

    /**
     * Method will get the list of the exceptions for a cell and will create a
     * new list if no exceptions are currently on the cell.
     * 
     * @param row
     * @param col
     * @return
     */
    private ArrayList<Exception> getValidateExceptionList(int row, int col) {
        HashMap<Integer, ArrayList<Exception>> cellExceptions = null;
        ArrayList<Exception> list;
        Row key;

        key = getRowAt(row);
        if (validateExceptions == null)
            validateExceptions = new HashMap<Row, HashMap<Integer, ArrayList<Exception>>>();

        cellExceptions = validateExceptions.get(key);

        if (cellExceptions == null) {
            cellExceptions = new HashMap<Integer, ArrayList<Exception>>();
            validateExceptions.put(key, cellExceptions);
        }

        list = cellExceptions.get(col);

        if (list == null) {
            list = new ArrayList<Exception>();
            cellExceptions.put(col, list);
        }

        return list;

    }

    /**
     * Method to draw the balloon display of the exceptions for a cell
     * 
     * @param row
     * @param col
     * @param x
     * @param y
     */
    protected void drawExceptions(final int row, final int col, final int x, final int y) {
        if (row == editingRow && col == editingCol)
            return;

        balloonTimer = new Timer() {
            public void run() {
                Balloon.drawExceptions(getEndUserExceptions(row, col), getValidateExceptions(row, col),
                        view.table().getCellFormatter().getElement(row, col), x, y);
            }
        };
        balloonTimer.schedule(500);
    }

    // ******************** Drag and Drop methods
    // ****************************************
    /**
     * Method will enable the rows in the table to be dragged. This must be
     * called before the model is first set.
     */
    public void enableDrag() {
        assert model == null : "Drag must be set before model is loaded";

        dragController = new TableDragController(this, RootPanel.get());
    }

    /**
     * Method will enable this table to receive drop events from a drag
     */
    public void enableDrop() {
        dropController = new TableDropController(this);
    }

    /**
     * Adds a DropController as a drop target for rows from this table
     * 
     * @param target
     */
    public void addDropTarget(DropController target) {
        dragController.registerDropController(target);
    }

    /**
     * Removes a DropController as a drop target for rows from this table
     * 
     * @param target
     */
    public void removeDropTarget(DropController target) {
        dragController.unregisterDropController(target);
    }

    /**
     * Returns the TableDragController for this Table.
     * 
     * @return
     */
    public TableDragController getDragController() {
        return dragController;
    }

    /**
     * Returns the TableDropController for this Table.
     * 
     * @return
     */
    public TableDropController getDropController() {
        return dropController;
    }

    // ********* Registration of Handlers ******************
    /**
     * Registers a BeforeSelectionHandler to this Table
     */
    public HandlerRegistration addBeforeSelectionHandler(BeforeSelectionHandler<Integer> handler) {
        return addHandler(handler, BeforeSelectionEvent.getType());
    }

    /**
     * Registers a SelectionHandler to this Table
     */
    public HandlerRegistration addSelectionHandler(SelectionHandler<Integer> handler) {
        return addHandler(handler, SelectionEvent.getType());
    }

    /**
     * Registers an UnselectionHandler to this Table
     */
    public HandlerRegistration addUnselectionHandler(UnselectionHandler<Integer> handler) {
        return addHandler(handler, UnselectionEvent.getType());
    }

    /**
     * Registers a BeforeCellEditedHandler to this Table
     */
    public HandlerRegistration addBeforeCellEditedHandler(BeforeCellEditedHandler handler) {
        return addHandler(handler, BeforeCellEditedEvent.getType());
    }

    /**
     * Registers a CellEditedHandler to this Table
     */
    public HandlerRegistration addCellEditedHandler(CellEditedHandler handler) {
        return addHandler(handler, CellEditedEvent.getType());
    }

    /**
     * Registers a BeforeRowAddedHandler to this Table
     */
    public HandlerRegistration addBeforeRowAddedHandler(BeforeRowAddedHandler handler) {
        return addHandler(handler, BeforeRowAddedEvent.getType());
    }

    /**
     * Registers a RowAddedHandler to this Table
     */
    public HandlerRegistration addRowAddedHandler(RowAddedHandler handler) {
        return addHandler(handler, RowAddedEvent.getType());
    }

    /**
     * Registers a BeforeRowDeletedHandler to this Table
     */
    public HandlerRegistration addBeforeRowDeletedHandler(BeforeRowDeletedHandler handler) {
        return addHandler(handler, BeforeRowDeletedEvent.getType());
    }

    /**
     * Registers a RowDeletedHandler to this Table
     */
    public HandlerRegistration addRowDeletedHandler(RowDeletedHandler handler) {
        return addHandler(handler, RowDeletedEvent.getType());
    }

    /**
     * Register a CellClickedHandler to this Table
     */
    public HandlerRegistration addCellClickedHandler(CellClickedHandler handler) {
        return addHandler(handler, CellClickedEvent.getType());
    }

    public HandlerRegistration addCellDoubleClickedHandler(CellDoubleClickedEvent.Handler handler) {
        return addHandler(handler, CellDoubleClickedEvent.getType());
    }

    /**
     * Register a FilterHandler to this Table
     */
    public HandlerRegistration addFilterHandler(FilterHandler handler) {
        return addHandler(handler, FilterEvent.getType());
    }

    /**
     * This method will check the model to make sure that all required cells
     * have values
     */
    public void validate() {
        boolean render = false;
        ArrayList<Exception> exceptions;
        Exception exception;

        finishEditing();

        if (queryMode)
            return;

        for (int col = 0; col < getColumnCount(); col++) {
            if (getColumnAt(col).isRequired()) {
                for (int row = 0; row < getRowCount(); row++) {
                    if (getValueAt(row, col) == null) {
                        exceptions = getValidateExceptionList(row, col);
                        exception = new Exception(Messages.get().exc_fieldRequired());
                        if (!exceptions.contains(exception)) {
                            exceptions.add(exception);
                            setValidateException(row, col, exceptions);
                            render = true;
                        }
                    }
                }
            }
        }

        if (render)
            view.renderExceptions(-1, -1);
    }

    /**
     * Returns the model as part of the HasValue interface
     */
    public ArrayList<? extends Row> getValue() {
        return getModel();
    }

    /**
     * Sets the model as part of the HasValue interface
     */
    public void setValue(ArrayList<? extends Row> value) {
        setValue(value, false);
    }

    /**
     * Sets the model and will fire ValueChangeEvent if fireEvents is true as
     * part of the HasValue interface
     */
    public void setValue(ArrayList<? extends Row> value, boolean fireEvents) {
        setModel(value);

        if (fireEvents)
            ValueChangeEvent.fire(this, value);

    }

    /**
     * Handler Registration for ValueChangeEvent
     */
    public HandlerRegistration addValueChangeHandler(ValueChangeHandler<ArrayList<? extends Row>> handler) {
        return addHandler(handler, ValueChangeEvent.getType());
    }

    public void addException(Exception exception) {

    }

    public void addExceptionStyle() {

    }

    public void checkExceptions() {
        if (endUserExceptions != null) {
            for (Row row : endUserExceptions.keySet()) {
                if (model == null || !model.contains(row))
                    endUserExceptions.remove(row);
            }

            if (endUserExceptions.size() == 0)
                endUserExceptions = null;
        }

        if (validateExceptions != null) {
            for (Row row : validateExceptions.keySet()) {
                if (model == null || !model.contains(row))
                    validateExceptions.remove(row);
            }

            if (validateExceptions.size() == 0)
                validateExceptions = null;
        }

    }

    public ArrayList<Exception> getEndUserExceptions() {
        ArrayList<Exception> exceptions;

        if (endUserExceptions == null)
            return null;

        exceptions = new ArrayList<Exception>();

        for (HashMap<Integer, ArrayList<Exception>> row : endUserExceptions.values()) {
            for (ArrayList<Exception> excs : row.values()) {
                exceptions.addAll(excs);
            }
        }

        return exceptions;
    }

    public ArrayList<Exception> getValidateExceptions() {
        ArrayList<Exception> exceptions;

        if (validateExceptions == null)
            return null;

        exceptions = new ArrayList<Exception>();

        for (HashMap<Integer, ArrayList<Exception>> row : validateExceptions.values()) {
            for (ArrayList<Exception> excs : row.values()) {
                exceptions.addAll(excs);
            }
        }

        return exceptions;
    }

    public boolean hasExceptions() {
        validate();
        return (endUserExceptions != null && !endUserExceptions.isEmpty())
                || (validateExceptions != null && !validateExceptions.isEmpty());
    }

    public void removeExceptionStyle() {

    }

    /**
     * This private inner class is used to map Row indexes from the model to a
     * sorted or filtered view
     */
    private class RowIndexes {
        protected int model, view;

        protected RowIndexes(int model, int view) {
            this.model = model;
            this.view = view;
        }
    }

    /**
     * Private inner class that implements Comparator<Row> interface and will
     * sort the table model using the Collections.sort() method
     */

    private class Sort<T extends Row> implements Comparator<T> {
        int col, dir;

        @SuppressWarnings("rawtypes")
        Comparator comparator;

        @SuppressWarnings("rawtypes")
        public Sort(int col, int dir, Comparator comparator) {
            this.col = col;
            this.dir = dir;
            this.comparator = comparator;
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        public int compare(Row o1, Row o2) {
            Comparable c1, c2;

            c1 = o1.getCell(col);
            c2 = o2.getCell(col);

            if (comparator != null)
                return dir * comparator.compare(c1, c2);

            if (c1 == null && c2 == null)
                return 0;
            else if (c1 != null && c2 != null)
                return dir * ((Comparable) o1.getCell(col)).compareTo((Comparable) o2.getCell(col));
            else {
                if (c1 == null && c2 != null)
                    return 1;
                else
                    return -1;
            }
        };
    }

    public class UniqueFilter implements Filter {
        int column;
        ArrayList<FilterChoice> choices;
        HashMap<Object, FilterChoice> values;

        public ArrayList<FilterChoice> getChoices(ArrayList<? extends Row> model) {
            Object value;
            FilterChoice choice;
            CellRenderer renderer;

            if (values == null) {
                values = new HashMap<Object, FilterChoice>();
                choices = new ArrayList<FilterChoice>();
            }

            renderer = getColumnAt(column).getCellRenderer();
            if (values.isEmpty() && renderer instanceof CheckBoxCell) {
                choice = new FilterChoice();
                choice.setDisplay("Checked");
                choice.setValue("Y");
                choice.setSelected(false);
                choices.add(choice);
                values.put("Y", choice);
                choice = new FilterChoice();
                choice.setDisplay("Unchecked");
                choice.setSelected(false);
                choice.setValue("N");
                choices.add(choice);
                values.put("N", choice);
            } else {
                for (Row row : model) {
                    value = row.getCell(column);
                    if (!values.containsKey(value)) {
                        choice = new FilterChoice();
                        values.put(value, choice);
                        choice.setValue(value);
                        choice.setDisplay(renderer.display(value));
                        choices.add(choice);
                    }
                }
            }

            return choices;
        }

        public void setColumn(int column) {
            this.column = column;
        }

        public int getColumn() {
            return column;
        }

        public boolean include(Object value) {
            return values == null || values.get(value).selected;
        }

        public void unselectAll() {
            for (FilterChoice choice : choices)
                choice.setSelected(false);
        }

        public boolean isFilterSet() {
            if (choices == null)
                return false;

            for (FilterChoice choice : choices) {
                if (choice.isSelected())
                    return true;
            }
            return false;
        }
    }

    @Override
    public HandlerRegistration addMouseOverHandler(MouseOverHandler handler) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public int getTabIndex() {
        // TODO Auto-generated method stub
        return 0;
    }

    @Override
    public void setAccessKey(char key) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setFocus(boolean focused) {
        super.setFocus(focused);

    }

    @Override
    public void setTabIndex(int index) {
        // TODO Auto-generated method stub

    }

    @Override
    public void add(IsWidget w) {
        assert w instanceof Column;

        ((Column) w).setTable(this);
        addColumn((Column) w);
    }

    public void onResize() {
        Element parent;

        if (!isAttached())
            return;

        parent = (Element) (getParent() instanceof LayoutPanel
                ? ((LayoutPanel) getParent()).getWidgetContainerElement(this)
                : getParent().getElement());

        int width = parent.getOffsetWidth();
        int height = parent.getOffsetHeight();

        view.setSize(width + "px", height + "px");
        view.onResize();

    }

    @Override
    public void setHeight(String height) {
        super.setHeight(height);
        onResize();
    }

    public void setTipProvider(CellTipProvider tipProvider) {
        this.tipProvider = tipProvider;

        if (toolTip == null)
            setBalloonOptions(new Options());

    }

    @Override
    public Options getBalloonOptions() {
        return toolTip;
    }

    @Override
    public void setBalloonOptions(Options options) {
        toolTip = options;

        options.setPlacement(Placement.MOUSE);

        view.table().addCellMouseOverHandler(new CellMouseOverEvent.Handler() {

            @Override
            public void onCellMouseOver(CellMouseOverEvent event) {
                tipRow = event.getRow();
                tipCol = event.getCol();
                final int x, y;

                Element td = view.table().getCellFormatter().getElement(event.getRow(), event.getCol());

                y = td.getAbsoluteTop();
                x = td.getAbsoluteLeft() + (td.getOffsetWidth() / 2);

                if (!hasExceptions(tipRow, tipCol)) {
                    balloonTimer = new Timer() {
                        public void run() {
                            Balloon.show((HasBalloon) source, x, y);
                        }
                    };
                    balloonTimer.schedule(500);
                }

            }
        });

        options.setTipProvider(new Balloon.TipProvider<Object>() {

            @Override
            public Object getTip(HasBalloon target) {

                if (tipProvider != null)
                    return tipProvider.getTip(tipRow, tipCol);

                return "No Tip Provider set";
            }

        });
    }

    public void setCSS(TableCSS css) {
        css.ensureInjected();
        view.setCSS(css);
    }

    public void insertBefore(Table bef) {
        HorizontalPanel hp = new HorizontalPanel();
        hp.add(bef);
        hp.add(((StaticView) view).scrollView.getWidget());
        ((StaticView) view).scrollView.setWidget(hp);
    }

}