org.openelis.ui.widget.tree.Tree.java Source code

Java tutorial

Introduction

Here is the source code for org.openelis.ui.widget.tree.Tree.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.tree;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;

import org.openelis.ui.common.Util;
import org.openelis.ui.common.data.QueryData;
import org.openelis.ui.messages.Messages;
import org.openelis.ui.resources.TreeCSS;
import org.openelis.ui.resources.UIResources;
import org.openelis.ui.widget.Balloon;
import org.openelis.ui.widget.HasExceptions;
import org.openelis.ui.widget.HasBalloon;
import org.openelis.ui.widget.Queryable;
import org.openelis.ui.widget.ScreenWidgetInt;
import org.openelis.ui.widget.Balloon.Options;
import org.openelis.ui.widget.Balloon.Placement;
import org.openelis.ui.widget.table.CellEditor;
import org.openelis.ui.widget.table.CellRenderer;
import org.openelis.ui.widget.table.CellTipProvider;
import org.openelis.ui.widget.table.Row;
import org.openelis.ui.widget.table.event.BeforeCellEditedEvent;
import org.openelis.ui.widget.table.event.BeforeCellEditedHandler;
import org.openelis.ui.widget.table.event.CellClickedEvent;
import org.openelis.ui.widget.table.event.CellClickedHandler;
import org.openelis.ui.widget.table.event.CellEditedEvent;
import org.openelis.ui.widget.table.event.CellEditedHandler;
import org.openelis.ui.widget.table.event.CellMouseOutEvent;
import org.openelis.ui.widget.table.event.CellMouseOverEvent;
import org.openelis.ui.widget.table.event.HasBeforeCellEditedHandlers;
import org.openelis.ui.widget.table.event.HasCellClickedHandlers;
import org.openelis.ui.widget.table.event.HasCellEditedHandlers;
import org.openelis.ui.widget.table.event.HasUnselectionHandlers;
import org.openelis.ui.widget.table.event.UnselectionEvent;
import org.openelis.ui.widget.table.event.UnselectionHandler;
import org.openelis.ui.widget.tree.event.BeforeNodeAddedEvent;
import org.openelis.ui.widget.tree.event.BeforeNodeAddedHandler;
import org.openelis.ui.widget.tree.event.BeforeNodeCloseEvent;
import org.openelis.ui.widget.tree.event.BeforeNodeCloseHandler;
import org.openelis.ui.widget.tree.event.BeforeNodeDeletedEvent;
import org.openelis.ui.widget.tree.event.BeforeNodeDeletedHandler;
import org.openelis.ui.widget.tree.event.BeforeNodeOpenEvent;
import org.openelis.ui.widget.tree.event.BeforeNodeOpenHandler;
import org.openelis.ui.widget.tree.event.HasBeforeNodeAddedHandlers;
import org.openelis.ui.widget.tree.event.HasBeforeNodeCloseHandlers;
import org.openelis.ui.widget.tree.event.HasBeforeNodeDeletedHandlers;
import org.openelis.ui.widget.tree.event.HasBeforeNodeOpenHandlers;
import org.openelis.ui.widget.tree.event.HasNodeAddedHandlers;
import org.openelis.ui.widget.tree.event.HasNodeClosedHandlers;
import org.openelis.ui.widget.tree.event.HasNodeDeletedHandlers;
import org.openelis.ui.widget.tree.event.HasNodeOpenedHandlers;
import org.openelis.ui.widget.tree.event.NodeAddedEvent;
import org.openelis.ui.widget.tree.event.NodeAddedHandler;
import org.openelis.ui.widget.tree.event.NodeClosedEvent;
import org.openelis.ui.widget.tree.event.NodeClosedHandler;
import org.openelis.ui.widget.tree.event.NodeDeletedEvent;
import org.openelis.ui.widget.tree.event.NodeDeletedHandler;
import org.openelis.ui.widget.tree.event.NodeOpenedEvent;
import org.openelis.ui.widget.tree.event.NodeOpenedHandler;

import com.allen_sauer.gwt.dnd.client.drop.DropController;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
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.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.Event;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FocusPanel;
import com.google.gwt.user.client.ui.HasValue;
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;

/**
 * This class is used by screens to display information in a Tree.
 * 
 * @author tschmidt
 * 
 */
