A Tree Table : Tree Table « GWT « Java






A Tree Table

  

/*
 * Copyright 2006 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
 
package com.java2s.gwt.client;
import java.util.Iterator;
import java.util.Vector;
import java.util.EventListener;
import java.util.List;
import java.util.ArrayList;


import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.KeyboardListenerCollection;
import com.google.gwt.user.client.ui.MouseListener;
import com.google.gwt.user.client.ui.MouseListenerCollection;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwt.user.client.ui.Widget;


import com.google.gwt.user.client.ui.HasFocus;
import com.google.gwt.user.client.ui.HasHTML;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Tree;

import com.google.gwt.core.client.EntryPoint;

import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.RootPanel;


public class GWTClient implements EntryPoint{

  /**
   * This is the entry point method.
   */
  public void onModuleLoad() {
    HorizontalPanel p = new HorizontalPanel();
    TreeTable fileTreeTable = createFileTreeTable();
    p.add(fileTreeTable);
    TreeTable treeTable = createToDoTreeTable();
    p.add(treeTable);
    treeTable = createSimpleTreeTable();
    p.add(treeTable);
    RootPanel.get("slot1").add(p);

    // Adds a few items after display
    fileTreeTable.addItem(new File("File E", "1 TB", "Jan 1, 2005"));
    fileTreeTable.getItem(0).addItem(new File("File E", "1 TB", "Jan 1, 2005"));
  }
  
  /**
   * Creates an example tree using the default renderer. Wigdets are 
   * rendered, objects are rendered with toString(), and array values
   * are inserted across the table. 
   */
  public TreeTable createSimpleTreeTable() {
    TreeTable treeTable = new TreeTable();
    
    TreeItem item = treeTable.addItem("I'm text");
    item.addItem("I'm <b>HTML</b>");
    item.setState(true);
    item = treeTable.addItem(new CheckBox("I'm a Widget!!!"));
    item.addItem("Child");
    item = treeTable.addItem("Parent");
    item.addItem("Child");
    treeTable.addItem(new Object[] {new CheckBox("I'm in an array"), "1", "2", new CheckBox("3")});
    
    return treeTable;
  }
  
  /**
   * Creates an example tree using a custom renderer. File objects are 
   * added as user objects and the renderer displays values. 
   * @return
   */
  public TreeTable createFileTreeTable() {
    TreeTable treeTable = new TreeTable();
    treeTable.setBorderWidth(1);
    treeTable.setCellPadding(3);
    treeTable.setCellSpacing(0);
    
    TreeItem item = treeTable.addItem(new File("Folder A", "-", "Apr 4, 2007"));
    item.addItem(new File("File AA", "128 kb", "Apr 4, 2007"));
    item.addItem(new File("File AB", "64 kb", "Apr 1, 2007"));
    item = treeTable.addItem(new File("Folder B", "-", "Jan 21, 2006"));
    item.addItem(new File("File BA", "256 kb", "Jan 18, 2006"));
    item = item.addItem(new File("Folder BB", "-", "Jan 21, 2006"));
    item.addItem(new File("File BBA", "256 kb", "Jan 18, 2006"));
    item.addItem(new File("File BBB", "256 kb", "Jan 18, 2006"));
    item.addItem(new File("File BBC", "256 kb", "Jan 18, 2006"));
    item.addItem(new File("File BBD", "256 kb", "Jan 21, 2006"));
    treeTable.addItem(new File("File C", "256 kb", "Jan 18, 2006"));
    treeTable.addItem(new File("File D", "256 kb", "Jan 18, 2006"));
    
    treeTable.setRenderer(new ExampleRenderer());
    
    return treeTable;
  }
  
  /**
   * Creates an example tree using a custom renderer. ToDo objects
   * are added as the user objects of TreeItems. The renderer turns
   * them into Widgets. 
   * @return
   */
  public TreeTable createToDoTreeTable() {
    TreeTable treeTable = new TreeTable();
    TreeItem grp1 = treeTable.addItem("Group 1");
    grp1.addItem(new ToDo("Garbage", "3 days", "Matt"));
    grp1.addItem(new ToDo("Dishes", "1 day", "Matt"));
    grp1.addItem(new ToDo("Laundry", "2 days", "Matt"));
    TreeItem grp2 = treeTable.addItem("Group 2");
    grp2.addItem(new ToDo("Task 1", "2 days", "Unassigned"));
    grp2.addItem(new ToDo("Task 2", "3 day", "Unassigned"));
    grp2.addItem(new ToDo("Task 3", "7 days", "Unassigned"));
    
    treeTable.setRenderer(new ExampleRenderer());
    
    return treeTable;
  }
  
  class ExampleRenderer implements TreeTableRenderer {
    public void renderTreeItem(TreeTable table, TreeItem item, int row) {
      Object obj = item.getUserObject();
      if (obj instanceof ToDo) {
        ToDo todo = (ToDo) obj;
        item.setWidget(new CheckBox(todo.name));
        table.setText(row, 1, todo.due);
        table.setText(row, 2, todo.who);
      } else if (obj instanceof File) {
        File f = (File) obj;
        item.setText(f.name);
        table.setText(row, 1, f.size);
        table.setText(row, 2, f.date);
      } else if (obj != null) {
        item.setText(obj.toString());
      }
    }
  }
  
  public class File {
    public String name;
    public String size;
    public String date;
    public File(String n, String s, String d) {
      name = n;
      size = s;
      date = d;
    }
    
    public String toString() {
      return name;
    }
  }
  
  public class ToDo {
    public String name;
    public String due;
    public String who;
    public ToDo(String n, String d, String w) {
      name = n;
      due = d;
      who = w;
    }
    
    public String toString() {
      return name;
    }
  }
}



