Java/GWT/Tree Table
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.jexp.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.rupare(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.rupare(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.rupare(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");
}
}
}