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 {