/**
 * Shameless copy of com.google.gwt.user.client.ui.TreeItem. GWT's TreeItem does
 * not expose enough to allow a simple subclass. If that changes, this class
 * will be greatly simplified.
 * 
 * Changes:
 * <li>Removed the DOM hierarchy of tree nodes. Each node is
 * independent and therefore placable is a table cell.</li>
 * <li>Changed subclass to Widget so the TreeItem could be added to a table.</li> 
 * <li>Changed parent Tree to TreeTable.</li>
 * <li>Worked around package scope methods from the GWT ui package.</li>
 * <li>Removed ContentPanel.</li>
 * <li>Added row index cache.</li>
 * </ul>
 * 
 * @author Matt Boyd (modifications to GWT's classes)
 */
class TreeItem extends Widget implements HasHTML {

  private Vector children = new Vector();

  private Element itemTable, contentElem, imgElem;

  private boolean open;

  private TreeItem parentItem;

  private boolean selected;

  private Object userObject;

  private TreeTable table;

  private int row;
  
  private Widget widget;

  /**
   * Creates an empty tree item.
   */
  public TreeItem() {
    setElement(DOM.createDiv());
    itemTable = DOM.createTable();
    contentElem = DOM.createSpan();
    imgElem = DOM.createImg();

    // Uses the following Element hierarchy:
    // <div (handle)>
    // <table (itemElem)>
    // <tr>
    // <td><img (imgElem)/></td>
    // <td><span (contents)/></td>
    // </tr>
    // </table>
    // </div>

    Element tbody = DOM.createTBody(), tr = DOM.createTR();
    Element tdImg = DOM.createTD(), tdContent = DOM.createTD();
    DOM.appendChild(itemTable, tbody);
    DOM.appendChild(tbody, tr);
    DOM.appendChild(tr, tdImg);
    DOM.appendChild(tr, tdContent);
    DOM.setStyleAttribute(tdImg, "verticalAlign", "middle");
    DOM.setStyleAttribute(tdContent, "verticalAlign", "middle");

    DOM.appendChild(getElement(), itemTable);
    DOM.appendChild(tdImg, imgElem);
    DOM.appendChild(tdContent, contentElem);

    DOM.setAttribute(getElement(), "position", "relative");
    DOM.setStyleAttribute(contentElem, "display", "inline");
    DOM.setStyleAttribute(getElement(), "whiteSpace", "nowrap");
    DOM.setAttribute(itemTable, "whiteSpace", "nowrap");
    setStyleName(contentElem, "gwt-TreeItem", true);
  }
  
  public TreeItem(Object userObj) {
    this();
    setUserObject(userObj);
  }

  /**
   * Adds a child tree item containing the specified text.
   * 
   * @param itemText
   *            the text to be added
   * @return the item that was added
   */
  public TreeItem addItem(String itemText) {
    TreeItem ret = new TreeItem(itemText);
    addItem(ret);
    return ret;
  }
  
  public TreeItem addItem(Object userObj) {
    TreeItem ret = new TreeItem(userObj);
    addItem(ret);
    return ret;
  }
  
  /**
   * Adds another item as a child to this one.
   * 
   * @param item
   *            the item to be added
   */
  public void addItem(TreeItem item) {
    // If this element already belongs to a tree or tree item, it should be
    // removed.
    if ((item.getParentItem() != null) || (item.getTreeTable() != null)) {
      item.remove();
    }
    item.setTreeTable(table);
    item.setParentItem(this);
    children.add(item);
    int d = item.getDepth();
    if (d != 0) {
      DOM.setStyleAttribute(item.getElement(), "marginLeft", (d * 16) + "px");
    }
    if (table != null) {
      table.insertItem(item, getRow() + getChildCount());
      table.updateRowCache();
      table.updateVisibility(item);
    }

    if (children.size() == 1) {
      updateState();
    }
  }

  public int getRow() {
    return row;
  }

  void setRow(int r) {
    row = r;
  }

  /**
   * Returns the depth of this item. Depth of root child is 0.
   * 
   * @return
   */
  public int getDepth() {
    if (parentItem == null) {
      return 0;
    }
    return parentItem.getDepth() + 1;
  }

  /**
   * Returns the count of all descendents; includes this item in the count.
   * 
   * @return
   */
  int getDescendentCount() {
    int d = 1;
    for (int i = getChildCount() - 1; i >= 0; i--) {
      d += getChild(i).getDescendentCount();
    }
    return d;
  }

  /**
   * Adds a child tree item containing the specified widget.
   * 
   * @param widget
   *            the widget to be added
   * @return the item that was added
   */
  public TreeItem addItem(Widget widget) {
    TreeItem ret = new TreeItem(widget);
    addItem(ret);
    return ret;
  }

  /**
   * Gets the child at the specified index.
   * 
   * @param index
   *            the index to be retrieved
   * @return the item at that index
   */

  public TreeItem getChild(int index) {
    if ((index < 0) || (index >= children.size())) {
      return null;
    }

    return (TreeItem) children.get(index);
  }

  /**
   * Gets the number of children contained in this item.
   * 
   * @return this item's child count.
   */

  public int getChildCount() {
    return children.size();
  }

  /**
   * Gets the index of the specified child item.
   * 
   * @param child
   *            the child item to be found
   * @return the child's index, or <code>-1</code> if none is found
   */

  public int getChildIndex(TreeItem child) {
    return children.indexOf(child);
  }

  public String getHTML() {
    return DOM.getInnerHTML(contentElem);
  }

  /**
   * Gets this item's parent.
   * 
   * @return the parent item
   */
  public TreeItem getParentItem() {
    return parentItem;
  }

  /**
   * Gets whether this item's children are displayed.
   * 
   * @return <code>true</code> if the item is open
   */
  public boolean getState() {
    return open;
  }

  public boolean isOpen() {
    return getState();
  }

  public String getText() {
    return DOM.getInnerText(contentElem);
  }

  /**
   * Gets the tree that contains this item.
   * 
   * @return the containing tree
   */
  public TreeTable getTreeTable() {
    return table;
  }