public class Tree extends FocusPanel
        implements ScreenWidgetInt, Queryable, HasBeforeSelectionHandlers<Integer>, HasSelectionHandlers<Integer>,
        HasUnselectionHandlers<Integer>, HasBeforeCellEditedHandlers, HasCellEditedHandlers, HasCellClickedHandlers,
        HasBeforeNodeAddedHandlers, HasNodeAddedHandlers, HasBeforeNodeDeletedHandlers, HasNodeDeletedHandlers,
        HasBeforeNodeOpenHandlers, HasNodeClosedHandlers, HasBeforeNodeCloseHandlers, HasNodeOpenedHandlers,
        HasBalloon, HasValue<Node>, HasExceptions, FocusHandler, RequiresResize {

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

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

    /**
     * Root for the Tree and currently displayed rows
     */
    protected Node root;
    protected ArrayList<Node> modelView;
    protected HashMap<Node, NodeIndex> nodeIndex;

    /**
     * Columns used by the Tree
     */
    protected ArrayList<Column> columns;
    protected HashMap<String, ArrayList<LeafColumn>> nodeDefs;

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

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

    protected Timer balloonTimer;

    /**
     * Tree state values
     */
    protected boolean enabled, multiSelect, editing, hasFocus, queryMode, hasHeader, fixScrollBar = true, showRoot;

    /**
     * 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 TreeDragController dragController;
    protected TreeDropController dropController;

    protected TreeCSS css;

    protected HandlerRegistration visibleHandler;

    protected CellTipProvider tipProvider;

    protected Options toolTip;

    protected int tipRow, tipCol;

    protected Tree source = this;

    private Tree() {
        css = UIResources.INSTANCE.tree();
        css.ensureInjected();

        columns = new ArrayList<Column>(5);
        nodeDefs = new HashMap<String, ArrayList<LeafColumn>>();
        rowHeight = 19;
        view = new StaticView(this);
        setWidget(view);

        /*
         * This Handler takes care of all key events on the tree 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() && getCellEditor(row, col).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);
                                    finishEditing();
                                    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);
                                    finishEditing();
                                    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 (isAnyNodeSelected()) {
                            row = getSelectedNode();
                            while (true) {
                                row++;
                                if (row >= getRowCount())
                                    break;

                                selectNodeAt(row, event.getNativeEvent());

                                if (isNodeSelected(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 (isAnyNodeSelected()) {
                            row = getSelectedNode();
                            while (true) {
                                row--;
                                if (row < 0)
                                    break;

                                selectNodeAt(row, event.getNativeEvent());

                                if (isNodeSelected(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 not editing and a row is selected, focus on first
                    // editable cell
                    if (!isAnyNodeSelected())
                        row = 0;
                    else
                        row = getSelectedNode();

                    col = 0;
                    while (col < getColumnCount()) {
                        if (startEditing(row, col, event.getNativeEvent()))
                            break;
                        col++;
                    }
                    break;
                }
            }
        }, KeyDownEvent.getType());

    }

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

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

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

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

    /**
     * Returns the Root node for this tree.
     * 
     * @return
     */
    public Node getRoot() {
        return root;
    }

    /**
     * Sets the root node for this tree. 
     * 
     * @param model
     */
    public void setRoot(Node root) {
        finishEditing();

        unselectAll();

        this.root = root;

        getDisplayedRows();

        checkExceptions();

        ((StaticView) view).bulkRender();

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

    /**
     * This method is called when the root is changed to create the displayed
     * Node list for the Tree to render.
     */
    private void getDisplayedRows() {
        ArrayList<Node> children;

        modelView = new ArrayList<Node>();
        nodeIndex = new HashMap<Node, NodeIndex>();

        if (root == null)
            return;

        if (showRoot) {
            modelView.add(root);
            nodeIndex.put(root, new NodeIndex(0));
        } else
            root.setOpen(true);

        children = getDisplayedChildren(root);
        for (Node child : children) {
            modelView.add(child);
            nodeIndex.put(child, new NodeIndex(modelView.size() - 1));
        }

    }

    public void checkExceptions() {
        if (endUserExceptions != null) {
            for (Node node : endUserExceptions.keySet()) {
                if (modelView == null || !modelView.contains(node))
                    endUserExceptions.remove(node);
            }

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

        if (validateExceptions != null) {
            for (Node node : validateExceptions.keySet()) {
                if (modelView == null || !modelView.contains(node))
                    validateExceptions.remove(node);
            }

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

    }

    public int getNodeViewIndex(Node node) {
        return modelView.indexOf(node);
    }

    /**
     * This is a recursive method that will return an ArrayList of descendant nodes
     * from the passed node that can be displayed in the Tree View.
     * 
     * @param node
     * @return
     */
    private ArrayList<Node> getDisplayedChildren(Node node) {
        ArrayList<Node> children = new ArrayList<Node>();

        if (node.isOpen && node.getChildCount() > 0) {
            for (Node child : node.children) {
                children.add(child);
                if (!child.isLeaf())
                    children.addAll(getDisplayedChildren(child));
            }
        }

        return children;
    }

    private ArrayList<Node> getAllNodes() {
        ArrayList<Node> children;
        ArrayList<Node> nodes = new ArrayList<Node>();

        if (root == null)
            return nodes;

        if (showRoot)
            nodes.add(root);

        children = getChildren(root);
        for (Node child : children)
            nodes.add(child);

        return nodes;
    }

    private ArrayList<Node> getChildren(Node node) {
        ArrayList<Node> children = new ArrayList<Node>();

        if (node.getChildCount() > 0) {
            for (Node child : node.children) {
                children.add(child);
                if (!child.isLeaf())
                    children.addAll(getDisplayedChildren(child));
            }
        }
        return children;
    }

    private void adjustNodeIndexes(int row, int adj) {
        for (int i = row; i < modelView.size(); i++) {
            nodeIndex.get(modelView.get(i)).index += adj;
        }

    }

    /**
     * Method used to determine if the passed node is currently in the display;
     * 
     * @param node
     * @return
     */
    public boolean isDisplayed(Node node) {
        return nodeIndex.containsKey(node) || node == root;
    }

    /**
     * Method used to toggle a node to an open or close state.  If the node is
     * not currently displayed the method will do nothing.
     * @param node
     */
    public void toggle(Node node) {
        if (isDisplayed(node))
            toggle(nodeIndex.get(node).index, null);
    }

    /**
     * This method will toggle a node to be open or closed depending on its
     * current state. The index passed is the current displayed index of the the
     * node.
     * 
     * @param row
     */
    public void toggle(int row) {
        toggle(row, null);
    }

    protected void toggle(int row, NativeEvent event) {
        if (getNodeAt(row).isOpen())
            close(row, event);
        else
            open(row, event);
    }

    /**
     * This method will open the passed node.  If the node is not currently displayed
     * or the node is already open, the method will do nothing.
     * @param node
     */
    public void open(Node node) {
        if (isDisplayed(node))
            open(nodeIndex.get(node).index, null);
    }

    /**
     * This method will open the node at the displayed index of the tree passed in.
     * After this method is called the display of the tree will change to show all displayable
     * descendant nodes under this node.  If the row is a leaf node or the node is already open 
     * the method will do nothing.  
     * @param row
     */
    public void open(int row) {
        open(row, null);
    }

    protected void open(int row, NativeEvent event) {
        finishEditing();

        if (event != null)
            selectNodeAt(row, event);

        Node node;
        int pos;

        node = getNodeAt(row);

        if (node.isLeaf() || node.isOpen)
            return;

        if (!fireBeforeNodeOpenEvent(row))
            return;

        node.setOpen(true);

        pos = row + 1;

        ArrayList<Node> children = getDisplayedChildren(node);
        for (int i = 0; i < children.size(); i++) {
            modelView.add(pos + i, children.get(i));
            nodeIndex.put(children.get(i), new NodeIndex(pos + i));
        }

        if (children.size() > 0) {
            adjustNodeIndexes(pos + children.size(), children.size());

            //Adjust selction indexes for multiselect so children are not mistaken for selected nodes
            for (int i = 0; i < selections.size(); i++) {
                if (selections.get(i) > row)
                    selections.set(i, selections.get(i) + children.size());
            }
        }

        fireNodeOpenEvent(row);

        view.addNodes(pos, pos + children.size() - 1);

        view.renderView(row, row);
    }

    /**
     * This method will close the passed node.  If the node is not currently displayed
     * or the node is already closed, the method will do nothing.
     * @param node
     */
    public void close(Node node) {
        if (isDisplayed(node))
            close(nodeIndex.get(node).index);
    }

    /**
     * This method will close the node at the displayed index of the tree passed in.
     * After this method is called the display of the tree will change to remove all displayed
     * descendant nodes under this node.  If the row is a leaf node or the node is already closed 
     * the method will do nothing.  
     * @param row
     */
    public void close(int row) {
        close(row, null);
    }

    protected void close(int row, NativeEvent event) {
        int adj = 0, children;

        finishEditing();

        if (event != null)
            selectNodeAt(row, event);

        Node node;
        int pos;

        node = getNodeAt(row);

        if (node.isLeaf() || !node.isOpen)
            return;

        if (!fireBeforeNodeCloseEvent(row))
            return;

        node.setOpen(false);

        pos = row + 1;
        children = 0;
        while (pos < modelView.size() && node.isNodeDescendent(getNodeAt(pos))) {
            nodeIndex.remove(modelView.remove(pos));
            adj--;
            children++;
        }

        adjustNodeIndexes(pos, adj);

        fireNodeCloseEvent(row);

        view.removeNodes(row + 1, children);

        view.renderView(row, row);

    }

    /**
     * This method will expand the tree to the level specified by the param starting at the root.
     * @param level
     */
    public void expand(int level) {
        finishEditing();

        expand(root, level);

        getDisplayedRows();

        ((StaticView) view).bulkRender();

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

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

    /**
     * Private method used to recurse through the tree setting the specified expand level
     * @param node
     * @param level
     */
    private void expand(Node node, int level) {
        if (node.isLeaf())
            return;

        if (node.getLevel() > level) {
            node.setOpen(false);
            return;
        }

        node.setOpen(true);

        if (!node.hasChildren())
            return;

        for (Node child : node.children)
            expand(child, level);

    }

    /**
     * This method will close all nodes in the tree down to the root or if the root is not shown
     * then the root children.
     */
    public void collapse() {
        expand(0);
    }

    /**
     * This method is used to determine if the root node is showed in the
     * display
     * 
     * @return
     */
    public boolean showRoot() {
        return showRoot;
    }

    /**
     * This method will set a flag that is used to determine if the root node
     * should be shown in the Tree display.
     * 
     * @param showRoot
     */
    public void setShowRoot(boolean showRoot) {
        this.showRoot = showRoot;
    }

    /**
     * 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;

        return modelView.size();
    }

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

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

    /**
     * Used to put the tree 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(Scrolling verticalScroll) {
        this.verticalScroll = 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(Scrolling horizontalScroll) {
        this.horizontalScroll = horizontalScroll;
        layout();
    }

    /**
     * Sets a flag to set the size of the tree 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 tree reserves space for the scrollbar
     * 
     * @return
     */
    public boolean getFixScrollbar() {
        return fixScrollBar;
    }

    /**
     * Sets the width of the tree 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 Tree
     * 
     * @return
     */
    public int getWidth() {
        return viewWidth;
    }

    /**
     * Returns the view width of the tree 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 (verticalScroll != Scrolling.NEVER && fixScrollBar  && viewWidth > -1)
        //return viewWidth - 18;

        return viewWidth == -1 ? totalColumnWidth : viewWidth;
    }

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

    /**
     * Adds a Node definition type to the tree.
     * @param key
     * @param def
     */
    public void addNodeDefinition(String key, ArrayList<LeafColumn> def) {
        for (LeafColumn col : def)
            col.setTree(this);

        if (nodeDefs == null)
            nodeDefs = new HashMap<String, ArrayList<LeafColumn>>();
        nodeDefs.put(key, def);
    }

    /**
     * Returns the Node definition for the given type
     * @param key
     * @return
     */
    public ArrayList<LeafColumn> getNodeDefinition(String type) {
        return nodeDefs.get(type);
    }

    /**
     * Returns the Column in the Node definition for the passed type and index 
     * @param key
     * @param index
     * @return
     */
    public LeafColumn getNodeDefinitionAt(String type, int index) {
        return nodeDefs.get(type).get(index);
    }

    /**
     * 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).getName().equals(name))
                return i;
        }
        return -1;
    }

    /**
     * Returns the index of the passed Column
     * @param col
     * @return
     */
    public int getColumn(Column col) {
        return columns.indexOf(col);
    }

    /**
     * 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 tree as a header or not.
     */
    public void setHeader(boolean hasHeader) {
        this.hasHeader = hasHeader;
    }

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

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

        for (Column column : columns)
            column.setTree(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 Tree header.
     * @return The newly created and added column
     */
    public Column addColumn(String name, String label) {
        return addColumnAt(columns.size(), name, label);
    }

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

    /**
     * Creates and inserts a new Column in the tree 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) {
        Column column;

        column = new Column.Builder(75).name(name).label(label).build();
        column.setTree(this);
        columns.add(index, column);
        for (Node node : getAllNodes()) {
            if (node.getCells().size() >= index)
                node.getCells().add(index, null);
        }
        view.addColumn(index);

        return column;
    }

    public void addColumn(Column column) {
        columns.add(columns.size(), column);
        for (Node node : getAllNodes()) {
            if (node.getCells().size() >= columns.size())
                node.getCells().add(columns.size(), null);
        }
        layout();
    }

    /**
     * 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, "", "");
    }

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

        col = columns.remove(index);
        for (Node node : getAllNodes()) {
            if (index < node.getCells().size())
                node.getCells().remove(index);
        }
        view.removeColumn(index);

        return col;
    }

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

    /**
     * Creates a new blank Row and adds it to the bottom of the Tree model.
     * 
     * @return
     */
    public Node addNode(String type) {
        if (root == null)
            setRoot(new Node());

        return addNodeAt(type, root.getChildCount());
    }

    /**
     * Creates a new blank Row and inserts it in the tree model at the passed
     * index.
     * 
     * @param index
     * @return
     */
    public Node addNodeAt(String type, int index) {
        Node node;

        node = new Node(getNodeDefinition(type).size()).setType(type);

        if (root == null)
            setRoot(new Node());

        return addNode(index, root, node);
    }

    /**
     * Adds the passed Row to the end of the Tree model.
     * 
     * @param row
     * @return
     */
    public Node addNode(Node node) {
        if (root == null)
            setRoot(new Node());

        return addNode(root.getChildCount(), root, node);
    }

    /**
     * Adds the passed Row into the Tree model at the passed index.
     * 
     * @param index
     * @param row
     * @return
     */
    public Node addNodeAt(int index, Node node) {
        return addNode(index, root, node);
    }

    public void addNodeAfter(Node selected, Node node) {
        Node parent = selected.getParent();

        if (selected.isLastChild())
            addNodeAt(parent, node);
        else
            addNodeAt(parent, node, selected.getChildIndex() + 1);
    }

    public void addNodeBefore(Node selected, Node node) {
        Node parent = selected.getParent();

        if (selected.isFirstChild())
            addNodeAt(parent, node, 0);
        else
            addNodeAt(parent, node, selected.getChildIndex() - 1);
    }

    /**
     * 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 Tree 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.
     */
    private Node addNode(int index, Node parent, Node node) {
        assert (node != null);

        ArrayList<Node> children;
        int pos;

        finishEditing();

        if (!fireBeforeNodeAddedEvent(index, parent, node))
            return null;

        unselectAll();

        if (parent.isOpen) {
            if (parent == root) {
                if (index == root.getChildCount())
                    pos = getRowCount();
                else
                    pos = nodeIndex.get(root.getChildAt(index)).index;

            } else if (parent.getChildCount() == 0)
                pos = nodeIndex.get(parent).index + 1;
            else if (index >= parent.getChildCount())
                pos = nodeIndex.get(parent.getLastChild()).index + 1;
            else
                pos = nodeIndex.get(parent.getChildAt(index)).index;

            parent.add(node, index);

            modelView.add(pos, node);
            nodeIndex.put(node, new NodeIndex(pos));

            pos++;
            children = getDisplayedChildren(node);
            for (int i = 0; i < children.size(); i++) {
                modelView.add(pos + i, children.get(i));
                nodeIndex.put(children.get(i), new NodeIndex(pos + i));
            }

            adjustNodeIndexes(pos + children.size(), children.size() + 1);

            view.addNodes(pos - 1, pos - 1 + children.size());
        } else
            parent.add(node, index);

        fireRowAddedEvent(index, parent, node);

        return node;
    }

    /**
     * Method will add the child node to the passed parent node at the end 
     * of the parents child list.
     * 
     * @param parent
     * @param child
     */
    public void addNodeAt(Node parent, Node child) {
        addNodeAt(parent, child, parent.getChildCount());
    }

    /**
     * Method will add the child node to the passed parent node at the position 
     * specified by the index parameter
     * @param parent
     * @param child
     * @param index
     */
    public void addNodeAt(Node parent, Node child, int index) {
        if (!isDisplayed(parent) || !parent.isOpen()) {
            parent.add(child, index);
            view.renderView(nodeIndex.get(parent).index, nodeIndex.get(parent).index);
            fireRowAddedEvent(index, parent, child);
        } else
            addNode(index, parent, child);
    }

    /**
     * Method will delete a row from the model at the specified index and
     * refersh the view.
     * 
     * @param index
     * @return
     */
    public Node removeNodeAt(int index) {
        int adj = 0;
        Node node, parent;
        boolean lastChild;

        finishEditing();

        unselectAll();

        node = getNodeAt(index);

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

        while (index < modelView.size() && node.isNodeDescendent(modelView.get(index))) {
            nodeIndex.remove(modelView.remove(index));
            adj--;
        }

        adjustNodeIndexes(index, adj);

        lastChild = node.isLastChild();
        parent = node.getParent();
        parent.remove(node);

        view.removeNodes(index, 1 + (node.isOpen ? node.getChildCount() : 0));

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

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

        fireRowDeletedEvent(index, node);

        if (parent != root && parent.isOpen && lastChild) {
            close(parent);
            open(parent);
        } else {
            if (parent != root) {
                view.renderView(nodeIndex.get(parent).index, nodeIndex.get(parent).index);
            }
        }

        return node;
    }

    /**
     * Method will remove the passed node form the tree model and refresh the 
     * view.
     * 
     * @param node
     */
    public void removeNode(Node node) {
        if (!isDisplayed(node)) {
            if (fireBeforeRowDeletedEvent(-1, node)) {
                node.removeFromParent();
            }
            fireRowDeletedEvent(-1, node);
        } else {
            removeNodeAt(nodeIndex.get(node).index);
        }
    }

    /**
     * Set the model for this tree to null and redraws a blank view
     */
    public void removeAllNodes() {
        finishEditing();
        unselectAll();
        root = null;
        modelView = null;
        view.removeAllNodes();
    }

    /**
     * Returns the Row at the specified index in the display model
     * 
     * @param row
     * @return
     */
    public Node getNodeAt(int index) {
        if (index < 0 || index >= getRowCount())
            return null;
        return modelView.get(index);
    }

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

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

    public ArrayList<Node> getAllSelectedNodes() {
        ArrayList<Node> nodes;

        nodes = new ArrayList<Node>();
        for (Integer index : selections) {
            nodes.add(getNodeAt(index));
        }
        return nodes;
    }

    /**
     * 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 selectNodeAt(int index) {
        selectNodeAt(index, null);
    }

    /**
     * Selects the node passed to the mehtod. 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 node is not currently displayed 
     * the selection will not occur.
     * 
     * @param index
     */
    public void selectNodeAt(Node node) {
        selectNodeAt(node, 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 addTo is passed as true and the table
     * allows multiple selection the row will be added to the current list of
     * selections.
     * 
     * @param index
     */
    public void selectNodeAt(int index, NativeEvent event) {
        if (index < 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 (event != null && Event.getTypeInt(event.getType()) == Event.ONCLICK) {
                multiSelect(index, event);
                return;
            }
        }

        if (isNodeSelected(index))
            return;

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

            finishEditing();

            selections.add(index);

            view.applySelectionStyle(index);

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

            scrollToVisible(index);
        }
    }

    /**
     * Selects the node passed in to the method. 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 addTo is passed as true and the table
     * allows multiple selection the row will be added to the current list of
     * selections. If the node is not currently displayed the selection will not
     * occur
     * 
     * @param index
     */
    public void selectNodeAt(Node node, NativeEvent event) {
        if (isDisplayed(node))
            selectNodeAt(nodeIndex.get(node).index, event);
    }

    public void selectAll() {
        if (isMultipleSelectionAllowed()) {
            selections = new ArrayList<Integer>();
            for (int i = 0; i < getRowCount(); i++)
                selections.add(i);
            view.selectAll();
        }
    }

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

        startSelect = node;
        endSelect = node;

        ctrlKey = event.getCtrlKey();
        shiftKey = event.getShiftKey();

        if (ctrlKey) {
            if (isNodeSelected(node)) {
                unselectNodeAt(node, event);
                return;
            }
        } else if (shiftKey) {
            if (!isAnyNodeSelected()) {
                startSelect = 0;
                endSelect = node;
            } else {
                Collections.sort(selections);
                minSelected = Collections.min(selections);
                maxSelected = Collections.max(selections);
                if (minSelected > node) {
                    startSelect = node;
                    endSelect = minSelected;
                } else if (node > maxSelected) {
                    startSelect = maxSelected;
                    endSelect = node;
                } else {
                    i = 0;
                    while (selections.get(i + 1) < node)
                        i++;
                    startSelect = selections.get(i);
                    endSelect = node;
                }
            }
            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);

    }

    public void unselectNodeAt(int index) {
        unselectNodeAt(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 unselectNodeAt(int index, NativeEvent event) {
        if (selections.contains(index)) {
            finishEditing();
            if (event != null)
                fireUnselectEvent(index);
            selections.remove(new Integer(index));
            view.applyUnselectionStyle(index);
        }
    }

    /**
     * Unselects the passed node 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
     */
    public void unselectNodeAt(Node node) {
        if (isDisplayed(node))
            unselectNodeAt(nodeIndex.get(node).index, null);
    }

    /**
     * Returns the selected index of the first row selected
     * 
     * @return
     */
    public int getSelectedNode() {
        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 isNodeSelected(int index) {
        return selections.contains(index);
    }

    /**
     * Used to determine if the passed node is currently in the selection
     * @param node
     * @return
     */
    public boolean isNodeSelected(Node node) {
        if (isDisplayed(node))
            return isNodeSelected(nodeIndex.get(node).index);
        return false;
    }

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

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

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

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

    /**
     * Private method that will fire a BeforeNodeOpenEvent for the passed
     * index. Returns false if the open is canceled by registered handler
     * and true if the open is allowed.
     */
    private boolean fireBeforeNodeOpenEvent(int index) {
        BeforeNodeOpenEvent event = null;

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

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

    /**
     * Private method that will fire a NodeOpenedEvent for the passed index to
     * notify all registered handlers that the Node at the passed index was opened.
     * Returns true as a default.
     * 
     * @param index
     * @return
     */
    private void fireNodeOpenEvent(int index) {
        if (!queryMode)
            NodeOpenedEvent.fire(this, index, getNodeAt(index));
    }

    /**
     * Private method that will fire a BeforeNodeCloseEvent for the passed
     * index. Returns false if the close is canceled by registered handler
     * and true if the close is allowed.
     */
    private boolean fireBeforeNodeCloseEvent(int index) {
        BeforeNodeCloseEvent event = null;

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

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

    /**
     * Private method that will fire a NodeClosedEvent for the passed index to
     * notify all registered handlers that the Node at the passed index was closed.
     * Returns true as a default.
     * 
     * @param index
     * @return
     */
    private void fireNodeCloseEvent(int index) {
        if (!queryMode)
            NodeClosedEvent.fire(this, index, getNodeAt(index));
    }

    /**
     * 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
     */
    public 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 fireBeforeNodeAddedEvent(int index, Node parent, Node node) {
        BeforeNodeAddedEvent event = null;

        if (!queryMode)
            BeforeNodeAddedEvent.fire(this, index, parent, node);

        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, Node parent, Node node) {

        if (!queryMode)
            NodeAddedEvent.fire(this, index, parent, node);

        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, Node row) {
        BeforeNodeAddedEvent event = null;

        if (!queryMode)
            BeforeNodeDeletedEvent.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, Node row) {

        if (!queryMode)
            NodeDeletedEvent.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();
    }

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

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

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

        column = getNodeDefinition(modelView.get(row).getType()).get(col);

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

        if (column.isRequired() && value == null)
            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 void setRowAt(int index, Node node) {
        Node parent, replace;
        ArrayList<Node> children;
        int pos;
        int childIndex;

        finishEditing();

        replace = getNodeAt(index);
        parent = replace.getParent();
        childIndex = parent.getIndex(replace);

        if (replace.isOpen()) {
            while (node.isNodeDescendent(modelView.get(index)))
                nodeIndex.remove(modelView.remove(index));
        }

        replace.removeFromParent();

        modelView.set(index, node);
        nodeIndex.put(node, new NodeIndex(index));

        if (node.isOpen()) {
            pos = index + 1;
            children = getDisplayedChildren(node);
            for (int i = 0; i < children.size(); i++) {
                modelView.add(pos + i, children.get(i));
                nodeIndex.put(children.get(i), new NodeIndex(pos + i));
            }
        }

        parent.add(node, childIndex);

        if (!replace.isOpen() && !node.isOpen())
            renderView(index, index);
        else
            renderView(index, -1);
    }

    /**
     * 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)
            return null;
        return 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 Tree and readies it for user
     * input. event is passed to this method by view click handler 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 tree is not enable or the passed cell is already
         * being edited
         */
        if (!isEnabled() || (row == editingRow && col == editingCol))
            return false;

        finishEditing();

        selectNodeAt(row, event);

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

        // If a column is outside the definition of this column then return false
        if (col >= getNodeDefinition(getNodeAt(row).getType()).size())
            return false;

        // If a column is not editable then return false
        if (!getNodeDefinitionAt(getNodeAt(row).getType(), 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 tree. 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;
    }

    @SuppressWarnings("rawtypes")
    protected CellEditor getCellEditor(int r, int c) {
        return getNodeDefinitionAt(getNodeAt(r).getType(), c).getCellEditor();
    }

    @SuppressWarnings("rawtypes")
    protected CellRenderer getCellRenderer(int r, int c) {
        return getNodeDefinitionAt(getNodeAt(r).getType(), c).getCellRenderer();
    }

    // ********* 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 tree 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 tree when any part of its physical definition is changed.
     */
    protected void layout() {
        computeColumnsWidth();
        view.layout();
    }

    /**
     * Method called when a column width has been set to resize the tree
     * columns
     */
    protected void resize() {
        if (!isAttached()) {
            layout();
            return;
        }

        finishEditing();

        computeColumnsWidth();

        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
     */
    private void computeColumnsWidth() {
        int from, to;

        //
        // compute total width
        //
        totalColumnWidth = 0;
        xForColumn = new short[getColumnCount()];
        for (int i = 0; i < getColumnCount(); i++) {
            xForColumn[i] = (short) totalColumnWidth;
            totalColumnWidth += getColumnAt(i).getWidth();
        }
        //
        // mark the array
        //
        from = 0;
        columnForX = new short[totalColumnWidth];
        for (int i = 0; i < getColumnCount(); i++) {
            to = from + getColumnAt(i).getWidth();
            while (from < to)
                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);
    }

    public void refreshNode(Node node) {
        view.renderView(nodeIndex.get(node).index, nodeIndex.get(node).index);
    }

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

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

    }

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

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

    /**
     * Removes the Focus style from the Tree
     */
    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 tree 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 tree into and out of query mode.
     */
    public void setQueryMode(boolean query) {
        assert (getNodeDefinition("query") != null);

        Node root;

        if (query == queryMode)
            return;

        this.queryMode = query;
        if (query) {
            if (!showRoot) {
                root = new Node();
                root.add(new Node(getNodeDefinition("query").size()).setType("query"));
            } else
                root = new Node(getNodeDefinition("query").size()).setType("query");
            setRoot(root);
        } else
            setRoot(null);
    }

    /**
     * Method to determine if Tree 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) {
        Node key;

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

    public void addException(Node node, int col, Exception error) {
        int r;

        getEndUserExceptionList(node, col).add(error);

        if (nodeIndex != null && nodeIndex.containsKey(node)) {
            r = nodeIndex.get(node).index;
            view.renderExceptions(r, r);
        }
    }

    /**
     * Adds a manual Exception to the widgets exception list.
     */
    public void addException(int row, int col, Exception error) {
        getEndUserExceptionList(getNodeAt(row), col).add(error);
        view.renderExceptions(row, row);
    }

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

        node = getNodeAt(row);
        setValidateException(node, col, errors);
    }

    public void setValidateException(Node node, int col, ArrayList<Exception> errors) {
        HashMap<Integer, ArrayList<Exception>> cellExceptions = null;
        HashMap<Integer, ArrayList<Exception>> rowExceptions;

        // 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(node)) {
                rowExceptions = validateExceptions.get(node);
                rowExceptions.remove(col);
                if (rowExceptions.isEmpty())
                    validateExceptions.remove(node);
            }
            return;
        }

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

            validateExceptions.put(node, cellExceptions);
        }

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

        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(getNodeAt(row)))
                return validateExceptions.get(getNodeAt(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(getNodeAt(row)))
                return endUserExceptions.get(getNodeAt(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(int row, int col) {
        if (row < getRowCount())
            clearExceptions(getNodeAt(row), col);
    }

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

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

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

        if (nodeIndex != null && nodeIndex.containsKey(node))
            view.renderExceptions(nodeIndex.get(node).index, nodeIndex.get(node).index);

    }

    public void clearEndUserExceptions(int row, int col) {
        if (row < getRowCount())
            clearEndUserExceptions(getNodeAt(row), col);
    }

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

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

        if (nodeIndex != null && nodeIndex.containsKey(node))
            view.renderExceptions(nodeIndex.get(node).index, nodeIndex.get(node).index);

    }

    /**
     * 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(Node node, int col) {
        HashMap<Integer, ArrayList<Exception>> cellExceptions = null;
        ArrayList<Exception> list = null;

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

        cellExceptions = endUserExceptions.get(node);

        if (cellExceptions == null) {
            cellExceptions = new HashMap<Integer, ArrayList<Exception>>();
            endUserExceptions.put(node, 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) {
        Node key;

        key = getNodeAt(row);
        return getValidateExceptionList(key, col);
    }

    private ArrayList<Exception> getValidateExceptionList(Node key, int col) {
        HashMap<Integer, ArrayList<Exception>> cellExceptions = null;
        ArrayList<Exception> list;
        if (validateExceptions == null)
            validateExceptions = new HashMap<Node, 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(int row, int col, int x, int y) {
        if (row == editingRow && col == editingCol)
            return;

        Balloon.drawExceptions(getEndUserExceptions(row, col), getValidateExceptions(row, col),
                view.table().getCellFormatter().getElement(row, col), x, y);
    }

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

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

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

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

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

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

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

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

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

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

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

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

    /**
     * Registers a BeforeRowAddedHandler to this Tree
     */
    public HandlerRegistration addBeforeNodeAddedHandler(BeforeNodeAddedHandler handler) {
        return addHandler(handler, BeforeNodeAddedEvent.getType());
    }

    /**
     * Registers a RowAddedHandler to this Tree
     */
    public HandlerRegistration addNodeAddedHandler(NodeAddedHandler handler) {
        return addHandler(handler, NodeAddedEvent.getType());
    }

    /**
     * Registers a BeforeRowDeletedHandler to this Tree
     */
    public HandlerRegistration addBeforeNodeDeletedHandler(BeforeNodeDeletedHandler handler) {
        return addHandler(handler, BeforeNodeDeletedEvent.getType());
    }

    /**
     * Registers a RowDeletedHandler to this Tree
     */
    public HandlerRegistration addNodeDeletedHandler(NodeDeletedHandler handler) {
        return addHandler(handler, NodeDeletedEvent.getType());
    }

    /**
     * Registers a BeforeNodeOpenHandler to this Tree
     */
    public HandlerRegistration addBeforeNodeOpenHandler(BeforeNodeOpenHandler handler) {
        return addHandler(handler, BeforeNodeOpenEvent.getType());
    }

    /**
     * Registers a NodeClosedHandler to this Tree
     */
    public HandlerRegistration addNodeClosedHandler(NodeClosedHandler handler) {
        return addHandler(handler, NodeClosedEvent.getType());
    }

    /**
     * Registers a BeforeNodeClosedHandler to this Tree
     */
    public HandlerRegistration addBeforeNodeCloseHandler(BeforeNodeCloseHandler handler) {
        return addHandler(handler, BeforeNodeCloseEvent.getType());
    }

    /**
     * Registers a NodeOpenEvent to this Tree
     */
    public HandlerRegistration addNodeOpenedHandler(NodeOpenedHandler handler) {
        return addHandler(handler, NodeOpenedEvent.getType());
    }

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

    /**
     * This method will check the model to make sure that all required cells
     * have values
     */
    public void validate() {
        HashSet<String> required = new HashSet<>();

        finishEditing();

        for (String type : nodeDefs.keySet()) {
            ArrayList<LeafColumn> columns = nodeDefs.get(type);
            for (int i = 0; i < columns.size(); i++) {
                if (columns.get(i).isRequired()) {
                    required.add(type);
                }
            }
        }

        if (required.isEmpty()) {
            return;
        }

        validateNode(required, root);

        if (validateExceptions != null) {
            getDisplayedRows();
            ((StaticView) view).bulkRender();

            ((StaticView) view).bulkExceptions(validateExceptions);

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

    public void validateNode(HashSet<String> required, Node node) {
        if (required.contains(node.getType())) {
            for (int i = 0; i < nodeDefs.get(node.type).size(); i++) {
                if (nodeDefs.get(node.type).get(i).isRequired()) {
                    if (node.getCell(i) == null) {
                        ArrayList<Exception> exceptions = getValidateExceptionList(node, i);
                        exceptions.add(new Exception(Messages.get().exc_fieldRequired()));
                        setValidateException(node, i, exceptions);
                        node.isOpen = true;
                        Node parent = node.getParent();
                        while (!parent.isOpen) {
                            parent.isOpen = true;
                            parent = parent.getParent();
                        }
                    }
                }
            }
        }
        if (node.hasChildren()) {
            for (Node child : node.children()) {
                validateNode(required, child);
            }
        }
    }

    /**
     * Returns the model as part of the HasValue interface
     */
    public Node getValue() {
        return root;
    }

    /**
     * Sets the model as part of the HasValue interface
     */
    public void setValue(Node value) {
        setValue(value, false);
    }

    /**
     * Sets the model and will fire ValueChangeEvent if fireEvents is true as
     * part of the HasValue interface
     */
    public void setValue(Node value, boolean fireEvents) {
        setRoot(value);

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

    }

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

    public void addException(Exception exception) {
        // TODO Auto-generated method stub

    }

    public void addExceptionStyle() {
        // TODO Auto-generated method stub

    }

    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() {
        // TODO Auto-generated method stub

    }

    private class NodeIndex {
        int index;

        public NodeIndex(int index) {
            this.index = index;
        }
    }

    @Override
    public void add(IsWidget w) {
        if (w instanceof Column) {
            ((Column) w).setTree(this);
            addColumn((Column) w);
        } else if (w instanceof Leaf) {
            addNodeDefinition(((Leaf) w).getKey(), ((Leaf) w).getColumns());
        }
    }

    public void onResize() {
        Element 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";
            }

        });
    }
}