Add side-by-side trees to the bikeshed Review at http://gwt-code-reviews.appspot.com/160802 git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7669 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java index 5539171..c2b6210 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/StockSample.java
@@ -31,7 +31,8 @@ import com.google.gwt.bikeshed.sample.stocks.shared.StockRequest; import com.google.gwt.bikeshed.sample.stocks.shared.StockResponse; import com.google.gwt.bikeshed.sample.stocks.shared.Transaction; -import com.google.gwt.bikeshed.tree.client.TreeView; +import com.google.gwt.bikeshed.tree.client.SideBySideTreeView; +import com.google.gwt.bikeshed.tree.client.StandardTreeView; import com.google.gwt.core.client.EntryPoint; import com.google.gwt.core.client.GWT; import com.google.gwt.event.dom.client.KeyUpEvent; @@ -177,8 +178,9 @@ private PagingTableListView<Transaction> transactionTable; - private TreeView transactionTree; + private StandardTreeView transactionTree1; + private SideBySideTreeView transactionTree2; /** * The timer used to update the stock quotes. */ @@ -244,9 +246,13 @@ transactionTable.addColumn(Columns.subtotalColumn); // Create the transactions tree. - transactionTree = new TreeView(new TransactionTreeViewModel(favoritesListModel, + transactionTree1 = new StandardTreeView(new TransactionTreeViewModel(favoritesListModel, transactionListListModelsByTicker), null); - transactionTree.setAnimationEnabled(true); + transactionTree1.setAnimationEnabled(true); + + // Create the transactions tree. + transactionTree2 = new SideBySideTreeView(new TransactionTreeViewModel(favoritesListModel, + transactionListListModelsByTicker), null); Columns.favoriteColumn.setFieldUpdater(new FieldUpdater<StockQuote, Boolean>() { public void update(StockQuote object, Boolean value) { @@ -319,7 +325,9 @@ RootPanel.get().add(new HTML("<hr>")); RootPanel.get().add(transactionTable); RootPanel.get().add(new HTML("<hr>")); - RootPanel.get().add(transactionTree); + RootPanel.get().add(transactionTree1); + RootPanel.get().add(new HTML("<hr>")); + RootPanel.get().add(transactionTree2); // Add a handler to send the name to the server queryField.addKeyUpHandler(new KeyUpHandler() {
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/TransactionTreeViewModel.java b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/TransactionTreeViewModel.java index fcaf4b2..a44d1d4 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/TransactionTreeViewModel.java +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/stocks/client/TransactionTreeViewModel.java
@@ -16,10 +16,11 @@ package com.google.gwt.bikeshed.sample.stocks.client; import com.google.gwt.bikeshed.cells.client.Cell; -import com.google.gwt.bikeshed.cells.client.TextCell; import com.google.gwt.bikeshed.list.shared.ListListModel; +import com.google.gwt.bikeshed.list.shared.ListModel; +import com.google.gwt.bikeshed.sample.stocks.shared.StockQuote; import com.google.gwt.bikeshed.sample.stocks.shared.Transaction; -import com.google.gwt.bikeshed.tree.client.TreeNodeView; +import com.google.gwt.bikeshed.tree.client.TreeNode; import com.google.gwt.bikeshed.tree.client.TreeViewModel; import java.util.Map; @@ -57,7 +58,8 @@ this.transactionListListModelsByTicker = transactionListListModelsByTicker; } - public <T> NodeInfo<?> getNodeInfo(T value, TreeNodeView<T> treeNodeView) { + @SuppressWarnings("unused") + public <T> NodeInfo<?> getNodeInfo(T value, TreeNode<T> treeNode) { if (value == null) { return new TreeViewModel.DefaultNodeInfo<StockQuote>(stockQuoteListModel, STOCK_QUOTE_CELL) { @@ -67,8 +69,14 @@ } }; } else if (value instanceof StockQuote) { - return new TreeViewModel.DefaultNodeInfo<Transaction>( - transactionListListModelsByTicker.get(((StockQuote) value).getTicker()), TRANSACTION_CELL); + String ticker = ((StockQuote) value).getTicker(); + ListListModel<Transaction> listModel = transactionListListModelsByTicker.get(ticker); + if (listModel == null) { + listModel = new ListListModel<Transaction>(); + transactionListListModelsByTicker.put(ticker, listModel); + } + return new TreeViewModel.DefaultNodeInfo<Transaction>(listModel, + TRANSACTION_CELL); } throw new IllegalArgumentException(value.toString());
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/tree/client/MyTreeViewModel.java b/bikeshed/src/com/google/gwt/bikeshed/sample/tree/client/MyTreeViewModel.java index 89474f5..c0aaba5 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/tree/client/MyTreeViewModel.java +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/tree/client/MyTreeViewModel.java
@@ -20,7 +20,7 @@ import com.google.gwt.bikeshed.cells.client.ValueUpdater; import com.google.gwt.bikeshed.list.shared.AsyncListModel; import com.google.gwt.bikeshed.list.shared.ListModel; -import com.google.gwt.bikeshed.tree.client.TreeNodeView; +import com.google.gwt.bikeshed.tree.client.TreeNode; import com.google.gwt.bikeshed.tree.client.TreeViewModel; import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.Timer; @@ -93,7 +93,7 @@ } }; - public <T> NodeInfo<?> getNodeInfo(T value, TreeNodeView<T> treeNodeView) { + public <T> NodeInfo<?> getNodeInfo(T value, TreeNode<T> treeNode) { if (value instanceof String) { return getNodeInfoHelper((String) value); }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/sample/tree/client/TreeSample.java b/bikeshed/src/com/google/gwt/bikeshed/sample/tree/client/TreeSample.java index 5a59f8b..3063d4b 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/sample/tree/client/TreeSample.java +++ b/bikeshed/src/com/google/gwt/bikeshed/sample/tree/client/TreeSample.java
@@ -15,8 +15,10 @@ */ package com.google.gwt.bikeshed.sample.tree.client; -import com.google.gwt.bikeshed.tree.client.TreeView; +import com.google.gwt.bikeshed.tree.client.SideBySideTreeView; +import com.google.gwt.bikeshed.tree.client.StandardTreeView; import com.google.gwt.core.client.EntryPoint; +import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.RootPanel; /** @@ -25,8 +27,13 @@ public class TreeSample implements EntryPoint { public void onModuleLoad() { - TreeView tree = new TreeView(new MyTreeViewModel(), "..."); + StandardTreeView tree = new StandardTreeView(new MyTreeViewModel(), "..."); tree.setAnimationEnabled(true); RootPanel.get().add(tree); + + RootPanel.get().add(new HTML("<hr>")); + + SideBySideTreeView sstree = new SideBySideTreeView(new MyTreeViewModel(), "..."); + RootPanel.get().add(sstree); } }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java new file mode 100644 index 0000000..8ac9dd9 --- /dev/null +++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
@@ -0,0 +1,207 @@ +/* + * Copyright 2010 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.google.gwt.bikeshed.tree.client; + +import static com.google.gwt.bikeshed.tree.client.SideBySideTreeView.COLUMN_WIDTH; + +import com.google.gwt.bikeshed.cells.client.Cell; +import com.google.gwt.bikeshed.tree.client.TreeViewModel.NodeInfo; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; + +import java.util.List; + +/** + * A tree view that displays each level in a side-by-side manner. + * + * @param <T> the type that this {@link TreeNodeView} contains + */ +public class SideBySideTreeNodeView<T> extends TreeNodeView<T> { + + private final int imageLeft; + + private int level; + + private String path; + + /** + * Construct a {@link TreeNodeView}. + * + * @param tree the parent {@link TreeView} + * @param parent the parent {@link TreeNodeView} + * @param parentNodeInfo the {@link NodeInfo} of the parent + * @param elem the outer element of this {@link TreeNodeView}. + * @param value the value of this node + */ + SideBySideTreeNodeView(final TreeView tree, final SideBySideTreeNodeView<?> parent, + NodeInfo<T> parentNodeInfo, Element elem, T value, int level, String path) { + super(tree, parent, parentNodeInfo, value); + this.imageLeft = 85 - tree.getImageWidth(); + this.level = level; + this.path = path; + + setElement(elem); + } + + @Override + protected <C> TreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo, + Element childElem, C childValue, int idx) { + return new SideBySideTreeNodeView<C>(tree, + SideBySideTreeNodeView.this, nodeInfo, childElem, childValue, + level + 1, path + "-" + idx); + } + + @Override + protected <C> void emitHtml(StringBuilder sb, NodeInfo<C> nodeInfo, + List<C> childValues, List<TreeNodeView<?>> savedViews) { + TreeViewModel model = tree.getTreeViewModel(); + int imageWidth = tree.getImageWidth(); + Cell<C> theCell = nodeInfo.getCell(); + + int idx = 0; + for (C childValue : childValues) { + sb.append("<div id=\"" + path + "-" + idx + "\" style=\"position:relative;padding-right:"); + sb.append(imageWidth); + sb.append("px;\">"); + if (savedViews.get(idx) != null) { + sb.append(tree.getOpenImageHtml(imageLeft)); + } else if (model.isLeaf(childValue)) { + sb.append(LEAF_IMAGE); + } else { + sb.append(tree.getClosedImageHtml(imageLeft)); + } + sb.append("<div class=\"gwt-sstree-cell\">"); + theCell.render(childValue, sb); + sb.append("</div></div>"); + + idx++; + } + } + + /** + * Ensure that the child container exists and return it. + * + * @return the child container + */ + @Override + protected Element ensureChildContainer() { + if (childContainer == null) { + // Create the container within the top-level widget element. + Element container = createContainer(level); + container.setInnerHTML(""); + Element animFrame = container.appendChild( + Document.get().createDivElement()); + animFrame.getStyle().setPosition(Position.RELATIVE); + childContainer = animFrame.appendChild(Document.get().createDivElement()); + } + + return childContainer; + } + + /** + * @return the element that contains the rendered cell + */ + @Override + protected Element getCellParent() { + return getElement().getChild(1).cast(); + } + + /** + * @return the image element + */ + @Override + protected Element getImageElement() { + return getElement().getFirstChildElement(); + } + + @Override + protected int getImageLeft() { + return imageLeft; + } + + @Override + protected void postClose() { + destroyContainer(level); + } + + @Override + protected void preOpen() { + // Close siblings of this node + TreeNodeView<?> parentNode = getParentTreeNodeView(); + if (parentNode != null) { + int numSiblings = parentNode.getChildCount(); + for (int i = 0; i < numSiblings; i++) { + Element container = parentNode.getChildContainer().getChild(i).cast(); + + TreeNodeView<?> sibling = parentNode.getChildTreeNodeView(i); + if (sibling == this) { + container.setClassName("gwt-SideBySideTree-selectedItem"); + } else { + if (sibling.getState()) { + sibling.setState(false); + container.setClassName("gwt-SideBySideTree-unselectedItem"); + } + } + } + } + } + + /** + * Returns the container for child nodes at the given level. + */ + private Element createContainer(int level) { + // Resize the root element + Element rootElement = tree.getElement(); + rootElement.getStyle().setWidth((level + 1) * COLUMN_WIDTH, Unit.PX); + + // Create children of the root container as needed. + int childCount = rootElement.getChildCount(); + while (childCount <= level) { + Element div = rootElement.appendChild(Document.get().createDivElement()); + div.setClassName("gwt-SideBySideTreeColumn"); + Style style = div.getStyle(); + style.setPosition(Position.ABSOLUTE); + style.setTop(0, Unit.PX); + style.setLeft(level * COLUMN_WIDTH, Unit.PX); + + childCount++; + } + + return rootElement.getChild(level).cast(); + } + + /** + * Destroys the containers for child nodes at the given level and all + * subsequent levels. + */ + private void destroyContainer(int level) { + // Resize the root element + Element rootElement = tree.getElement(); + rootElement.getStyle().setWidth((level + 1) * COLUMN_WIDTH, Unit.PX); + + // Create children of the root container as needed. + int childCount = rootElement.getChildCount(); + while (childCount > level) { + rootElement.removeChild(rootElement.getLastChild()); + childCount--; + } + + childContainer = null; + } +}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java new file mode 100644 index 0000000..cab7067 --- /dev/null +++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
@@ -0,0 +1,103 @@ +/* + * Copyright 2010 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.google.gwt.bikeshed.tree.client; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; + +/** + * A view of a tree. + */ +public class SideBySideTreeView extends TreeView { + + protected static final int COLUMN_HEIGHT = 200; + + protected static final int COLUMN_WIDTH = 100; + + /** + * Construct a new {@link TreeView}. + * + * @param <T> the type of data in the root node + * @param viewModel the {@link TreeViewModel} that backs the tree + * @param rootValue the hidden root value of the tree + */ + public <T> SideBySideTreeView(TreeViewModel viewModel, T rootValue) { + super(viewModel); + + Element rootElement = Document.get().createDivElement(); + rootElement.setClassName("gwt-SideBySideTreeView"); + Style style = rootElement.getStyle(); + style.setPosition(Position.RELATIVE); + style.setWidth(COLUMN_WIDTH, Unit.PX); + style.setHeight(COLUMN_HEIGHT, Unit.PX); + setElement(rootElement); + + // Add event handlers. + sinkEvents(Event.ONCLICK | Event.ONMOUSEDOWN | Event.ONMOUSEUP); + + // Associate a view with the item. + TreeNodeView<T> root = new SideBySideTreeNodeView<T>(this, null, null, + rootElement, rootValue, 0, "gwt-sstree"); + setRootNode(root); + root.setState(true); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + int eventType = DOM.eventGetType(event); + switch (eventType) { + case Event.ONMOUSEUP: + Element currentTarget = event.getCurrentEventTarget().cast(); + if (currentTarget == getElement()) { + Element target = event.getEventTarget().cast(); + String id = target.getId(); + boolean inCell = target.getClassName().equals("gwt-sstree-cell"); + while (id == null || !id.startsWith("gwt-sstree")) { + target = target.getParentElement(); + if (target == null) { + return; + } + + id = target.getId(); + inCell |= target.getClassName().equals("gwt-sstree-cell"); + } + + if (id.startsWith("gwt-sstree-")) { + id = id.substring(11); + String[] path = id.split("-"); + + TreeNodeView<?> nodeView = rootNode; + for (String s : path) { + nodeView = nodeView.getChildTreeNodeView(Integer.parseInt(s)); + } + if (inCell) { + nodeView.fireEventToCell(event); + } else { + nodeView.setState(!nodeView.getState()); + } + } + } + break; + } + } +}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java new file mode 100644 index 0000000..fcdb562 --- /dev/null +++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java
@@ -0,0 +1,121 @@ +/* + * Copyright 2010 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.google.gwt.bikeshed.tree.client; + +import com.google.gwt.bikeshed.cells.client.Cell; +import com.google.gwt.bikeshed.tree.client.TreeViewModel.NodeInfo; +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.Style.Overflow; +import com.google.gwt.dom.client.Style.Position; + +import java.util.List; + +/** + * A view of a tree node. + * + * @param <T> the type that this {@link TreeNodeView} contains + */ +public class StandardTreeNodeView<T> extends TreeNodeView<T> { + + /** + * Construct a {@link TreeNodeView}. + * + * @param tree the parent {@link TreeView} + * @param parent the parent {@link TreeNodeView} + * @param parentNodeInfo the {@link NodeInfo} of the parent + * @param elem the outer element of this {@link TreeNodeView}. + * @param value the value of this node + */ + StandardTreeNodeView(final TreeView tree, + final StandardTreeNodeView<?> parent, NodeInfo<T> parentNodeInfo, + Element elem, T value) { + super(tree, parent, parentNodeInfo, value); + setElement(elem); + } + + @Override + protected void postClose() { + tree.maybeAnimateTreeNode(this); + } + + @Override + protected <C> TreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo, + Element childElem, C childValue, int idx) { + return new StandardTreeNodeView<C>(tree, this, nodeInfo, childElem, + childValue); + } + + @Override + protected <C> void emitHtml(StringBuilder sb, NodeInfo<C> nodeInfo, + List<C> childValues, List<TreeNodeView<?>> savedViews) { + TreeViewModel model = tree.getTreeViewModel(); + int imageWidth = tree.getImageWidth(); + Cell<C> theCell = nodeInfo.getCell(); + + int idx = 0; + for (C childValue : childValues) { + sb.append("<div style=\"position:relative;padding-left:"); + sb.append(imageWidth); + sb.append("px;\">"); + if (savedViews.get(idx) != null) { + sb.append(tree.getOpenImageHtml(0)); + } else if (model.isLeaf(childValue)) { + sb.append(LEAF_IMAGE); + } else { + sb.append(tree.getClosedImageHtml(0)); + } + sb.append("<div>"); + theCell.render(childValue, sb); + sb.append("</div>"); + sb.append("</div>"); + } + } + + /** + * Ensure that the child container exists and return it. + * + * @return the child container + */ + @Override + protected Element ensureChildContainer() { + if (childContainer == null) { + // If this is a root node or the element does not exist, create it. + Element animFrame = getElement().appendChild( + Document.get().createDivElement()); + animFrame.getStyle().setPosition(Position.RELATIVE); + animFrame.getStyle().setOverflow(Overflow.HIDDEN); + childContainer = animFrame.appendChild(Document.get().createDivElement()); + } + return childContainer; + } + + /** + * @return the element that contains the rendered cell + */ + @Override + protected Element getCellParent() { + return getElement().getChild(1).cast(); + } + + /** + * @return the image element + */ + @Override + protected Element getImageElement() { + return getElement().getFirstChildElement(); + } +}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java new file mode 100644 index 0000000..241cfcb --- /dev/null +++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
@@ -0,0 +1,292 @@ +/* + * Copyright 2010 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.google.gwt.bikeshed.tree.client; + +import com.google.gwt.dom.client.Document; +import com.google.gwt.dom.client.Element; +import com.google.gwt.dom.client.NativeEvent; +import com.google.gwt.dom.client.Style.Display; +import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.dom.client.Style.Unit; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.Event; +import com.google.gwt.user.client.ui.HasAnimation; + +import java.util.ArrayList; + +/** + * A view of a tree. + */ +public class StandardTreeView extends TreeView implements HasAnimation { + + /** + * A {@link TreeNodeAnimation} that reveals the contents of child nodes. + */ + public static class RevealAnimation extends TreeNodeViewAnimation { + + /** + * Create a new {@link RevealAnimation}. + * + * @return the new animation + */ + public static RevealAnimation create() { + return new RevealAnimation(); + } + + /** + * The container that holds the child container. + */ + Element animFrame; + /** + * The container that holds the children. + */ + Element childContainer; + + /** + * The target height when opening, the start height when closing. + */ + int height; + /** + * True if the node is opening, false if closing. + */ + boolean opening; + + /** + * Not instantiable. + */ + private RevealAnimation() { + } + + /** + * Animate a {@link TreeNodeView} into its new state. + * + * @param node the {@link TreeNodeView} to animate + * @param isAnimationEnabled true to animate + */ + @Override + public void animate(TreeNodeView<?> node, boolean isAnimationEnabled) { + // Cancel any pending animations. + cancel(); + + // Initialize the fields. + this.opening = node.getState(); + childContainer = node.getChildContainer(); + animFrame = childContainer.getParentElement(); + + if (isAnimationEnabled) { + // Animated. + int duration = getDuration(); + int childCount = childContainer.getChildCount(); + if (childCount < 4) { + // Reduce the duration if there are less than four items or it will + // look really slow. + duration = (int) ((childCount / 4.0) * duration); + } + run(duration); + } else { + // Non animated. + cleanup(); + } + } + + @Override + protected void onComplete() { + cleanup(); + } + + @Override + protected void onStart() { + if (opening) { + animFrame.getStyle().setHeight(1.0, Unit.PX); + animFrame.getStyle().clearDisplay(); + height = childContainer.getScrollHeight(); + } else { + height = childContainer.getOffsetHeight(); + } + } + + @Override + protected void onUpdate(double progress) { + if (opening) { + double curHeight = progress * height; + animFrame.getStyle().setHeight(curHeight, Unit.PX); + } else { + double curHeight = (1.0 - progress) * height; + animFrame.getStyle().setHeight(curHeight, Unit.PX); + } + } + + /** + * Put the node back into a clean state and clear fields. + */ + private void cleanup() { + if (opening) { + animFrame.getStyle().clearDisplay(); + } else { + animFrame.getStyle().setDisplay(Display.NONE); + childContainer.setInnerHTML(""); + } + animFrame.getStyle().clearHeight(); + this.childContainer = null; + this.animFrame = null; + } + } + + /** + * A {@link TreeNodeAnimation} that slides children into view. + */ + public static class SlideAnimation extends RevealAnimation { + /** + * Create a new {@link RevealAnimation}. + * + * @return the new animation + */ + public static SlideAnimation create() { + return new SlideAnimation(); + } + + /** + * Not instantiable. + */ + private SlideAnimation() { + } + + @Override + protected void onComplete() { + childContainer.getStyle().clearPosition(); + childContainer.getStyle().clearTop(); + childContainer.getStyle().clearWidth(); + super.onComplete(); + } + + @Override + protected void onStart() { + super.onStart(); + if (opening) { + childContainer.getStyle().setTop(-height, Unit.PX); + } else { + childContainer.getStyle().setTop(0, Unit.PX); + } + childContainer.getStyle().setPosition(Position.RELATIVE); + } + + @Override + protected void onUpdate(double progress) { + super.onUpdate(progress); + if (opening) { + double curTop = (1.0 - progress) * -height; + childContainer.getStyle().setTop(curTop, Unit.PX); + } else { + double curTop = progress * -height; + childContainer.getStyle().setTop(curTop, Unit.PX); + } + } + } + + /** + * Construct a new {@link TreeView}. + * + * @param <T> the type of data in the root node + * @param viewModel the {@link TreeViewModel} that backs the tree + * @param rootValue the hidden root value of the tree + */ + public <T> StandardTreeView(TreeViewModel viewModel, T rootValue) { + super(viewModel); + setElement(Document.get().createDivElement()); + setStyleName("gwt-TreeView"); + + // We use one animation for the entire tree. + animation = SlideAnimation.create(); + + // Add event handlers. + sinkEvents(Event.ONCLICK | Event.ONMOUSEDOWN | Event.ONMOUSEUP); + + // Associate a view with the item. + TreeNodeView<T> root = new StandardTreeNodeView<T>(this, null, null, getElement(), rootValue); + setRootNode(root); + root.setState(true); + } + + @Override + public void onBrowserEvent(Event event) { + super.onBrowserEvent(event); + + int eventType = DOM.eventGetType(event); + switch (eventType) { + case Event.ONMOUSEUP: + Element currentTarget = event.getCurrentEventTarget().cast(); + if (currentTarget == getElement()) { + Element target = event.getEventTarget().cast(); + elementClicked(target, event, rootNode); + } + break; + } + } + + /** + * Collects parents going up the element tree, terminated at the tree root. + */ + private void collectElementChain(ArrayList<Element> chain, ArrayList<String> ids, Element hRoot, + Element hElem) { + if ((hElem == null) || (hElem == hRoot)) { + return; + } + + collectElementChain(chain, ids, hRoot, hElem.getParentElement()); + chain.add(hElem); + ids.add(hElem.getId()); + } + + private boolean elementClicked(Element hElem, NativeEvent event, TreeNodeView<?> rootNode) { + ArrayList<Element> chain = new ArrayList<Element>(); + ArrayList<String> ids = new ArrayList<String>(); + collectElementChain(chain, ids, getElement(), hElem); + + TreeNodeView<?> nodeView = findItemByChain(chain, 0, rootNode); + if (nodeView != null && nodeView != rootNode) { + if (nodeView.getImageElement().isOrHasChild(hElem)) { + nodeView.setState(!nodeView.getState()); + return true; + } else if (nodeView.getCellParent().isOrHasChild(hElem)) { + nodeView.fireEventToCell(event); + return true; + } + } + + return false; + } + + private TreeNodeView<?> findItemByChain(ArrayList<Element> chain, int idx, + TreeNodeView<?> parent) { + if (idx == chain.size()) { + return parent; + } + + Element hCurElem = chain.get(idx); + for (int i = 0, n = parent.getChildCount(); i < n; ++i) { + TreeNodeView<?> child = parent.getChildTreeNodeView(i); + if (child.getElement() == hCurElem) { + TreeNodeView<?> retItem = findItemByChain(chain, idx + 1, child); + if (retItem == null) { + return child; + } + return retItem; + } + } + + return findItemByChain(chain, idx + 1, parent); + } +}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java new file mode 100644 index 0000000..e9a0694 --- /dev/null +++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java
@@ -0,0 +1,32 @@ +/* + * Copyright 2010 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.google.gwt.bikeshed.tree.client; + +/** + * A interface allowing navigation within a tree view. + * + * @param <T> the type of data stored at this node. + */ +public interface TreeNode<T> { + + TreeNode<?> getChildNode(int childIndex); + + int getChildCount(); + + TreeNode<?> getParentNode(); + + T getValue(); +}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java index ab35216..1ba192c 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java +++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
@@ -15,7 +15,6 @@ */ package com.google.gwt.bikeshed.tree.client; -import com.google.gwt.bikeshed.cells.client.Cell; import com.google.gwt.bikeshed.list.shared.ListEvent; import com.google.gwt.bikeshed.list.shared.ListHandler; import com.google.gwt.bikeshed.list.shared.ListModel; @@ -26,8 +25,7 @@ import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Overflow; -import com.google.gwt.dom.client.Style.Position; +import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import java.util.ArrayList; @@ -40,110 +38,90 @@ * * @param <T> the type that this {@link TreeNodeView} contains */ -public class TreeNodeView<T> extends Composite { +public abstract class TreeNodeView<T> extends Composite implements TreeNode<T> { /** * The element used in place of an image when a node has no children. */ - private static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>"; - - private boolean animate; - - /** - * The children of this {@link TreeNodeView}. - */ - private List<TreeNodeView<?>> children; + protected static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>"; + + protected boolean animate; /** * A reference to the element that contains the children. */ - private Element childContainer; + protected Element childContainer; /** + * The children of this {@link TreeNodeView}. + */ + protected List<TreeNodeView<?>> children; + + /** * The list registration for the list of children. */ - private ListRegistration listReg; - + protected ListRegistration listReg; + /** * The info about children of this node. */ - private NodeInfo<?> nodeInfo; + protected NodeInfo<?> nodeInfo; /** * Indicates whether or not we've loaded the node info. */ - private boolean nodeInfoLoaded; + protected boolean nodeInfoLoaded; /** * Indicates whether or not this node is open. */ - private boolean open; - - /** - * The parent {@link TreeNodeView}. - */ - private TreeNodeView<?> parent; - - /** - * The info about this node. - */ - private NodeInfo<T> parentNodeInfo; + protected boolean open; /** * The containing {@link TreeView}. */ - private TreeView tree; + protected TreeView tree; /** + * The parent {@link SideBySideTreeNodeView}. + */ + private TreeNodeView<?> parentNode; + + /** + * The info about this node. + */ + private NodeInfo<T> parentNodeInfo; + + /** * This node's value. */ private T value; - /** - * Construct a {@link TreeNodeView}. - * - * @param tree the parent {@link TreeView} - * @param parent the parent {@link TreeNodeView} - * @param parentNodeInfo the {@link NodeInfo} of the parent - * @param elem the outer element of this {@link TreeNodeView}. - * @param value the value of this node - */ - TreeNodeView(final TreeView tree, final TreeNodeView<?> parent, - NodeInfo<T> parentNodeInfo, Element elem, T value) { - this.value = value; + public TreeNodeView(TreeView tree, TreeNodeView<?> parent, NodeInfo<T> parentNodeInfo, T value) { this.tree = tree; - this.parent = parent; - // We pass in parentNodeInfo so we know that it is type T. + this.parentNode = parent; this.parentNodeInfo = parentNodeInfo; - setElement(elem); + this.value = value; } - - /** - * Get the child at the specified index. - * - * @return the child node - */ - public TreeNodeView<?> getChild(int index) { - if ((index < 0) || (index >= getChildCount())) { - return null; - } - return children.get(index); - } - - /** - * Get the number of children under this node. - * - * @return the child count - */ + public int getChildCount() { return children == null ? 0 : children.size(); } - /** - * Get the parent {@link TreeNodeView}. - */ + public TreeNode<?> getChildNode(int childIndex) { + return children.get(childIndex); + } + + public TreeNodeView<?> getChildTreeNodeView(int childIndex) { + return children.get(childIndex); + } + + public TreeNode<?> getParentNode() { + return parentNode; + } + public TreeNodeView<?> getParentTreeNodeView() { - return parent; + return parentNode; } /** @@ -165,6 +143,15 @@ } /** + * Check if this is a root node at the top of the tree. + * + * @return true if a root node, false if not + */ + public boolean isRootNode() { + return getParentNode() == null; + } + + /** * Sets whether this item's children are displayed. * * @param open whether the item is open @@ -186,79 +173,40 @@ if (this.open == open) { return; } - + this.animate = true; this.open = open; if (open) { if (!nodeInfoLoaded) { - nodeInfo = tree.getTreeViewModel().getNodeInfo(value, this); + nodeInfo = tree.getTreeViewModel().getNodeInfo(getValue(), this); nodeInfoLoaded = true; } - + + preOpen(); // If we don't have any nodeInfo, we must be a leaf node. if (nodeInfo != null) { onOpen(nodeInfo); - } + } } else { cleanup(); - tree.maybeAnimateTreeNode(this); + postClose(); } // Update the image. updateImage(); } - - boolean consumeAnimate() { - boolean hasAnimate = animate; - animate = false; - return hasAnimate; - } - + /** - * Fire an event to the {@link Cell}. - * - * @param event the native event + * Unregister the list handler and destroy all child nodes. */ - void fireEventToCell(NativeEvent event) { - parentNodeInfo.onBrowserEvent(getCellParent(), value, event); - } - - /** - * @return the element that contains the rendered cell - */ - Element getCellParent() { - return getElement().getChild(1).cast(); - } - - /** - * @return the element that contains the children - */ - Element getChildContainer() { - return childContainer; - } - - /** - * @return the image element - */ - Element getImageElement() { - return getElement().getFirstChildElement(); - } - - NodeInfo<T> getParentNodeInfo() { - return parentNodeInfo; - } - - /** - * Cleanup this node and all its children. This node can still be used. - */ - private void cleanup() { + protected void cleanup() { // Unregister the list handler. if (listReg != null) { listReg.removeHandler(); listReg = null; } - - // Recursively kill chidren. + + // Recursively kill children. if (children != null) { for (TreeNodeView<?> child : children) { child.cleanup(); @@ -266,13 +214,25 @@ children = null; } } + + protected boolean consumeAnimate() { + boolean hasAnimate = animate; + animate = false; + return hasAnimate; + } + + protected abstract <C> TreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo, + Element childElem, C childValue, int idx); + + protected abstract <C> void emitHtml(StringBuilder sb, NodeInfo<C> nodeInfo, + List<C> childValues, List<TreeNodeView<?>> savedViews); /** * Ensure that the animation frame exists and return it. * * @return the animation frame */ - private Element ensureAnimationFrame() { + protected Element ensureAnimationFrame() { return ensureChildContainer().getParentElement(); } @@ -281,47 +241,79 @@ * * @return the child container */ - private Element ensureChildContainer() { - if (childContainer == null) { - // If this is a root node or the element does not exist, create it. - Element animFrame = getElement().appendChild( - Document.get().createDivElement()); - animFrame.getStyle().setPosition(Position.RELATIVE); - animFrame.getStyle().setOverflow(Overflow.HIDDEN); - childContainer = animFrame.appendChild(Document.get().createDivElement()); + protected abstract Element ensureChildContainer(); + + /** + * Fire an event to the {@link com.google.gwt.bikeshed.cells.client.Cell}. + * + * @param event the native event + */ + protected void fireEventToCell(NativeEvent event) { + if (parentNodeInfo != null) { + Element cellParent = getCellParent(); + parentNodeInfo.onBrowserEvent(cellParent, value, event); + } else { + Window.alert("parentNodeInfo == null"); } + } + + /** + * Returns the element that parents the cell contents of this node. + */ + protected abstract Element getCellParent(); + + /** + * Returns the element that contains the children of this node. + */ + protected Element getChildContainer() { return childContainer; } - - private Object getValueKey() { - return parentNodeInfo.getKey(getValue()); - } - + /** - * Check if this is a root node at the top of the tree. - * - * @return true if a root node, false if not + * Returns the element corresponding to the open/close image. */ - private boolean isRootNode() { - return getParentTreeNodeView() == null; + protected abstract Element getImageElement(); + + /** + * Returns the left position of the open/close image within a tree item node. + * The default implementation returns 0. + */ + protected int getImageLeft() { + return 0; + } + + /** + * Returns the nodeInfo for this node's parent. + */ + protected NodeInfo<T> getParentNodeInfo() { + return parentNodeInfo; } /** - * Set up the node when it is opened. + * Returns the key for the value of this node using the parent's + * implementation of NodeInfo.getKey(). + */ + protected Object getValueKey() { + return getParentNodeInfo().getKey(getValue()); + } + + /** + * Set up the node when it is opened. Delegates to createMap(), emitHtml(), + * and createTreeNodeView() for subclass-specific functionality. * * @param nodeInfo the {@link NodeInfo} that provides information about the * child values * @param <C> the child data type of the node. */ - private <C> void onOpen(final NodeInfo<C> nodeInfo) { + protected <C> void onOpen(final NodeInfo<C> nodeInfo) { // Add a loading message. ensureChildContainer().setInnerHTML(tree.getLoadingHtml()); ensureAnimationFrame().getStyle().setProperty("display", ""); - + // Get the node info. ListModel<C> listModel = nodeInfo.getListModel(); listReg = listModel.addListHandler(new ListHandler<C>() { - public void onDataChanged(ListEvent<C> event) { + public void onDataChanged(ListEvent<C> event) { // TODO - handle event start and length // Construct a map of former child views based on their value keys. @@ -335,16 +327,13 @@ } } } - + // Hide the child container so we can animate it. - ensureAnimationFrame().getStyle().setDisplay(Display.NONE); + if (tree.isAnimationEnabled()) { + ensureAnimationFrame().getStyle().setDisplay(Display.NONE); + } - // Construct the child contents. - TreeViewModel model = tree.getTreeViewModel(); - int imageWidth = tree.getImageWidth(); - Cell<C> theCell = nodeInfo.getCell(); - StringBuilder sb = new StringBuilder(); - + List<TreeNodeView<?>> savedViews = new ArrayList<TreeNodeView<?>>(); for (C childValue : event.getValues()) { // Remove any child elements that correspond to prior children // so the call to setInnerHtml will not destroy them @@ -352,29 +341,21 @@ if (savedView != null) { savedView.getElement().removeFromParent(); } - - sb.append("<div style=\"position:relative;padding-left:"); - sb.append(imageWidth); - sb.append("px;\">"); - if (savedView != null) { - sb.append(tree.getOpenImageHtml()); - } else if (model.isLeaf(childValue)) { - sb.append(LEAF_IMAGE); - } else { - sb.append(tree.getClosedImageHtml()); - } - sb.append("<div>"); - theCell.render(childValue, sb); - sb.append("</div>"); - sb.append("</div>"); + savedViews.add(savedView); } + + // Construct the child contents. + StringBuilder sb = new StringBuilder(); + emitHtml(sb, nodeInfo, event.getValues(), savedViews); childContainer.setInnerHTML(sb.toString()); // Create the child TreeNodeViews from the new elements. children = new ArrayList<TreeNodeView<?>>(); Element childElem = childContainer.getFirstChildElement(); + int idx = 0; for (C childValue : event.getValues()) { - TreeNodeView<C> child = new TreeNodeView<C>(tree, TreeNodeView.this, nodeInfo, childElem, childValue); + TreeNodeView<C> child = createTreeNodeView(nodeInfo, childElem, + childValue, idx); TreeNodeView<?> savedChild = map.get(nodeInfo.getKey(childValue)); // Copy the saved child's state into the new child if (savedChild != null) { @@ -383,18 +364,22 @@ child.listReg = savedChild.listReg; child.nodeInfo = savedChild.nodeInfo; child.nodeInfoLoaded = savedChild.nodeInfoLoaded; - child.open = savedChild.open; + child.open = savedChild.getState(); - // Copy the animation frame element to the new child + // Copy the child container element to the new child child.getElement().appendChild(savedChild.childContainer.getParentElement()); } children.add(child); childElem = childElem.getNextSiblingElement(); + + idx++; } // Animate the child container open. - tree.maybeAnimateTreeNode(TreeNodeView.this); + if (tree.isAnimationEnabled()) { + tree.maybeAnimateTreeNode(TreeNodeView.this); + } } public void onSizeChanged(SizeChangeEvent event) { @@ -404,16 +389,29 @@ } /** + * Called from setState(boolean, boolean) following the call to cleanup(). + */ + protected void postClose() { + } + + /** + * Called from setState(boolean, boolean) prior to the call to onOpen(). + */ + protected void preOpen() { + } + + /** * Update the image based on the current state. */ - private void updateImage() { + protected void updateImage() { // Early out if this is a root node. if (isRootNode()) { return; } // Replace the image element with a new one. - String html = open ? tree.getOpenImageHtml() : tree.getClosedImageHtml(); + int imageLeft = getImageLeft(); + String html = open ? tree.getOpenImageHtml(imageLeft) : tree.getClosedImageHtml(imageLeft); if (nodeInfoLoaded && nodeInfo == null) { html = LEAF_IMAGE; }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java index 529ccba..17dd5c9 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java +++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
@@ -17,49 +17,20 @@ import com.google.gwt.animation.client.Animation; import com.google.gwt.core.client.GWT; -import com.google.gwt.dom.client.Document; -import com.google.gwt.dom.client.Element; -import com.google.gwt.dom.client.NativeEvent; -import com.google.gwt.dom.client.Style.Display; -import com.google.gwt.dom.client.Style.Position; -import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.resources.client.ClientBundle; import com.google.gwt.resources.client.ImageResource; -import com.google.gwt.user.client.DOM; -import com.google.gwt.user.client.Event; import com.google.gwt.user.client.ui.AbstractImagePrototype; -import com.google.gwt.user.client.ui.HasAnimation; import com.google.gwt.user.client.ui.Widget; -import java.util.ArrayList; - /** * A view of a tree. */ -public class TreeView extends Widget implements HasAnimation { - - private static final Resources DEFAULT_RESOURCES = GWT.create(Resources.class); +public abstract class TreeView extends Widget { /** - * A ClientBundle that provides images for this widget. + * An Animation of a {@link TreeNodeView}. */ - public interface Resources extends ClientBundle { - - /** - * An image indicating a closed branch. - */ - ImageResource treeClosed(); - - /** - * An image indicating an open branch. - */ - ImageResource treeOpen(); - } - - /** - * The animation used for {@link TreeNodeView}. - */ - public abstract static class TreeNodeAnimation extends Animation { + public abstract static class TreeNodeViewAnimation extends Animation { /** * The default animation delay in milliseconds. @@ -72,212 +43,56 @@ private int duration = DEFAULT_ANIMATION_DURATION; /** - * Not instantiable. - */ - private TreeNodeAnimation() { - } - - /** - * Get the duration of animations in milliseconds. + * Animate a {@link TreeNodeView} into its new state. * - * @return the animation duration + * @param node the {@link TreeNodeView} to animate + * @param isAnimationEnabled true to animate */ + public abstract void animate(TreeNodeView<?> node, + boolean isAnimationEnabled); + public int getDuration() { return duration; } - - /** - * Set the animation duration in milliseconds. - * - * @param duration the duration - */ + public void setDuration(int duration) { this.duration = duration; } + } + + /** + * A ClientBundle that provides images for this widget. + */ + interface Resources extends ClientBundle { /** - * Animate a {@link TreeNodeView} into its new state. - * - * @param node the {@link TreeNodeView} to animate - * @param isAnimationEnabled true to animate + * An image indicating a closed branch. */ - abstract void animate(TreeNodeView<?> node, boolean isAnimationEnabled); + ImageResource treeClosed(); + + /** + * An image indicating an open branch. + */ + ImageResource treeOpen(); } + private static final Resources DEFAULT_RESOURCES = GWT.create(Resources.class); + /** - * A {@link TreeNodeAnimation} that reveals the contents of child nodes. + * The animation. */ - public static class RevealAnimation extends TreeNodeAnimation { - - /** - * Create a new {@link RevealAnimation}. - * - * @return the new animation - */ - public static RevealAnimation create() { - return new RevealAnimation(); - } - - /** - * The container that holds the child container. - */ - Element animFrame; - /** - * The container that holds the children. - */ - Element childContainer; - - /** - * The target height when opening, the start height when closing. - */ - int height; - /** - * True if the node is opening, false if closing. - */ - boolean opening; - - /** - * Not instantiable. - */ - private RevealAnimation() { - } - - @Override - protected void onComplete() { - cleanup(); - } - - @Override - protected void onStart() { - if (opening) { - animFrame.getStyle().setHeight(1.0, Unit.PX); - animFrame.getStyle().clearDisplay(); - height = childContainer.getScrollHeight(); - } else { - height = childContainer.getOffsetHeight(); - } - } - - @Override - protected void onUpdate(double progress) { - if (opening) { - double curHeight = progress * height; - animFrame.getStyle().setHeight(curHeight, Unit.PX); - } else { - double curHeight = (1.0 - progress) * height; - animFrame.getStyle().setHeight(curHeight, Unit.PX); - } - } - - /** - * Animate a {@link TreeNodeView} into its new state. - * - * @param node the {@link TreeNodeView} to animate - * @param isAnimationEnabled true to animate - */ - @Override - void animate(TreeNodeView<?> node, boolean isAnimationEnabled) { - // Cancel any pending animations. - cancel(); - - // Initialize the fields. - this.opening = node.getState(); - childContainer = node.getChildContainer(); - animFrame = childContainer.getParentElement(); - - if (isAnimationEnabled) { - // Animated. - int duration = getDuration(); - int childCount = childContainer.getChildCount(); - if (childCount < 4) { - // Reduce the duration if there are less than four items or it will - // look really slow. - duration = (int) ((childCount / 4.0) * duration); - } - run(duration); - } else { - // Non animated. - cleanup(); - } - } - - /** - * Put the node back into a clean state and clear fields. - */ - private void cleanup() { - if (opening) { - animFrame.getStyle().clearDisplay(); - } else { - animFrame.getStyle().setDisplay(Display.NONE); - childContainer.setInnerHTML(""); - } - animFrame.getStyle().clearHeight(); - this.childContainer = null; - this.animFrame = null; - } - } - + protected TreeNodeViewAnimation animation; + /** - * A {@link TreeNodeAnimation} that slides children into view. + * The hidden root node in the tree. */ - public static class SlideAnimation extends RevealAnimation { - /** - * Create a new {@link RevealAnimation}. - * - * @return the new animation - */ - public static SlideAnimation create() { - return new SlideAnimation(); - } - - /** - * Not instantiable. - */ - private SlideAnimation() { - } - - @Override - protected void onComplete() { - childContainer.getStyle().clearPosition(); - childContainer.getStyle().clearTop(); - childContainer.getStyle().clearWidth(); - super.onComplete(); - } - - @Override - protected void onStart() { - super.onStart(); - if (opening) { - childContainer.getStyle().setTop(-height, Unit.PX); - } else { - childContainer.getStyle().setTop(0, Unit.PX); - } - childContainer.getStyle().setPosition(Position.RELATIVE); - } - - @Override - protected void onUpdate(double progress) { - super.onUpdate(progress); - if (opening) { - double curTop = (1.0 - progress) * -height; - childContainer.getStyle().setTop(curTop, Unit.PX); - } else { - double curTop = progress * -height; - childContainer.getStyle().setTop(curTop, Unit.PX); - } - } - } - - /** - * We use one animation for the entire {@link TreeView}. - */ - private TreeNodeAnimation animation = SlideAnimation.create(); + protected TreeNodeView<?> rootNode; /** * The HTML used to generate the closed image. */ private String closedImageHtml; - + /** * Indicates whether or not animations are enabled. */ @@ -298,36 +113,14 @@ * The HTML used to generate the open image. */ private String openImageHtml; - - /** - * The Resources used by this tree. - */ - private Resources resources; - - /** - * The hidden root node in the tree. - */ - private TreeNodeView<?> rootNode; - + /** * Construct a new {@link TreeView}. * - * @param <T> the type of data in the root node * @param viewModel the {@link TreeViewModel} that backs the tree - * @param rootValue the hidden root value of the tree */ - public <T> TreeView(TreeViewModel viewModel, T rootValue) { + public TreeView(TreeViewModel viewModel) { this.model = viewModel; - this.resources = DEFAULT_RESOURCES; - setElement(Document.get().createDivElement()); - setStyleName("gwt-TreeView"); - - // Add event handlers. - sinkEvents(Event.ONCLICK | Event.ONMOUSEDOWN | Event.ONMOUSEUP); - - // Associate a view with the item. - rootNode = new TreeNodeView<T>(this, null, null, getElement(), rootValue); - rootNode.setState(true); } /** @@ -337,20 +130,10 @@ * @return the animation * @see #isAnimationEnabled() */ - public TreeNodeAnimation getAnimation() { + public Animation getAnimation() { return animation; } - - /** - * Get the HTML string that is displayed while nodes wait for their children - * to load. - * - * @return the loading HTML string - */ - public String getLoadingHtml() { - return loadingHtml; - } - + public TreeViewModel getTreeViewModel() { return model; } @@ -358,60 +141,36 @@ public boolean isAnimationEnabled() { return isAnimationEnabled; } - - @Override - public void onBrowserEvent(Event event) { - super.onBrowserEvent(event); - - int eventType = DOM.eventGetType(event); - switch (eventType) { - case Event.ONMOUSEUP: - Element currentTarget = event.getCurrentEventTarget().cast(); - if (currentTarget == getElement()) { - Element target = event.getEventTarget().cast(); - elementClicked(target, event); - } - break; - } - } - + /** * Set the animation used to open and close nodes in this tree. You must call * {@link #setAnimationEnabled(boolean)} to enable or disable animation. * - * @param animation the animation + * @param animation a {@link TreeNodeViewAnimation}. * @see #setAnimationEnabled(boolean) */ - public void setAnimation(TreeNodeAnimation animation) { + public void setAnimation(TreeNodeViewAnimation animation) { assert animation != null : "animation cannot be null"; this.animation = animation; } - + public void setAnimationEnabled(boolean enable) { this.isAnimationEnabled = enable; if (!enable && animation != null) { animation.cancel(); } } - - /** - * Set the HTML string that will be displayed when a node is waiting for its - * child nodes to load. - * - * @param loadingHtml the HTML string - */ - public void setLoadingHtml(String loadingHtml) { - this.loadingHtml = loadingHtml; - } - /** * @return the HTML to render the closed image. */ - String getClosedImageHtml() { + protected String getClosedImageHtml(int left) { if (closedImageHtml == null) { - AbstractImagePrototype proto = AbstractImagePrototype.create(resources.treeClosed()); + AbstractImagePrototype proto = + AbstractImagePrototype.create(DEFAULT_RESOURCES.treeClosed()); + // CHECKSTYLE_OFF closedImageHtml = proto.getHTML().replace("style='", - "style='position:absolute;left:0px;top:0px;"); + "style='position:absolute;left:" + left + "px;top:0px;"); + // CHECKSTYLE_ON } return closedImageHtml; } @@ -421,19 +180,32 @@ * * @return the maximum width required for images. */ - int getImageWidth() { - return Math.max(resources.treeClosed().getWidth(), - resources.treeOpen().getWidth()); + protected int getImageWidth() { + return Math.max(DEFAULT_RESOURCES.treeClosed().getWidth(), + DEFAULT_RESOURCES.treeOpen().getWidth()); } /** + * Get the HTML string that is displayed while nodes wait for their children + * to load. + * + * @return the loading HTML string + */ + protected String getLoadingHtml() { + return loadingHtml; + } + + /** * @return the HTML to render the open image. */ - String getOpenImageHtml() { + protected String getOpenImageHtml(int left) { if (openImageHtml == null) { - AbstractImagePrototype proto = AbstractImagePrototype.create(resources.treeOpen()); + AbstractImagePrototype proto = + AbstractImagePrototype.create(DEFAULT_RESOURCES.treeOpen()); + // CHECKSTYLE_OFF openImageHtml = proto.getHTML().replace("style='", - "style='position:absolute;left:0px;top:0px;"); + "style='position:absolute;left:" + left + "px;top:0px;"); + // CHECKSTYLE_ON } return openImageHtml; } @@ -443,59 +215,27 @@ * * @param node the node to animate */ - void maybeAnimateTreeNode(TreeNodeView<?> node) { - animation.animate(node, node.consumeAnimate() && isAnimationEnabled); + protected void maybeAnimateTreeNode(TreeNodeView<?> node) { + if (animation != null) { + animation.animate(node, node.consumeAnimate() && isAnimationEnabled()); + } } /** - * Collects parents going up the element tree, terminated at the tree root. + * Set the HTML string that will be displayed when a node is waiting for its + * child nodes to load. + * + * @param loadingHtml the HTML string */ - private void collectElementChain(ArrayList<Element> chain, Element hRoot, - Element hElem) { - if ((hElem == null) || (hElem == hRoot)) { - return; - } - - collectElementChain(chain, hRoot, hElem.getParentElement()); - chain.add(hElem); + protected void setLoadingHtml(String loadingHtml) { + this.loadingHtml = loadingHtml; } - private boolean elementClicked(Element hElem, NativeEvent event) { - ArrayList<Element> chain = new ArrayList<Element>(); - collectElementChain(chain, getElement(), hElem); - - TreeNodeView<?> nodeView = findItemByChain(chain, 0, rootNode); - if (nodeView != null && nodeView != rootNode) { - if (nodeView.getImageElement().isOrHasChild(hElem)) { - nodeView.setState(!nodeView.getState(), true); - return true; - } else if (nodeView.getCellParent().isOrHasChild(hElem)) { - nodeView.fireEventToCell(event); - return true; - } - } - - return false; + TreeNodeView<?> getRootNode() { + return rootNode; } - private TreeNodeView<?> findItemByChain(ArrayList<Element> chain, int idx, - TreeNodeView<?> parent) { - if (idx == chain.size()) { - return parent; - } - - Element hCurElem = chain.get(idx); - for (int i = 0, n = parent.getChildCount(); i < n; ++i) { - TreeNodeView<?> child = parent.getChild(i); - if (child.getElement() == hCurElem) { - TreeNodeView<?> retItem = findItemByChain(chain, idx + 1, child); - if (retItem == null) { - return child; - } - return retItem; - } - } - - return findItemByChain(chain, idx + 1, parent); + void setRootNode(TreeNodeView<?> rootNode) { + this.rootNode = rootNode; } }
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java index b3ae963..14f1656 100644 --- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java +++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
@@ -119,10 +119,10 @@ * {@link Cell} to retrieve the children of the specified value. * * @param value the value in the parent node - * @param treeNodeView the {@link TreeNodeView} that contains the value + * @param treeNode the {@link TreeNode} that contains the value * @return the {@link NodeInfo} */ - <T> NodeInfo<?> getNodeInfo(T value, TreeNodeView<T> treeNodeView); + <T> NodeInfo<?> getNodeInfo(T value, TreeNode<T> treeNode); /** * Check if the value is known to be a leaf node.
diff --git a/bikeshed/war/Stocks.css b/bikeshed/war/Stocks.css index 7aca7ac..37018e6 100644 --- a/bikeshed/war/Stocks.css +++ b/bikeshed/war/Stocks.css
@@ -1,5 +1,24 @@ /** Add css rules here for your application. */ +div.gwt-SideBySideTreeColumn { + width: 100px; + height: 200px; + overflow-y: scroll; + overflow-x: auto; + position: relative; +} + +div.gwt-SideBySideTreeView { + border: 2px solid black; +} + +div.gwt-SideBySideTree-selectedItem { + background-color: rgb(56, 117, 215); +} + +div.gwt-SideBySideTree-unselectedItem { + background-color: rgb(255, 255, 255); +} /** Example rules used by the template application (remove for your app) */ h1 {
diff --git a/bikeshed/war/Tree.css b/bikeshed/war/Tree.css index 7aca7ac..37018e6 100644 --- a/bikeshed/war/Tree.css +++ b/bikeshed/war/Tree.css
@@ -1,5 +1,24 @@ /** Add css rules here for your application. */ +div.gwt-SideBySideTreeColumn { + width: 100px; + height: 200px; + overflow-y: scroll; + overflow-x: auto; + position: relative; +} + +div.gwt-SideBySideTreeView { + border: 2px solid black; +} + +div.gwt-SideBySideTree-selectedItem { + background-color: rgb(56, 117, 215); +} + +div.gwt-SideBySideTree-unselectedItem { + background-color: rgb(255, 255, 255); +} /** Example rules used by the template application (remove for your app) */ h1 {