  /**
   * Gets the user-defined object associated with this item.
   * 
   * @return the item's user-defined object
   */
  public Object getUserObject() {
    return userObject;
  }

  /**
   * Gets the <code>Widget</code> associated with this tree item.
   * 
   * @return the widget
   */
  public Widget getWidget() {
    return widget;
  }

  /**
   * Determines whether this item is currently selected.
   * 
   * @return <code>true</code> if it is selected
   */
  public boolean isSelected() {
    return selected;
  }

  /**
   * Removes this item from its tree.
   */
  public void remove() {
    if (parentItem != null) {
      // If this item has a parent, remove self from it.
      parentItem.removeItem(this);
    } else if (table != null) {
      // If the item has no parent, but is in the Tree, it must be a
      // top-level
      // element.
      table.removeItem(this);
    }
  }

  /**
   * Removes one of this item's children.
   * 
   * @param item
   *            the item to be removed
   */

  public void removeItem(TreeItem item) {
    if (!children.contains(item)) {
      return;
    }
    // Update Item state.
    item.setTreeTable(null);
    item.setParentItem(null);

    children.remove(item);
    if (table != null) {
      table.removeItemFromTable(item);
    }

    if (children.size() == 0) {
      updateState();
    }
  }

  /**
   * Removes all of this item's children.
   */
  public void removeItems() {
    while (getChildCount() > 0) {
      removeItem(getChild(0));
    }
  }

  public void setHTML(String html) {
    DOM.setInnerHTML(contentElem, html);
//    if (widget != null) {
//      DOM.removeChild(contentElem, widget.getElement());
//      widget = null;
//    }
  }

  /**
   * Selects or deselects this item.
   * 
   * @param selected
   *            <code>true</code> to select the item, <code>false</code>
   *            to deselect it
   */
  public void setSelected(boolean selected) {
    if (this.selected == selected) {
      return;
    }
    this.selected = selected;
    setStyleName(contentElem, "gwt-TreeItem-selected", selected);
  }

  /**
   * Sets whether this item's children are displayed.
   * 
   * @param open
   *            whether the item is open
   */
  public void setState(boolean open) {
    setState(open, true);
  }

  /**
   * Sets whether this item's children are displayed.
   * 
   * @param open
   *            whether the item is open
   * @param fireEvents
   *            <code>true</code> to allow open/close events to be fired
   */
  public void setState(boolean open, boolean fireEvents) {
    if (open && children.size() == 0) {
      return;
    }

    this.open = open;
    if (open) {
      table.showChildren(this);
    } else {
      table.hideChildren(this);
    }
    updateState();

    if (fireEvents) {
      table.fireStateChanged(this);
    }
  }

  public void setText(String text) {
    DOM.setInnerText(contentElem, text);
//    if (widget != null) {
//      DOM.removeChild(contentElem, widget.getElement());
//      widget = null;
//    }
  }

  /**
   * Sets the user-defined object associated with this item.
   * 
   * @param userObj
   *            the item's user-defined object
   */
  public void setUserObject(Object userObj) {
    userObject = userObj;
  }

  /**
   * Sets the current widget. Any existing child widget will be removed.
   * 
   * @param widget
   *            Widget to set
   */
  public void setWidget(Widget w) {
    if (widget != null) {
      DOM.removeChild(contentElem, widget.getElement());
//      widget.setParent(null);
    }
    
    if (w != null) {
      widget = w;
      DOM.setInnerText(contentElem, null);
      DOM.appendChild(contentElem, w.getElement());
//      widget.setParent(this);
    }
  }

  /**
   * Returns the widget, if any, that should be focused on if this TreeItem is
   * selected.
   * 
   * @return widget to be focused.
   */
  protected HasFocus getFocusableWidget() {
    Widget widget = getWidget();
    if (widget instanceof HasFocus) {
      return (HasFocus) widget;
    } else {
      return null;
    }
  }

  void addTreeItems(List accum) {
    for (int i = 0; i < children.size(); i++) {
      TreeItem item = (TreeItem) children.get(i);
      accum.add(item);
      item.addTreeItems(accum);
    }
  }

  Vector getChildren() {
    return children;
  }

  Element getContentElem() {
    return contentElem;
  }

  int getContentHeight() {
    return DOM.getIntAttribute(itemTable, "offsetHeight");
  }

  Element getImageElement() {
    return imgElem;
  }

  int getTreeTop() {
    TreeItem item = this;
    int ret = 0;

    while (item != null) {
      ret += DOM.getIntAttribute(item.getElement(), "offsetTop");
      item = item.getParentItem();
    }

    return ret;
  }

  String imgSrc(String img) {
    if (table == null) {
      return img;
    }
    return table.getImageBase() + img;
  }

  void setParentItem(TreeItem parent) {
    this.parentItem = parent;
  }

  void setTreeTable(TreeTable table) {
    if (this.table == table) {
      return;
    }

    if (this.table != null) {
      if (this.table.getSelectedItem() == this) {
        this.table.setSelectedItem(null);
      }
    }
    this.table = table;
    for (int i = 0, n = children.size(); i < n; ++i) {
      ((TreeItem) children.get(i)).setTreeTable(table);
    }
    updateState();
  }

  void updateState() {
    if (children.size() == 0) {
      // UIObject.setVisible(childSpanElem, false);
      DOM.setAttribute(imgElem, "src", imgSrc("tree_white.gif"));
      return;
    }

    // We must use 'display' rather than 'visibility' here,
    // or the children will always take up space.
    if (open) {
      // UIObject.setVisible(childSpanElem, true);
      DOM.setAttribute(imgElem, "src", imgSrc("tree_open.gif"));
    } else {
      // UIObject.setVisible(childSpanElem, false);
      DOM.setAttribute(imgElem, "src", imgSrc("tree_closed.gif"));
    }
    
//    if (getParentItem() != null) {
//      table.updateVisibility(getParentItem());
//    }
  }

  void updateStateRecursive() {
    updateState();
    for (int i = 0, n = children.size(); i < n; ++i) {
      ((TreeItem) children.get(i)).updateStateRecursive();
    }
  }
}


/**
 * Shameless copy of com.google.gwt.user.client.ui.TreeListener. 
 * Changed to replace GWT's TreeItem with the altered TreeItem. 
 * 
 * Event listener interface for tree events.
 */
public interface TreeTableListener extends EventListener {

  /**
   * Fired when a tree item is selected.
   * 
   * @param item the item being selected.
   */
  void onTreeItemSelected(TreeItem item);

  /**
   * Fired when a tree item is opened or closed.
   * 
   * @param item the item whose state is changing.
   */
  void onTreeItemStateChanged(TreeItem item);

}

/**
 * Shameless copy of com.google.gwt.user.client.ui.TreeListenerCollection. 
 * Changed to replace TreeListener with TreeTableListener. 
 * 
 * A helper class for implementers of the SourcesClickEvents interface. This
 * subclass of Vector assumes that all objects added to it will be of type
 * {@link com.google.gwt.user.client.ui.ClickListener}.
 */
 class TreeTableListenerCollection extends Vector {

  /**
   * Fires a "tree item selected" event to all listeners.
   * 
   * @param item the tree item being selected.
   */
  public void fireItemSelected(TreeItem item) {
    for (Iterator it = iterator(); it.hasNext();) {
      TreeTableListener listener = (TreeTableListener) it.next();
      listener.onTreeItemSelected(item);
    }
  }

  /**
   * Fires a "tree item state changed" event to all listeners.
   * 
   * @param item the tree item whose state has changed.
   */
  public void fireItemStateChanged(TreeItem item) {
    for (Iterator it = iterator(); it.hasNext();) {
      TreeTableListener listener = (TreeTableListener) it.next();
      listener.onTreeItemStateChanged(item);
    }
  }
}


interface TreeTableRenderer {

  /**
   * Called to render a tree item row. 
   * @param table
   * @param item
   * @param row
   */
  void renderTreeItem(TreeTable table, TreeItem item, int row);
}

/*
 * Copyright 2006 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */


/**
 * Shameless copy of com.google.gwt.user.client.ui.Tree. Extension of FlexTable
 * adding a tree in one column. Uses a TreeItem model and row based rendering of
 * table cells. 
 * 
 * Changes: 
 * <ul>
 * <li>Removed focus functionality from Tree code. It was causing problems with IE. 
 * Not sure how applicable it is with FlexTable as the base class. It may have
 * problems playing well with GWT because of package scope work arounds. Seems
 * to work ok without the code, minus drawing an outline.</li>
 * <li>Made TreeItem a Widget to be added to a table cell. Removed ContentPanel
 * handling from the Tree code. 
 * <li>Disabled some Widget add/remove code. This may cause strange bugs. Again, 
 * package scope issues. This needs a work around.</li>
 * <li>Streamlined findItemByChain() and modified elementClicked() to search the
 * table. This should probably be rewritten to leverage FlexTable. 
 * </ul>
 * 
 * Notes:
 * <ul>
 * <li>If anyone has a firm understanding of "focus" in GWT I could use the help
 * cleaning this up.</li>
 * </ul>
 * 
 * @author Matt Boyd (modifications to GWT's classes)
 */
class TreeTable extends FlexTable {

  private Element headElem;

  private TreeItem curSelection;

//  private final Element focusable;

//  private FocusListenerCollection focusListeners;

  private String imageBase = GWT.getModuleBaseURL();

  private KeyboardListenerCollection keyboardListeners;

  private TreeTableListenerCollection listeners;

  private MouseListenerCollection mouseListeners = null;

  private final TreeItem root;
  
  private TreeTableRenderer renderer;

  /**
   * Keeps track of the last event type seen. We do this to determine if we
   * have a duplicate key down.
   */
  private int lastEventType;

  /**
   * Needed local instance. GWT's is hidden in package scope. 
   */
//  private FocusImpl impl = (FocusImpl) GWT.create(FocusImpl.class);

  public class Renderer {
    public void renderRow(TreeTable tree, TreeItem item, int row) {

    }
  }

  /**
   * Constructs an empty tree.
   */
  public TreeTable() {
    Element tableElem = getElement();
    headElem = DOM.createElement("thead");
    Element tr = DOM.createTR();
    DOM.appendChild(headElem, tr);
    DOM.insertChild(tableElem, headElem, 0);

    DOM.setStyleAttribute(getElement(), "position", "relative");
//    focusable = impl.createFocusable();
//    DOM.setStyleAttribute(focusable, "fontSize", "0");
//    DOM.setStyleAttribute(focusable, "position", "absolute");
//    DOM.setIntStyleAttribute(focusable, "zIndex", -1);
//    DOM.appendChild(getElement(), focusable);

    sinkEvents(Event.MOUSEEVENTS | Event.ONCLICK | Event.KEYEVENTS);
//    DOM.sinkEvents(focusable, Event.FOCUSEVENTS | Event.KEYEVENTS | DOM.getEventsSunk(focusable));

    // The 'root' item is invisible and serves only as a container
    // for all top-level items.
    root = new TreeItem() {
      public void addItem(TreeItem item) {
        // If this element already belongs to a tree or tree item,
        // remove it.
        if ((item.getParentItem() != null) || (item.getTreeTable() != null)) {
          item.remove();
        }
        item.setTreeTable(this.getTreeTable());

        // Explicitly set top-level items' parents to null.
        item.setParentItem(null);
        getChildren().add(item);

        // Use no margin on top-most items.
        DOM.setIntStyleAttribute(item.getElement(), "marginLeft", 0);
      }

      public void removeItem(TreeItem item) {
        if (!getChildren().contains(item)) {
          return;
        }
        // Update Item state.
        item.setTreeTable(null);
        item.setParentItem(null);
        getChildren().remove(item);
      }
    };
    root.setTreeTable(this);
    setStyleName("gwt-TreeTable");
  }

  /**
   * Adds the widget as a root tree item.
   * 
   * @see com.google.gwt.user.client.ui.HasWidgets#add(com.google.gwt.user.client.ui.Widget)
   * @param widget
   *            widget to add.
   */
  public void add(Widget widget) {
    addItem(widget);
  }
  
  /**
   * Adds a new tree item containing the specified widget.
   * 
   * @param widget
   *            the widget to be added
   */
  public TreeItem addItem(Widget widget) {
    TreeItem item = new TreeItem(widget);
    addItem(item);
    return item;
  }

  /**
   * Adds a simple tree item containing the specified text.
   * 
   * @param itemText
   *            the text of the item to be added
   * @return the item that was added
   */
  public TreeItem addItem(String itemText) {
    TreeItem ret = new TreeItem(itemText);
    addItem(ret);

    return ret;
  }
  
  public TreeItem addItem(Object userObj) {
    TreeItem ret = new TreeItem(userObj);
    addItem(ret);
    return ret;
  }

  /**
   * Adds an item to the root level of this tree.
   * 
   * @param item
   *            the item to be added
   */
  public void addItem(TreeItem item) {
    root.addItem(item);

    // Adds the item to the proper row
    insertItem(item, getRowCount());
    updateRowCache();
    updateVisibility(item);
  }

  /**
   * Updates table rows to include children.
   * 
   * @param item
   */
  void insertItem(TreeItem item, int r) {
    // inserts this item into the tree
    insertRow(r);
    setWidget(r, getTreeColumn(), item);
    item.setRow(r);
    render(item);

    Vector chlds = item.getChildren();
    for (int i = 0, s = chlds.size(); i < s; i++) {
      TreeItem chld = (TreeItem) chlds.get(i);
      insertItem(chld, r + 1);
    }
    
    TreeItem p = item.getParentItem();
    if (p != null) {
      if (!p.isOpen()) {
        setVisible(false, item.getRow());
        setChildrenVisible(item, false);
      }
    }
  }

  /**
   * Removes an item from the root level of this tree.
   * 
   * @param item
   *            the item to be removed
   */
  public void removeItem(TreeItem item) {
    root.removeItem(item);
    removeItemFromTable(item);
  }

  void removeItemFromTable(TreeItem item) {
    int r = item.getRow();
    int rs = item.getDescendentCount();
    for (int i = 0; i < rs; i++) {
      removeRow(r);
    }
    updateRowCache();
  }

  /**
   * Removes all items from the root level of this tree.
   */
  public void removeItems() {
    while (getItemCount() > 0) {
      removeItem(getItem(0));
    }
  }

  /**
   * Updates the cached row index for each tree item. TODO - Optomize with
   * start item.
   */
  void updateRowCache() {
    updateRowCache(root, -1);
  }

  int updateRowCache(TreeItem item, int r) {
    item.setRow(r);

    Vector chlds = item.getChildren();
    for (int i = 0, s = chlds.size(); i < s; i++) {
      TreeItem chld = (TreeItem) chlds.get(i);
      r++;
      r = updateRowCache(chld, r);
    }

    return r;
  }

  protected int getTreeColumn() {
    return 0;
  }

  public void addKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners == null) {
      keyboardListeners = new KeyboardListenerCollection();
    }
    keyboardListeners.add(listener);
  }

  public void addMouseListener(MouseListener listener) {
    if (mouseListeners == null) {
      mouseListeners = new MouseListenerCollection();
    }
    mouseListeners.add(listener);
  }

  public void addTreeTableListener(TreeTableListener listener) {
    if (listeners == null) {
      listeners = new TreeTableListenerCollection();
    }
    listeners.add(listener);
  }

  /**
   * Clears all tree items from the current tree.
   */
  public void clear() {
    int size = root.getChildCount();
    for (int i = size - 1; i >= 0; i--) {
      root.getChild(i).remove();
    }
  }

  /**
   * Ensures that the currently-selected item is visible, opening its parents
   * and scrolling the tree as necessary.
   */
  public void ensureSelectedItemVisible() {
    if (curSelection == null) {
      return;
    }

    TreeItem parent = curSelection.getParentItem();
    while (parent != null) {
      parent.setState(true);
      parent = parent.getParentItem();
    }
  }

  /**
   * Gets this tree's default image package.
   * 
   * @return the tree's image package
   * @see #setImageBase
   */
  public String getImageBase() {
    return imageBase;
  }

  /**
   * Gets the top-level tree item at the specified index.
   * 
   * @param index
   *            the index to be retrieved
   * @return the item at that index
   */
  public TreeItem getItem(int index) {
    return root.getChild(index);
  }

  /**
   * Gets the number of items contained at the root of this tree.
   * 
   * @return this tree's item count
   */
  public int getItemCount() {
    return root.getChildCount();
  }

  /**
   * Gets the currently selected item.
   * 
   * @return the selected item
   */
  public TreeItem getSelectedItem() {
    return curSelection;
  }

  public void onBrowserEvent(Event event) {
    int eventType = DOM.eventGetType(event);
    switch (eventType) {
    case Event.ONCLICK: {
      Element e = DOM.eventGetTarget(event);
      if (shouldTreeDelegateFocusToElement(e)) {
        // The click event should have given focus to this element
        // already.
        // Avoid moving focus back up to the tree (so that focusable
        // widgets
        // attached to TreeItems can receive keyboard events).
      } else {
//        setFocus(true);
      }
      break;
    }
    case Event.ONMOUSEDOWN: {
      if (mouseListeners != null) {
        mouseListeners.fireMouseEvent(this, event);
      }
      elementClicked(root, DOM.eventGetTarget(event));
      break;
    }

    case Event.ONMOUSEUP: {
      if (mouseListeners != null) {
        mouseListeners.fireMouseEvent(this, event);
      }
      break;
    }

    case Event.ONMOUSEMOVE: {
      if (mouseListeners != null) {
        mouseListeners.fireMouseEvent(this, event);
      }
      break;
    }

    case Event.ONMOUSEOVER: {
      if (mouseListeners != null) {
        mouseListeners.fireMouseEvent(this, event);
      }
      break;
    }

    case Event.ONMOUSEOUT: {
      if (mouseListeners != null) {
        mouseListeners.fireMouseEvent(this, event);
      }
      break;
    }

//    case Event.ONFOCUS:
//      // If we already have focus, ignore the focus event.
//      if (focusListeners != null) {
//        focusListeners.fireFocusEvent(this, event);
//      }
//      break;
//
//    case Event.ONBLUR: {
//      if (focusListeners != null) {
//        focusListeners.fireFocusEvent(this, event);
//      }
//
//      break;
//    }

    case Event.ONKEYDOWN:
      // If nothing's selected, select the first item.
      if (curSelection == null) {
        if (root.getChildCount() > 0) {
          onSelection(root.getChild(0), true);
        }
        super.onBrowserEvent(event);
        return;
      }

      if (lastEventType == Event.ONKEYDOWN) {
        // Two key downs in a row signal a duplicate event. Multiple key
        // downs can be triggered in the current configuration
        // independent
        // of the browser.
        return;
      }

      // Handle keyboard events
      switch (DOM.eventGetKeyCode(event)) {
      case KeyboardListener.KEY_UP: {
        moveSelectionUp(curSelection);
        DOM.eventPreventDefault(event);
        break;
      }
      case KeyboardListener.KEY_DOWN: {
        moveSelectionDown(curSelection, true);
        DOM.eventPreventDefault(event);
        break;
      }
      case KeyboardListener.KEY_LEFT: {
        if (curSelection.getState()) {
          curSelection.setState(false);
        }
        DOM.eventPreventDefault(event);
        break;
      }
      case KeyboardListener.KEY_RIGHT: {
        if (!curSelection.getState()) {
          curSelection.setState(true);
        }
        DOM.eventPreventDefault(event);
        break;
      }
      }

      // Intentional fallthrough.
    case Event.ONKEYUP:
      if (eventType == Event.ONKEYUP) {
        // If we got here because of a key tab, then we need to make
        // sure the
        // current tree item is selected.
        if (DOM.eventGetKeyCode(event) == KeyboardListener.KEY_TAB) {
          Vector chain = new Vector();
          collectElementChain(chain, getElement(), DOM.eventGetTarget(event));
          TreeItem item = findItemByChain(chain, 0, root);
          if (item != getSelectedItem()) {
            setSelectedItem(item, true);
          }
        }
      }

      // Intentional fallthrough.
    case Event.ONKEYPRESS: {
      if (keyboardListeners != null) {
        keyboardListeners.fireKeyboardEvent(this, event);
      }
      break;
    }
    }

    // We must call SynthesizedWidget's implementation for all other events.
    super.onBrowserEvent(event);
    lastEventType = eventType;
  }
  
  public void removeKeyboardListener(KeyboardListener listener) {
    if (keyboardListeners != null) {
      keyboardListeners.remove(listener);
    }
  }

  public void removeTreeTableListener(TreeTableListener listener) {
    if (listeners != null) {
      listeners.remove(listener);
    }
  }

  /**
   * Sets the base URL under which this tree will find its default images.
   * These images must be named "tree_white.gif", "tree_open.gif", and
   * "tree_closed.gif".
   */
  public void setImageBase(String baseUrl) {
    imageBase = baseUrl;
    root.updateStateRecursive();
  }

  /**
   * Selects a specified item.
   * 
   * @param item
   *            the item to be selected, or <code>null</code> to deselect
   *            all items
   */
  public void setSelectedItem(TreeItem item) {
    setSelectedItem(item, true);
  }

  /**
   * Selects a specified item.
   * 
   * @param item
   *            the item to be selected, or <code>null</code> to deselect
   *            all items
   * @param fireEvents
   *            <code>true</code> to allow selection events to be fired
   */
  public void setSelectedItem(TreeItem item, boolean fireEvents) {
    if (item == null) {
      if (curSelection == null) {
        return;
      }
      curSelection.setSelected(false);
      curSelection = null;
      return;
    }

    onSelection(item, fireEvents);
  }

  /**
   * Iterator of tree items.
   */
  public Iterator treeItemIterator() {
    List accum = new ArrayList();
    root.addTreeItems(accum);
    return accum.iterator();
  }

  protected void onLoad() {
    root.updateStateRecursive();
    
    renderTable();
    updateVisibility();
  }

  void fireStateChanged(TreeItem item) {
    if (listeners != null) {
      listeners.fireItemStateChanged(item);
    }
  }

  /**
   * Collects parents going up the element tree, terminated at the tree root.
   */
  private void collectElementChain(Vector chain, Element hRoot, Element hElem) {
    if ((hElem == null) || DOM.compare(hElem, hRoot)) {
      return;
    }

    collectElementChain(chain, hRoot, DOM.getParent(hElem));
    chain.add(hElem);
  }

  private boolean elementClicked(TreeItem root, Element hElem) {
    Vector chain = new Vector();
    collectElementChain(chain, getElement(), hElem);

    TreeItem item = findItemByChain(chain, 0, root);
    if (item != null) {
      if (DOM.compare(item.getImageElement(), hElem)) {
        item.setState(!item.getState(), true);
        return true;
      } else if (DOM.isOrHasChild(item.getElement(), hElem)) {
        onSelection(item, true);
        return true;
      }
    }

    return false;
  }

  private TreeItem findDeepestOpenChild(TreeItem item) {
    if (!item.getState()) {
      return item;
    }
    return findDeepestOpenChild(item.getChild(item.getChildCount() - 1));
  }

  private TreeItem findItemByChain(Vector chain, int idx, TreeItem root) {
    if (idx == chain.size()) {
      return root;
    }

    for (int i = 0, s = chain.size(); i < s; i++) {
      Element elem = (Element) chain.get(i);
      String n = getNodeName(elem);
      if ("div".equalsIgnoreCase(n)) {
        return findItemByElement(root, elem);
      }
    }

    return null;
  }

  private TreeItem findItemByElement(TreeItem item, Element elem) {
    if (DOM.compare(item.getElement(), elem)) {
      return item;
    }
    for (int i = 0, n = item.getChildCount(); i < n; ++i) {
      TreeItem child = item.getChild(i);
      child = findItemByElement(child, elem);
      if (child != null) {
        return child;
      }
    }
    return null;
  }

  private native String getNodeName(Element elem) /*-{
    return elem.nodeName;
  }-*/;

  /**
   * Moves to the next item, going into children as if dig is enabled.
   */
  private void moveSelectionDown(TreeItem sel, boolean dig) {
    if (sel == root) {
      return;
    }

    TreeItem parent = sel.getParentItem();
    if (parent == null) {
      parent = root;
    }
    int idx = parent.getChildIndex(sel);

    if (!dig || !sel.getState()) {
      if (idx < parent.getChildCount() - 1) {
        onSelection(parent.getChild(idx + 1), true);
      } else {
        moveSelectionDown(parent, false);
      }
    } else if (sel.getChildCount() > 0) {
      onSelection(sel.getChild(0), true);
    }
  }

  /**
   * Moves the selected item up one.
   */
  private void moveSelectionUp(TreeItem sel) {
    TreeItem parent = sel.getParentItem();
    if (parent == null) {
      parent = root;
    }
    int idx = parent.getChildIndex(sel);

    if (idx > 0) {
      TreeItem sibling = parent.getChild(idx - 1);
      onSelection(findDeepestOpenChild(sibling), true);
    } else {
      onSelection(parent, true);
    }
  }

  private void onSelection(TreeItem item, boolean fireEvents) {

    // 'root' isn't a real item, so don't let it be selected
    // (some cases in the keyboard handler will try to do this)
    if (item == root) {
      return;
    }

    if (curSelection != null) {
      curSelection.setSelected(false);
    }

    curSelection = item;

    if (curSelection != null) {
//      moveFocus(curSelection);

      // Select the item and fire the selection event.
      curSelection.setSelected(true);
      if (fireEvents && (listeners != null)) {
        listeners.fireItemSelected(curSelection);
      }
    }
  }

  private native boolean shouldTreeDelegateFocusToElement(Element elem) /*-{
    var focus = 
      ((elem.nodeName == "SELECT") || 
      (elem.nodeName == "INPUT")  || 
      (elem.nodeName == "CHECKBOX")
    );
    return focus;
  }-*/;

  public void updateVisibility() {
    for (int i = 0, s = root.getChildCount(); i < s; i++) {
      TreeItem item = root.getChild(i);
      updateVisibility(item);
    }
  }

  protected void updateVisibility(TreeItem item) {
    if (item.isOpen()) {
      showChildren(item);
    } else {
      hideChildren(item);
    }
  }

  void setVisible(boolean visible, int row) {
    UIObject.setVisible(getRowFormatter().getElement(row), visible);
  }

  protected void setVisible(boolean visible, int row, int count) {
    for (int r = row, s = row + count; r < s; r++) {
      setVisible(visible, r);
    }
  }

  public void showChildren(TreeItem item) {
    for (int i = 0, s = item.getChildCount(); i < s; i++) {
      TreeItem child = item.getChild(i);
      setVisible(true, child.getRow());

      if (child.isOpen()) {
        showChildren(child);
      }
    }
  }

  public void hideChildren(TreeItem item) {
    setChildrenVisible(item, false);
  }

  
  public void setChildrenVisible(TreeItem item, boolean visible) {
    if (item.getChildCount() == 0) {
      return;
    }
    int row = item.getRow() + 1;
    int lastChildRow = getLastChildRow(item);
    int count = lastChildRow - row + 1;
    setVisible(visible, row, count);
  }

  protected TreeItem getNextSibling(TreeItem item) {
    TreeItem p = item.getParentItem();
    if (p == null) {
      int idx = root.getChildIndex(item) + 1;
      if (idx < root.getChildCount()) {
        // Gets the next sibling
        return root.getChild(idx);
      }
    } else {
      int idx = p.getChildIndex(item) + 1;
      if (idx < p.getChildCount()) {
        // Gets the next sibling
        return p.getChild(idx);
      }
    }
    return null;
  }

  protected TreeItem getNextNonChild(TreeItem item) {
    TreeItem next = getNextSibling(item);
    if (next != null) {
      return next;
    }
    TreeItem p = item.getParentItem();
    if (p != null) {
      return getNextNonChild(p);
    } else {
      return null;
    }
  }

  public int getLastChildRow(TreeItem item) {
    // Checks the row of the next sibling
    TreeItem next = getNextNonChild(item);
    if (next != null) {
      return next.getRow() - 1;
    }

    return getRowCount() - 1;
  }
  
  public void renderTable() {
    render(root);
  }
  
  /**
   * Renders TreeItems recursively. 
   * @param item
   */
  public void render(TreeItem item) {
    getRenderer().renderTreeItem(this, item, item.getRow());
    if (item.getParentItem() != null) {
      updateVisibility(item.getParentItem());
    }
    
    for (int i = 0, s = item.getChildCount(); i < s; i++) {
      TreeItem child = item.getChild(i);
      render(child);
    }
  }

  public TreeTableRenderer getRenderer() {
    if (renderer == null) {
      renderer = new DefaultRenderer();
    }
    return renderer;
  }

  public void setRenderer(TreeTableRenderer renderer) {
    this.renderer = renderer;
  }
  
//  /**
//   * Move the tree focus to the specified selected item.
//   * 
//   * @param selection
//   */
//  private void moveFocus(TreeItem selection) {
//    HasFocus focusableWidget = selection.getFocusableWidget();
//    if (focusableWidget != null) {
//      focusableWidget.setFocus(true);
//      DOM.scrollIntoView(((Widget) focusableWidget).getElement());
//    } else {
//      // Get the location and size of the given item's content element
//      // relative
//      // to the tree.
//      Element selectedElem = selection.getContentElem();
//      int containerLeft = getAbsoluteLeft();
//      int containerTop = getAbsoluteTop();
//
//      int left = DOM.getAbsoluteLeft(selectedElem) - containerLeft;
//      int top = DOM.getAbsoluteTop(selectedElem) - containerTop;
//      int width = DOM.getIntAttribute(selectedElem, "offsetWidth");
//      int height = DOM.getIntAttribute(selectedElem, "offsetHeight");
//
//      // Set the focusable element's position and size to exactly underlap
//      // the
//      // item's content element.
//      DOM.setIntStyleAttribute(focusable, "left", left);
//      DOM.setIntStyleAttribute(focusable, "top", top);
//      DOM.setIntStyleAttribute(focusable, "width", width);
//      DOM.setIntStyleAttribute(focusable, "height", height);
//
//      // Scroll it into view.
//      DOM.scrollIntoView(focusable);
//
//      // Ensure Focus is set, as focus may have been previously delegated
//      // by
//      // tree.
//      impl.focus(focusable);
//    }
//  }
  
//  public int getTabIndex() {
//    return impl.getTabIndex(focusable);
//  }

//  public void addFocusListener(FocusListener listener) {
//    if (focusListeners == null) {
//      focusListeners = new FocusListenerCollection();
//    }
//    focusListeners.add(listener);
//  }

//  public void removeFocusListener(FocusListener listener) {
//    if (focusListeners != null) {
//      focusListeners.remove(listener);
//    }
//  }

//  public void setAccessKey(char key) {
//    impl.setAccessKey(focusable, key);
//  }

//  public void setFocus(boolean focus) {
//    if (focus) {
//      impl.focus(focusable);
//    } else {
//      impl.blur(focusable);
//    }
//  }

//  public void setTabIndex(int index) {
//    impl.setTabIndex(focusable, index);
//  }

  /**
   * Default renderer for TreeTable. Renders the user object into
   * the TreeItem. Widget user objects are preserved. Arrays are mapped
   * into the row with first object rendered into the TreeItem. All
   * other objects are rendered to the TreeItem with toString().
   */
  class DefaultRenderer implements TreeTableRenderer {
    public void renderTreeItem(TreeTable table, TreeItem item, int row) {
      Object obj = item.getUserObject();
      if (obj instanceof Widget) {
        item.setWidget((Widget) obj);
      } else if (obj instanceof Object[]) {
        Object [] objs = (Object []) obj;
        if (objs.length > 0) {
          Object o = objs[0];
          if (o instanceof Widget) {
            item.setWidget((Widget) o);
          } else if (o != null) {
            item.setHTML(o.toString());
          } else {
            item.setText(null);
          }
          for (int i = 1, s = objs.length; i < s; i++) {
            o = objs[i];
            if (o instanceof Widget) {
              setWidget(row, i, (Widget) o);
            } else if (o != null) {
              setHTML(row, i, o.toString());
            } else {
              setHTML(row, i, null);
            }
          }
        }
      } else if (obj != null) {
        item.setHTML(obj.toString());
      }
    }
  }
  
  public void setWidget(int row, int column, Widget widget) {
    if (column != getTreeColumn()) {
      super.setWidget(row, column, widget);
    } else {
      if (widget instanceof TreeItem) {
        super.setWidget(row, column, widget);
      } else {
        throw new RuntimeException("Cannot add non-TreeItem to tree column");
      }
    }
  }
  
  public void setText(int row, int column, String text) {
    if (column != getTreeColumn()) {
      super.setText(row, column, text);
    } else {
      throw new RuntimeException("Cannot add non-TreeItem to tree column");
    }
  }
  
  public void setHTML(int row, int column, String text) {
    if (column != getTreeColumn()) {
      super.setHTML(row, column, text);
    } else {
      throw new RuntimeException("Cannot add non-TreeItem to tree column");
    }
  }
}


           
         
    
  








GWT-TreeTable.zip( 13 k)

Related examples in the same category

1.Tree, table and detail panel (Smart GWT)Tree, table and detail panel (Smart GWT)
2.Tree in a grid (Smart GWT)Tree in a grid (Smart GWT)
3.Tree table with Frozen Columns Sample (Smart GWT)Tree table with Frozen Columns Sample (Smart GWT)
4.TreeGrid and TreeGridField (Smart GWT)TreeGrid and TreeGridField (Smart GWT)
5.Tree with Multiple Columns Sample (Smart GWT)Tree with Multiple Columns Sample (Smart GWT)
6.ColumnTree provides an alternate navigation paradigm for Tree data (Smart GWT)ColumnTree provides an alternate navigation paradigm for Tree data (Smart GWT)
7.Formatter interfaces allow you to add custom tree titles (Smart GWT)Formatter interfaces allow you to add custom tree titles (Smart GWT)
8.TreeTable editing (Ext GWT)TreeTable editing (Ext GWT)
9.Double click to edit a table tree (Ext GWT)Double click to edit a table tree (Ext GWT)
10.TreeGrid example (Ext GWT)TreeGrid example (Ext GWT)
11.TreeGrid with Row Editor (Ext GWT)TreeGrid with Row Editor (Ext GWT)
12.TreeGrid with Widget Example (Ext GWT)TreeGrid with Widget Example (Ext GWT)
13.Adding row number to TreeGrid (Ext GWT)Adding row number to TreeGrid (Ext GWT)