Refactoring Tree code. SideBySideTreeView now uses SimpleCellList, which a protected method that allows users to use any ListView. SideBySideTreeNodeView shares a lot of code with SimpleCellList. Added animations to SideBySideTreeView. NodeInfo now includes a SelectionModel, but still allows a single instance to be used across an entire tree of different types. NodeInfo now returns one Cell instead of a list of HasCell, but CompositeCell has been added to combine HasCells into a single Cell.
Review at http://gwt-code-reviews.appspot.com/390801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7967 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
index 32271d7..1248a46 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/Cell.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -20,7 +20,7 @@
/**
* A light weight representation of a renderable object.
- *
+ *
* @param <C> the type that this Cell represents
* @param <V> the type of view data that this cell consumes
*/
@@ -35,9 +35,18 @@
}
/**
+ * Check if this cell depends on the selection state.
+ *
+ * @return true if dependant on selection, false if not
+ */
+ public boolean dependsOnSelection() {
+ return false;
+ }
+
+ /**
* Handle a browser event that took place within the cell. The default
* implementation returns null.
- *
+ *
* @param parent the parent Element
* @param value the value associated with the cell
* @param viewData the view data associated with the cell, or null
@@ -53,7 +62,7 @@
/**
* Render a cell as HTML into a StringBuilder, suitable for passing to
* {@link Element#setInnerHTML} on a container element.
- *
+ *
* @param value the cell value to be rendered
* @param viewData view data associated with the cell
* @param sb the StringBuilder to be written to
diff --git a/bikeshed/src/com/google/gwt/bikeshed/cells/client/CompositeCell.java b/bikeshed/src/com/google/gwt/bikeshed/cells/client/CompositeCell.java
new file mode 100644
index 0000000..902fb8d
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/cells/client/CompositeCell.java
@@ -0,0 +1,159 @@
+/*
+ * 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.cells.client;
+
+import com.google.gwt.bikeshed.list.client.HasCell;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.NativeEvent;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * A {@link Cell} that is composed of other {@link Cell}s.
+ * </p>
+ * <p>
+ * When this cell is rendered, it will render each component {@link Cell} inside
+ * a span. If the component {@link Cell} uses block level elements (such as a
+ * Div), the component cells will stack vertically.
+ * </p>
+ *
+ * @param <C> the type that this Cell represents
+ * @param <V> the type of view data that this cell consumes
+ */
+public class CompositeCell<C, V> extends Cell<C, V> {
+
+ /**
+ * The cells that compose this {@link Cell}.
+ */
+ private List<HasCell<C, ?, V>> hasCells = new ArrayList<HasCell<C, ?, V>>();
+
+ /**
+ * Add a {@link HasCell} to the composite.
+ *
+ * @param hasCell the {@link HasCell} to add
+ */
+ public void addHasCell(HasCell<C, ?, V> hasCell) {
+ hasCells.add(hasCell);
+ }
+
+ @Override
+ public boolean consumesEvents() {
+ // TODO(jlabanca): Should we cache this value? Can it change?
+ for (HasCell<C, ?, V> hasCell : hasCells) {
+ if (hasCell.getCell().consumesEvents()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean dependsOnSelection() {
+ // TODO(jlabanca): Should we cache this value? Can it change?
+ for (HasCell<C, ?, V> hasCell : hasCells) {
+ if (hasCell.getCell().dependsOnSelection()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Insert a {@link HasCell} into the composite.
+ *
+ * @param index the index to insert into
+ * @param hasCell the {@link HasCell} to insert
+ */
+ public void insertHasCell(int index, HasCell<C, ?, V> hasCell) {
+ hasCells.add(index, hasCell);
+ }
+
+ @Override
+ public V onBrowserEvent(Element parent, C value, V viewData,
+ NativeEvent event, ValueUpdater<C, V> valueUpdater) {
+ int index = 0;
+ Element target = event.getEventTarget().cast();
+ Element wrapper = parent.getFirstChildElement();
+ while (wrapper != null) {
+ if (wrapper.isOrHasChild(target)) {
+ return onBrowserEventImpl(wrapper, value, viewData, event,
+ valueUpdater, hasCells.get(index));
+ }
+
+ index++;
+ wrapper = wrapper.getNextSiblingElement();
+ }
+ return viewData;
+ }
+
+ /**
+ * Remove a {@link HasCell} from the composite.
+ *
+ * @param hasCell the {@link HasCell} to remove
+ */
+ public void removeHasCell(HasCell<C, ?, V> hasCell) {
+ hasCells.remove(hasCell);
+ }
+
+ @Override
+ public void render(C value, V viewData, StringBuilder sb) {
+ for (HasCell<C, ?, V> hasCell : hasCells) {
+ render(value, viewData, sb, hasCell);
+ }
+ }
+
+ @Override
+ public void setValue(Element parent, C object, V viewData) {
+ for (HasCell<C, ?, V> hasCell : hasCells) {
+ setValueImpl(parent, object, viewData, hasCell);
+ }
+ }
+
+ protected <X> void render(C value, V viewData, StringBuilder sb,
+ HasCell<C, X, V> hasCell) {
+ Cell<X, V> cell = hasCell.getCell();
+ sb.append("<span>");
+ cell.render(hasCell.getValue(value), viewData, sb);
+ sb.append("</span>");
+ }
+
+ private <X> V onBrowserEventImpl(Element parent, final C object, V viewData,
+ NativeEvent event, final ValueUpdater<C, V> valueUpdater,
+ final HasCell<C, X, V> hasCell) {
+ ValueUpdater<X, V> tempUpdater = null;
+ final FieldUpdater<C, X, V> fieldUpdater = hasCell.getFieldUpdater();
+ if (fieldUpdater != null) {
+ tempUpdater = new ValueUpdater<X, V>() {
+ public void update(X value, V viewData) {
+ fieldUpdater.update(-1, object, value, viewData);
+ if (valueUpdater != null) {
+ valueUpdater.update(object, viewData);
+ }
+ }
+ };
+ }
+ Cell<X, V> cell = hasCell.getCell();
+ return cell.onBrowserEvent(parent, hasCell.getValue(object), viewData,
+ event, tempUpdater);
+ }
+
+ private <X> void setValueImpl(Element parent, C object, V viewData,
+ HasCell<C, X, V> hasCell) {
+ hasCell.getCell().setValue(parent, hasCell.getValue(object), viewData);
+ }
+}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/HasCell.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/HasCell.java
index c4a4228..69315ca 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/HasCell.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/HasCell.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -22,15 +22,13 @@
* An interface for extracting a value from an underlying data type, provide a
* cell to render that value, and provide a FieldUpdater to perform notification
* of updates to the cell.
- *
+ *
* @param <T> the underlying data type
* @param <C> the cell data type
* @param <V> the view data type
*/
public interface HasCell<T, C, V> {
- boolean dependsOnSelection();
-
Cell<C, V> getCell();
FieldUpdater<T, C, V> getFieldUpdater();
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/ListView.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/ListView.java
index 3b13cb3..108f9e1 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/ListView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/ListView.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -16,19 +16,20 @@
package com.google.gwt.bikeshed.list.client;
import com.google.gwt.bikeshed.list.shared.Range;
+import com.google.gwt.bikeshed.list.shared.SelectionModel;
import java.util.List;
/**
* A list view.
- *
+ *
* @param <T> the data type of each row
*/
public interface ListView<T> {
/**
* A list view delegate, implemented by classes that supply data to a view.
- *
+ *
* @param <T> the data type of each row
*/
public interface Delegate<T> {
@@ -39,7 +40,21 @@
Range getRange();
+ /**
+ * Set a range of data in the view.
+ *
+ * @param start the start index of the data
+ * @param length the length of the data
+ * @param values the values within the range
+ */
void setData(int start, int length, List<T> values);
void setDataSize(int size, boolean isExact);
+
+ /**
+ * Set the {@link SelectionModel} used by this {@link ListView}.
+ *
+ * @param selectionModel the {@link SelectionModel}
+ */
+ void setSelectionModel(SelectionModel<? super T> selectionModel);
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
index 6a1e047..8d98aa1 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/PagingTableListView.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -41,7 +41,7 @@
/**
* A list view that supports paging and columns.
- *
+ *
* @param <T> the data type of each row
*/
public class PagingTableListView<T> extends Widget implements ListView<T> {
@@ -70,7 +70,8 @@
private ProvidesKey<T> providesKey;
private HandlerRegistration selectionHandler;
- private SelectionModel<T> selectionModel;
+ private SelectionModel<? super T> selectionModel;
+
private TableElement table;
private TableSectionElement tbody;
private TableSectionElement tfoot;
@@ -86,7 +87,7 @@
/**
* Constructs a table with the given page size.
- *
+ *
* @param pageSize the page size
*/
public PagingTableListView(final int pageSize) {
@@ -349,6 +350,8 @@
TableCellElement cell = row.getCells().getItem(c);
StringBuilder sb = new StringBuilder();
columns.get(c).render(q, sb);
+ // TODO(jlabanca): Render as one HTML string instead of embedding HTML
+ // in each cell.
cell.setInnerHTML(sb.toString());
// TODO: Really total hack! There's gotta be a better way...
@@ -362,7 +365,7 @@
public void setDataSize(int size, boolean isExact) {
this.size = size;
- refresh();
+ updateRowVisibility();
}
public void setDelegate(Delegate<T> delegate) {
@@ -371,9 +374,9 @@
/**
* Set the number of rows per page and refresh the table.
- *
+ *
* @param pageSize the page size
- *
+ *
* @throw {@link IllegalArgumentException} if pageSize is negative or 0
*/
public void setPageSize(int pageSize) {
@@ -396,9 +399,9 @@
}
/**
- * Set the starting index of the current visible page. The actual page
- * start will be clamped in the range [0, getSize() - 1].
- *
+ * Set the starting index of the current visible page. The actual page start
+ * will be clamped in the range [0, getSize() - 1].
+ *
* @param pageStart the index of the row that should appear at the start of
* the page
*/
@@ -410,19 +413,16 @@
/**
* Sets the {@link ProvidesKey} instance that will be used to generate keys
* for each record object as needed.
- *
- * @param providesKey an instance of {@link ProvidesKey<T>} used to generate
- * keys for record objects.
+ *
+ * @param providesKey an instance of {@link ProvidesKey} used to generate keys
+ * for record objects.
*/
- // TODO - when is this valid? Do we rehash column view data if it changes?
+ // TODO - when is this valid? Do we rehash column view data if it changes?
public void setProvidesKey(ProvidesKey<T> providesKey) {
this.providesKey = providesKey;
}
- /**
- * Sets the selection model.
- */
- public void setSelectionModel(SelectionModel<T> selectionModel) {
+ public void setSelectionModel(SelectionModel<? super T> selectionModel) {
if (selectionHandler != null) {
selectionHandler.removeHandler();
selectionHandler = null;
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java
index 06156ca..d83fd87 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/SimpleCellList.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -17,172 +17,166 @@
import com.google.gwt.bikeshed.cells.client.Cell;
import com.google.gwt.bikeshed.cells.client.ValueUpdater;
+import com.google.gwt.bikeshed.list.client.impl.SimpleCellListImpl;
import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter.DefaultRange;
+import com.google.gwt.bikeshed.list.shared.SelectionModel;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.Style.Display;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.Widget;
-import java.util.ArrayList;
import java.util.List;
/**
* A single column list of cells.
- *
+ *
* @param <T> the data type of list items
*/
public class SimpleCellList<T> extends Widget implements ListView<T> {
+ /**
+ * Style name applied to even rows.
+ * TODO(jlabanca): These style names only apply to SideBySideTreeView.
+ */
+ private static final String STYLENNAME_EVEN = "gwt-sstree-evenRow";
+
+ /**
+ * Style name applied to odd rows.
+ * TODO(jlabanca): These style names only apply to SideBySideTreeView.
+ */
+ private static final String STYLENNAME_ODD = "gwt-sstree-oddRow";
+
+ /**
+ * Style name applied to selected rows.
+ * TODO(jlabanca): These style names only apply to SideBySideTreeView.
+ */
+ private static final String STYLENNAME_SELECTED = "gwt-sstree-selectedItem";
+
private final Cell<T, Void> cell;
- private final ArrayList<T> data = new ArrayList<T>();
- private Delegate<T> delegate;
- private int increment;
- private int initialMaxSize;
- private int maxSize;
- private int seq; // for debugging - TODO: remove
- private final Element showFewerElem;
- private final Element showMoreElem;
- private int size;
- private final Element tmpElem;
+ private final SimpleCellListImpl<T> impl;
private ValueUpdater<T, Void> valueUpdater;
public SimpleCellList(Cell<T, Void> cell, int maxSize, int increment) {
- this.initialMaxSize = this.maxSize = maxSize;
- this.increment = increment;
this.cell = cell;
- this.seq = 0;
- tmpElem = Document.get().createDivElement();
+ // Create the DOM hierarchy.
+ Element childContainer = Document.get().createDivElement();
- showMoreElem = Document.get().createDivElement();
- showMoreElem.setInnerHTML("<button>Show more</button>");
+ Element showMoreElem = Document.get().createPushButtonElement();
+ showMoreElem.setInnerText("Show more");
- showFewerElem = Document.get().createDivElement();
- showFewerElem.setInnerHTML("<button>Show fewer</button>");
+ Element showFewerElem = Document.get().createPushButtonElement();
+ showFewerElem.setInnerText("Show fewer");
- showOrHide(showMoreElem, false);
- showOrHide(showFewerElem, false);
+ Element emptyMessageElem = Document.get().createDivElement();
+ emptyMessageElem.setInnerHTML("<i>no data</i>");
// TODO: find some way for cells to communicate what they're interested in.
DivElement outerDiv = Document.get().createDivElement();
- DivElement innerDiv = Document.get().createDivElement();
- outerDiv.appendChild(innerDiv);
+ outerDiv.appendChild(childContainer);
+ outerDiv.appendChild(emptyMessageElem);
outerDiv.appendChild(showFewerElem);
outerDiv.appendChild(showMoreElem);
setElement(outerDiv);
- sinkEvents(Event.ONCLICK);
- sinkEvents(Event.ONCHANGE);
+ sinkEvents(Event.ONCLICK | Event.ONCHANGE | Event.MOUSEEVENTS);
+
+ // Create the implementation.
+ impl = new SimpleCellListImpl<T>(this, cell, maxSize, increment,
+ childContainer, emptyMessageElem, showMoreElem, showFewerElem) {
+
+ @Override
+ protected void emitHtml(StringBuilder sb, List<T> values, int start,
+ Cell<T, Void> cell, SelectionModel<? super T> selectionModel) {
+ int length = values.size();
+ int end = start + length;
+ for (int i = start; i < end; i++) {
+ T value = values.get(i - start);
+ boolean isSelected = selectionModel == null ? false
+ : selectionModel.isSelected(value);
+ sb.append("<div __idx='").append(i).append("'");
+ sb.append(" class='");
+ sb.append(i % 2 == 0 ? STYLENNAME_EVEN : STYLENNAME_ODD);
+ if (isSelected) {
+ sb.append(" ").append(STYLENNAME_SELECTED);
+ }
+ sb.append("'>");
+ cell.render(value, null, sb);
+ sb.append("</div>");
+ }
+ }
+
+ @Override
+ protected void setSelected(Element elem, boolean selected) {
+ setStyleName(elem, STYLENNAME_SELECTED, selected);
+ }
+ };
}
public Range getRange() {
- return new DefaultRange(0, maxSize);
+ return impl.getRange();
}
@Override
public void onBrowserEvent(Event event) {
+ super.onBrowserEvent(event);
+
Element target = event.getEventTarget().cast();
- if (target.getParentElement() == showMoreElem) {
- this.maxSize += increment;
- sizeChanged();
- if (delegate != null) {
- delegate.onRangeChanged(this);
+ int type = event.getTypeInt();
+ if (type == Event.ONCLICK) {
+ // Open the node when the open image is clicked.
+ Element showFewerElem = impl.getShowFewerElem();
+ Element showMoreElem = impl.getShowMoreElem();
+ if (showFewerElem != null && showFewerElem.isOrHasChild(target)) {
+ impl.showFewer();
+ return;
+ } else if (showMoreElem != null && showMoreElem.isOrHasChild(target)) {
+ impl.showMore();
+ return;
}
- } else if (target.getParentElement() == showFewerElem) {
- this.maxSize = Math.max(initialMaxSize, maxSize - increment);
- sizeChanged();
- if (delegate != null) {
- delegate.onRangeChanged(this);
- }
- } else {
- String idxString = "";
- while ((target != null)
- && ((idxString = target.getAttribute("__idx")).length() == 0)) {
- target = target.getParentElement();
- }
- if (idxString.length() > 0) {
- int idx = Integer.parseInt(idxString);
- cell.onBrowserEvent(target, data.get(idx), null, event, valueUpdater);
+ }
+
+ // Forward the event to the cell.
+ String idxString = "";
+ while ((target != null)
+ && ((idxString = target.getAttribute("__idx")).length() == 0)) {
+ target = target.getParentElement();
+ }
+ if (idxString.length() > 0) {
+ int idx = Integer.parseInt(idxString);
+ T value = impl.getValue(idx);
+ cell.onBrowserEvent(target, value, null, event, valueUpdater);
+ if (!cell.consumesEvents() && type == Event.ONMOUSEDOWN) {
+ SelectionModel<? super T> selectionModel = impl.getSelectionModel();
+ if (selectionModel != null) {
+ selectionModel.setSelected(value, true);
+ }
}
}
}
public void setData(int start, int length, List<T> values) {
- // Construct a run of element from start (inclusive) to start + length (exclusive)
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < length; i++) {
- sb.append("<div __idx='" + (start + i) + "' __seq='" + seq++ + "'>");
- cell.render(values.get(i), null, sb);
- sb.append("</div>");
- }
-
- Element parent = getElement().getFirstChildElement();
- if (start == 0 && length == maxSize) {
- parent.setInnerHTML(sb.toString());
- } else {
- makeElements();
- tmpElem.setInnerHTML(sb.toString());
- for (int i = 0; i < length; i++) {
- Element child = parent.getChild(start + i).cast();
- parent.replaceChild(tmpElem.getChild(0), child);
- }
- }
+ impl.setData(values, start);
}
public void setDataSize(int size, boolean isExact) {
- this.size = size;
- sizeChanged();
+ impl.setDataSize(size);
}
public void setDelegate(Delegate<T> delegate) {
- this.delegate = delegate;
+ impl.setDelegate(delegate);
}
+ public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
+ impl.setSelectionModel(selectionModel);
+ }
+
+ /**
+ * Set the value updater to use when cells modify items.
+ *
+ * @param valueUpdater the {@link ValueUpdater}
+ */
public void setValueUpdater(ValueUpdater<T, Void> valueUpdater) {
this.valueUpdater = valueUpdater;
}
-
- private void makeElements() {
- Element parent = getElement().getFirstChildElement();
- int childCount = parent.getChildCount();
-
- int actualSize = Math.min(size, maxSize);
- if (actualSize > childCount) {
- // Create new elements with a "loading..." message
- StringBuilder sb = new StringBuilder();
- int newElements = actualSize - childCount;
- for (int i = 0; i < newElements; i++) {
- sb.append("<div __idx='" + (childCount + i) + "'><i>loading...</i></div>");
- }
-
- if (childCount == 0) {
- parent.setInnerHTML(sb.toString());
- } else {
- tmpElem.setInnerHTML(sb.toString());
- for (int i = 0; i < newElements; i++) {
- parent.appendChild(tmpElem.getChild(0));
- }
- }
- } else if (actualSize < childCount) {
- // Remove excess elements
- while (actualSize < childCount) {
- parent.getChild(--childCount).removeFromParent();
- }
- }
- }
-
- private void showOrHide(Element element, boolean show) {
- if (show) {
- element.getStyle().clearDisplay();
- } else {
- element.getStyle().setDisplay(Display.NONE);
- }
- }
-
- private void sizeChanged() {
- showOrHide(showMoreElem, size > maxSize);
- showOrHide(showFewerElem, maxSize > initialMaxSize);
- }
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/SimpleCellListImpl.java b/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/SimpleCellListImpl.java
new file mode 100644
index 0000000..726aecc
--- /dev/null
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/client/impl/SimpleCellListImpl.java
@@ -0,0 +1,340 @@
+/*
+ * 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.list.client.impl;
+
+import com.google.gwt.bikeshed.cells.client.Cell;
+import com.google.gwt.bikeshed.list.client.ListView;
+import com.google.gwt.bikeshed.list.client.ListView.Delegate;
+import com.google.gwt.bikeshed.list.shared.Range;
+import com.google.gwt.bikeshed.list.shared.SelectionModel;
+import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter.DefaultRange;
+import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
+import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeHandler;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.event.shared.HandlerRegistration;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Implementation of {@link SimpleCellListImpl}. This class is subject to change
+ * or deletion. Do not rely on this class.
+ *
+ * @param <T> the data type of items in the list
+ */
+public abstract class SimpleCellListImpl<T> {
+
+ private final Cell<T, Void> cell;
+ private final Element childContainer;
+ private final List<T> data = new ArrayList<T>();
+ private final Set<Object> selectedKeys = new HashSet<Object>();
+ private Delegate<T> delegate;
+ private final Element emptyMessageElem;
+ private final int increment;
+ private final int initialMaxSize;
+ private final ListView<T> listView;
+ private int maxSize;
+ private HandlerRegistration selectionHandler;
+ private SelectionModel<? super T> selectionModel;
+ private final Element showFewerElem;
+ private final Element showMoreElem;
+ private int size;
+ private final Element tmpElem;
+
+ public SimpleCellListImpl(ListView<T> listView, Cell<T, Void> cell,
+ int maxSize, int increment, Element childContainer,
+ Element emptyMessageElem, Element showMoreElem, Element showFewerElem) {
+ this.cell = cell;
+ this.childContainer = childContainer;
+ this.emptyMessageElem = emptyMessageElem;
+ this.increment = increment;
+ this.initialMaxSize = maxSize;
+ this.listView = listView;
+ this.maxSize = maxSize;
+ this.showFewerElem = showFewerElem;
+ this.showMoreElem = showMoreElem;
+ tmpElem = Document.get().createDivElement();
+
+ showOrHide(showMoreElem, false);
+ showOrHide(showFewerElem, false);
+ showOrHide(emptyMessageElem, false);
+ }
+
+ public Range getRange() {
+ return new DefaultRange(0, maxSize);
+ }
+
+ public SelectionModel<? super T> getSelectionModel() {
+ return selectionModel;
+ }
+
+ public Element getShowFewerElem() {
+ return showFewerElem;
+ }
+
+ public Element getShowMoreElem() {
+ return showMoreElem;
+ }
+
+ public T getValue(int i) {
+ return data.get(i);
+ }
+
+ /**
+ * Set the data in the list.
+ *
+ * @param values the new data
+ * @param start the start index
+ */
+ public void setData(List<T> values, int start) {
+ int oldSize = size;
+ int len = values.size();
+ int end = start + len;
+
+ // The size must be at least as large as the data.
+ if (end > oldSize) {
+ size = end;
+ sizeChanged();
+ }
+
+ // Create placeholders up to the specified index.
+ while (data.size() < start) {
+ data.add(null);
+ }
+
+ // Insert the new values into the data array.
+ for (int i = start; i < end; i++) {
+ T value = values.get(i - start);
+ if (i < data.size()) {
+ data.set(i, value);
+ } else {
+ data.add(value);
+
+ // Update our local cache of selected values. We only need to consider
+ // new values at this point. If any existing value changes its selection
+ // state, we'll find out from the selection model.
+ if (selectionModel != null && selectionModel.isSelected(value)) {
+ selectedKeys.add(getKey(value));
+ }
+ }
+ }
+
+ // Construct a run of element from start (inclusive) to start + len
+ // (exclusive)
+ StringBuilder sb = new StringBuilder();
+ emitHtml(sb, values, start, cell, selectionModel);
+
+ // Replace the DOM elements with the new rendered cells.
+ if (oldSize == 0 || (start == 0 && len >= oldSize)) {
+ childContainer.setInnerHTML(sb.toString());
+ } else {
+ makeElements();
+ tmpElem.setInnerHTML(sb.toString());
+ Element toReplace = childContainer.getChild(start).cast();
+ for (int i = start; i < end; i++) {
+ // The child will be removed from tmpElem, so always use index 0.
+ Element nextSibling = toReplace.getNextSiblingElement();
+ childContainer.replaceChild(tmpElem.getChild(0), toReplace);
+ toReplace = nextSibling;
+ }
+ }
+ }
+
+ /**
+ * Set the overall size of the list.
+ *
+ * @param size the overall size
+ */
+ public void setDataSize(int size) {
+ this.size = size;
+ int toRemove = data.size() - size;
+ for (int i = 0; i < toRemove; i++) {
+ removeLastItem();
+ }
+ sizeChanged();
+ }
+
+ public void setDelegate(Delegate<T> delegate) {
+ this.delegate = delegate;
+ }
+
+ public void setSelectionModel(final SelectionModel<? super T> selectionModel) {
+ // Remove the old selection model.
+ if (selectionHandler != null) {
+ selectionHandler.removeHandler();
+ selectionHandler = null;
+ }
+
+ // Set the new selection model.
+ this.selectionModel = selectionModel;
+ if (selectionModel != null) {
+ selectionHandler = selectionModel.addSelectionChangeHandler(new SelectionChangeHandler() {
+ public void onSelectionChange(SelectionChangeEvent event) {
+ // Determine if our selection states are stale.
+ boolean dependsOnSelection = cell.dependsOnSelection();
+ boolean refreshRequired = false;
+ Element cellElem = childContainer.getFirstChildElement();
+ for (T value : data) {
+ boolean selected = selectionModel.isSelected(value);
+ Object key = getKey(value);
+ if (selected != selectedKeys.contains(key)) {
+ refreshRequired = true;
+ if (selected) {
+ selectedKeys.add(key);
+ } else {
+ selectedKeys.remove(key);
+ }
+ if (!dependsOnSelection) {
+ // The cell doesn't depend on Selection, so we only need to
+ // update the style.
+ setSelected(cellElem, selected);
+ }
+ }
+ cellElem = cellElem.getNextSiblingElement();
+ }
+
+ // Refresh the entire list if needed.
+ if (refreshRequired && dependsOnSelection) {
+ setData(data, 0);
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * Show fewer items.
+ */
+ public void showFewer() {
+ this.maxSize = Math.max(initialMaxSize, maxSize - increment);
+ sizeChanged();
+ if (delegate != null) {
+ delegate.onRangeChanged(listView);
+ }
+ }
+
+ /**
+ * Show more items.
+ */
+ public void showMore() {
+ this.maxSize += increment;
+ sizeChanged();
+ if (delegate != null) {
+ delegate.onRangeChanged(listView);
+ }
+ }
+
+ /**
+ * Construct the HTML that represents the list of items.
+ *
+ * @param sb the {@link StringBuilder} to build into
+ * @param values the values to render
+ * @param start the start index
+ * @param cell the cell to use as a renderer
+ * @param selectionModel the {@link SelectionModel}
+ */
+ protected abstract void emitHtml(StringBuilder sb, List<T> values, int start,
+ Cell<T, Void> cell, SelectionModel<? super T> selectionModel);
+
+ /**
+ * Remove the last element from the list.
+ */
+ protected void removeLastItem() {
+ data.remove(data.size() - 1);
+ childContainer.getLastChild().removeFromParent();
+ }
+
+ /**
+ * Mark an element as selected or unselected. This is called when a cells
+ * selection state changes, but the cell does not depend on selection.
+ *
+ * @param elem the element to modify
+ * @param selected true if selected, false if not
+ */
+ protected abstract void setSelected(Element elem, boolean selected);
+
+ /**
+ * Get the key for a given item.
+ *
+ * @param value the item
+ * @return the key, or null if there is no selection model
+ */
+ private Object getKey(T value) {
+ return selectionModel == null ? null
+ : selectionModel.getKeyProvider().getKey(value);
+ }
+
+ /**
+ * Create placeholder elements that will be replaced with data. This is used s
+ * when replacing a subset of the list.
+ */
+ private void makeElements() {
+ int childCount = childContainer.getChildCount();
+ int actualSize = Math.min(data.size(), maxSize);
+ if (actualSize > childCount) {
+ // Create new elements with a "loading..." message
+ StringBuilder sb = new StringBuilder();
+ int newElements = actualSize - childCount;
+ for (int i = 0; i < newElements; i++) {
+ // TODO(jlabanca): Make this I18N friendly.
+ sb.append("<div __idx='" + (childCount + i)
+ + "'><i>loading...</i></div>");
+ }
+
+ if (childCount == 0) {
+ childContainer.setInnerHTML(sb.toString());
+ } else {
+ tmpElem.setInnerHTML(sb.toString());
+ for (int i = 0; i < newElements; i++) {
+ childContainer.appendChild(tmpElem.getChild(0));
+ }
+ }
+ } else if (actualSize < childCount) {
+ // Remove excess elements
+ while (actualSize < childCount) {
+ removeLastItem();
+ childCount--;
+ }
+ }
+ }
+
+ /**
+ * Show or hide an element.
+ *
+ * @param element the element
+ * @param show true to show, false to hide
+ */
+ private void showOrHide(Element element, boolean show) {
+ if (show) {
+ element.getStyle().clearDisplay();
+ } else {
+ element.getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ /**
+ * Called when the size of the list changes.
+ */
+ private void sizeChanged() {
+ showOrHide(showMoreElem, size > maxSize);
+ showOrHide(showFewerElem, maxSize > initialMaxSize);
+ showOrHide(emptyMessageElem, size == 0);
+ }
+}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MultiSelectionModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/MultiSelectionModel.java
similarity index 75%
rename from bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MultiSelectionModel.java
rename to bikeshed/src/com/google/gwt/bikeshed/list/shared/MultiSelectionModel.java
index 6ebe91f..9d6d776 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MultiSelectionModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/MultiSelectionModel.java
@@ -1,19 +1,19 @@
/*
* 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.sample.bikeshed.cookbook.client;
+package com.google.gwt.bikeshed.list.shared;
import com.google.gwt.bikeshed.list.shared.SelectionModel.AbstractSelectionModel;
@@ -22,26 +22,34 @@
/**
* A simple selection model that allows multiple objects to be selected.
- *
+ *
* @param <T> the record data type
*/
public class MultiSelectionModel<T> extends AbstractSelectionModel<T> {
private Set<T> selectedSet = new TreeSet<T>();
+ private Set<Object> selectedKeys = new TreeSet<Object>();
+ /**
+ * Get the set of selected items.
+ *
+ * @return the set of selected items
+ */
public Set<T> getSelectedSet() {
return selectedSet;
}
public boolean isSelected(T object) {
- return selectedSet.contains(object);
+ return selectedKeys.contains(getKeyProvider().getKey(object));
}
public void setSelected(T object, boolean selected) {
if (selected) {
selectedSet.add(object);
+ selectedKeys.add(getKeyProvider().getKey(object));
} else {
selectedSet.remove(object);
+ selectedKeys.remove(getKeyProvider().getKey(object));
}
scheduleSelectionChangeEvent();
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
index 3b3a0ee..e6ef41f 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/SelectionModel.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -25,7 +25,7 @@
/**
* A model for selection within a list.
- *
+ *
* @param <T> the data type of records in the list
*/
public interface SelectionModel<T> extends HasHandlers {
@@ -37,7 +37,7 @@
/**
* Called when {@link SelectionChangeEvent} is fired.
- *
+ *
* @param event the {@link SelectionChangeEvent} that was fired
*/
void onSelectionChange(SelectionChangeEvent event);
@@ -57,7 +57,7 @@
/**
* Fires a selection change event on all registered handlers in the handler
* manager. If no such handlers exist, this method will do nothing.
- *
+ *
* @param source the source of the handlers
*/
static void fire(SelectionModel<?> source) {
@@ -69,7 +69,7 @@
/**
* Gets the type associated with this event.
- *
+ *
* @return returns the handler type
*/
public static Type<SelectionChangeHandler> getType() {
@@ -99,7 +99,7 @@
/**
* A default implementation of SelectionModel that provides listener addition
* and removal.
- *
+ *
* @param <T> the data type of records in the list
*/
public abstract class AbstractSelectionModel<T> implements SelectionModel<T> {
@@ -127,7 +127,7 @@
*/
public ProvidesKey<T> getKeyProvider() {
if (keyProvider == null) {
- keyProvider = new ProvidesKey<T>() {
+ keyProvider = new ProvidesKey<T>() {
public Object getKey(T item) {
return item;
}
@@ -137,6 +137,15 @@
}
/**
+ * Set the key provider for items in this model.
+ *
+ * @param keyProvider the {@link ProvidesKey}
+ */
+ public void setKeyProvider(ProvidesKey<T> keyProvider) {
+ this.keyProvider = keyProvider;
+ }
+
+ /**
* Schedules a {@link SelectionModel.SelectionChangeEvent} to fire at the
* end of the current event loop.
*/
@@ -155,21 +164,21 @@
/**
* Adds a {@link SelectionChangeEvent} handler.
- *
+ *
* @param handler the handler
* @return the registration for the event
*/
HandlerRegistration addSelectionChangeHandler(SelectionChangeHandler handler);
/**
- * Returns a ProvidesKey instance that may be used to provide a unique
- * key for each record.
+ * Returns a ProvidesKey instance that may be used to provide a unique key for
+ * each record.
*/
ProvidesKey<T> getKeyProvider();
/**
* Check if an object is selected.
- *
+ *
* @param object the object
* @return true if selected, false if not
*/
@@ -177,7 +186,7 @@
/**
* Set the selected state of an object.
- *
+ *
* @param object the object to select or deselect
* @param selected true to select, false to deselect
*/
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SingleSelectionModel.java b/bikeshed/src/com/google/gwt/bikeshed/list/shared/SingleSelectionModel.java
similarity index 72%
rename from bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SingleSelectionModel.java
rename to bikeshed/src/com/google/gwt/bikeshed/list/shared/SingleSelectionModel.java
index 7fae547..293c4c3 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SingleSelectionModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/list/shared/SingleSelectionModel.java
@@ -1,30 +1,31 @@
/*
* 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.sample.bikeshed.cookbook.client;
+package com.google.gwt.bikeshed.list.shared;
import com.google.gwt.bikeshed.list.shared.SelectionModel.AbstractSelectionModel;
/**
* A simple selection model that allows only one object to be selected a a time.
- *
+ *
* @param <T> the record data type
*/
public final class SingleSelectionModel<T> extends AbstractSelectionModel<T> {
private T curSelection;
+ private Object curKey;
/**
* Gets the currently-selected object.
@@ -34,14 +35,22 @@
}
public boolean isSelected(T object) {
- return object.equals(curSelection);
+ if (curSelection == null || curKey == null || object == null) {
+ return false;
+ }
+ ProvidesKey<T> keyProvider = getKeyProvider();
+ Object newKey = keyProvider.getKey(object);
+ return curKey.equals(newKey);
}
public void setSelected(T object, boolean selected) {
+ Object key = getKeyProvider().getKey(object);
if (selected) {
curSelection = object;
- } else if (object.equals(curSelection)) {
+ curKey = key;
+ } else if (curKey != null && curKey.equals(key)) {
curSelection = null;
+ curKey = null;
}
scheduleSelectionChangeEvent();
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
deleted file mode 100644
index 36e2b4c..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeNodeView.java
+++ /dev/null
@@ -1,253 +0,0 @@
-/*
- * 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.list.client.HasCell;
-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.Display;
-import com.google.gwt.dom.client.Style.Position;
-import com.google.gwt.dom.client.Style.Unit;
-import com.google.gwt.user.client.ui.RequiresResize;
-
-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 int columnWidth;
-
- private final int imageLeft;
-
- private int level;
-
- private int maxColumns;
-
- 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
- * @param maxColumns the maximum number of columns to display
- */
- SideBySideTreeNodeView(final TreeView tree, final SideBySideTreeNodeView<?> parent,
- NodeInfo<T> parentNodeInfo, Element elem, T value, int level, String path,
- int columnWidth, int maxColumns) {
- super(tree, parent, parentNodeInfo, value);
- this.imageLeft = columnWidth - 16 - tree.getImageWidth();
- this.level = level;
- this.path = path;
- this.columnWidth = columnWidth;
- this.maxColumns = maxColumns;
-
- setElement(elem);
- }
-
- @Override
- protected <C> TreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo,
- Element childElem, C childValue, Void viewData, int idx) {
- return new SideBySideTreeNodeView<C>(getTree(), this, nodeInfo, childElem,
- childValue, level + 1, path + "-" + idx, columnWidth, maxColumns);
- }
-
- @Override
- protected <C> void emitHtml(StringBuilder sb, List<C> childValues,
- List<HasCell<C, ?, Void>> hasCells, List<TreeNodeView<?>> savedViews) {
- TreeView tree = getTree();
- TreeViewModel model = tree.getTreeViewModel();
- int imageWidth = tree.getImageWidth();
-
- int idx = 0;
- for (C childValue : childValues) {
- sb.append("<div id=\"" + path + "-" + idx +
- "\" class=\"gwt-sstree-unselectedItem gwt-sstree-" +
- ((idx % 2) == 0 ? "even" : "odd") + "Row\"" +
- " 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, this)) {
- sb.append(LEAF_IMAGE);
- } else {
- sb.append(tree.getClosedImageHtml(imageLeft));
- }
- sb.append("<div class=\"gwt-sstree-cell\">");
- for (int i = 0; i < hasCells.size(); i++) {
- sb.append("<span __idx='");
- sb.append(i);
- sb.append("'>");
- render(sb, childValue, hasCells.get(i));
- sb.append("</span>");
- }
- sb.append("</div></div>");
-
- idx++;
- }
- }
-
- /**
- * Ensure that the child container exists and return it.
- *
- * @return the child container
- */
- @Override
- protected Element ensureChildContainer() {
- if (getChildContainer() == 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);
- animFrame.setId("animFrame");
- setChildContainer(animFrame.appendChild(Document.get().createDivElement()));
- }
-
- // TODO(jgw): Kind of a hack. We should probably be propagating onResize()
- // down from the TreeView, but this is simpler for the moment.
- TreeView tree = getTree();
- if (tree instanceof RequiresResize) {
- ((RequiresResize) tree).onResize();
- }
-
- return getChildContainer();
- }
-
- /**
- * @return the element that contains the rendered cell
- */
- @Override
- protected Element getCellParent() {
- return getElement().getChild(1).cast();
- }
-
- @Override
- protected Element getContainer() {
- return getTree().getElement().getChild(level).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-sstree-selectedItem");
- } else {
- if (sibling.getState()) {
- sibling.setState(false);
- }
-
- container.setClassName("gwt-sstree-unselectedItem " +
- (((i % 2) == 0) ? "gwt-sstree-evenRow" : "gwt-sstree-oddRow"));
- }
- }
- }
- }
-
- /**
- * Returns the container for child nodes at the given level.
- */
- private Element createContainer(int level) {
- // Resize the root element
- Element rootElement = getTree().getElement();
- rootElement.getStyle().setWidth(Math.min(maxColumns, level + 1) * columnWidth, 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-sstree-column");
- Style style = div.getStyle();
- style.setPosition(Position.ABSOLUTE);
- style.setTop(0, Unit.PX);
- style.setWidth(columnWidth, Unit.PX);
-
- childCount++;
- }
-
- Element child = rootElement.getFirstChild().cast();
-
- int x = Math.min(0, maxColumns - (level + 1));
- while (child != null) {
- Style style = child.getStyle();
- style.setLeft(x * columnWidth, Unit.PX);
- if (x < 0) {
- style.setDisplay(Display.NONE);
- } else {
- style.clearDisplay();
- }
- child = child.getNextSibling().cast();
- x++;
- }
-
- 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 = getTree().getElement();
- rootElement.getStyle().setWidth((level + 1) * columnWidth, Unit.PX);
-
- // Create children of the root container as needed.
- int childCount = rootElement.getChildCount();
- while (childCount > level) {
- rootElement.removeChild(rootElement.getLastChild());
- childCount--;
- }
-
- setChildContainer(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
index 2f048db..500c007 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/SideBySideTreeView.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,143 +15,615 @@
*/
package com.google.gwt.bikeshed.tree.client;
-import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.animation.client.Animation;
+import com.google.gwt.bikeshed.cells.client.Cell;
+import com.google.gwt.bikeshed.cells.client.ValueUpdater;
+import com.google.gwt.bikeshed.list.client.ListView;
+import com.google.gwt.bikeshed.list.client.SimpleCellList;
+import com.google.gwt.bikeshed.list.shared.ProvidesKey;
+import com.google.gwt.bikeshed.list.shared.Range;
+import com.google.gwt.bikeshed.list.shared.SelectionModel;
+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.Node;
-import com.google.gwt.dom.client.NodeList;
-import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style.Overflow;
import com.google.gwt.dom.client.Style.Position;
import com.google.gwt.dom.client.Style.Unit;
+import com.google.gwt.dom.client.Style.Visibility;
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.ProvidesResize;
+import com.google.gwt.user.client.ui.RequiresResize;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.SplitLayoutPanel;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* A view of a tree.
*/
-public class SideBySideTreeView extends TreeView {
-
- protected int columnWidth;
-
- protected int maxColumns;
+public class SideBySideTreeView extends TreeView implements ProvidesResize,
+ RequiresResize, HasAnimation {
/**
- * Construct a new {@link TreeView} that will display as many columns as
- * needed.
- *
- * @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
- * @param columnWidth
+ * The element used in place of an image when a node has no children.
*/
- public <T> SideBySideTreeView(TreeViewModel viewModel, T rootValue,
- int columnWidth) {
- this(viewModel, rootValue, columnWidth, Integer.MAX_VALUE);
+ private static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>";
+
+ /**
+ * The style name assigned to each column.
+ */
+ private static final String STYLENAME_COLUMN = "gwt-sstree-column";
+
+ /**
+ * The style name assigned to each column.
+ */
+ private static final String STYLENAME_OPEN = "gwt-sstree-openItem";
+
+ /**
+ * The prefix of the ID assigned to open cells.
+ */
+ private static final String ID_PREFIX_OPEN = "__gwt-sstree-open-";
+
+ /**
+ * The animation used to scroll to the newly added list view.
+ */
+ private class ScrollAnimation extends Animation {
+
+ /**
+ * The starting scroll position.
+ */
+ private int startScrollLeft;
+
+ /**
+ * The ending scroll position.
+ */
+ private int targetScrollLeft;
+
+ @Override
+ protected void onComplete() {
+ getElement().setScrollLeft(targetScrollLeft);
+ }
+
+ @Override
+ protected void onUpdate(double progress) {
+ int diff = targetScrollLeft - startScrollLeft;
+ getElement().setScrollLeft(startScrollLeft + (int) (diff * progress));
+ }
+
+ void scrollToEnd() {
+ Element elem = getElement();
+ targetScrollLeft = elem.getScrollWidth() - elem.getClientWidth();
+
+ if (isAnimationEnabled()) {
+ // Animate the scrolling.
+ startScrollLeft = elem.getScrollLeft();
+ run(250);
+ } else {
+ // Scroll instantly.
+ onComplete();
+ }
+ }
}
/**
+ * A wrapper around a cell that adds an open button.
+ *
+ * @param <C> the data type of the cell
+ */
+ private class CellDecorator<C> extends Cell<C, Void> {
+
+ /**
+ * The cell used to render the inner contents.
+ */
+ private final Cell<C, Void> cell;
+
+ /**
+ * The level of this list view.
+ */
+ private final int level;
+
+ /**
+ * The key of the currently open item.
+ */
+ private Object openKey;
+
+ /**
+ * The key provider for the node.
+ */
+ private final ProvidesKey<C> providesKey;
+
+ /**
+ * Construct a new {@link CellDecorator}.
+ *
+ * @param nodeInfo the {@link NodeInfo} associated with the cell
+ * @param level the level of items rendered by this decorator
+ */
+ public CellDecorator(NodeInfo<C> nodeInfo, int level) {
+ this.cell = nodeInfo.getCell();
+ this.level = level;
+ this.providesKey = nodeInfo.getProvidesKey();
+ }
+
+ @Override
+ public boolean consumesEvents() {
+ return cell.consumesEvents();
+ }
+
+ @Override
+ public boolean dependsOnSelection() {
+ return cell.dependsOnSelection();
+ }
+
+ public Object getOpenKey() {
+ return openKey;
+ }
+
+ @Override
+ public Void onBrowserEvent(Element parent, C value, Void viewData,
+ NativeEvent event, ValueUpdater<C, Void> valueUpdater) {
+ Element target = event.getEventTarget().cast();
+ if (getImageElement(parent).isOrHasChild(target)) {
+ if (Event.getTypeInt(event.getType()) == Event.ONMOUSEDOWN) {
+ trimToLevel(level);
+
+ // Remove style from currently open item.
+ Element curOpenItem = Document.get().getElementById(getOpenId());
+ if (curOpenItem != null) {
+ replaceImageElement(curOpenItem.getParentElement(), false);
+ }
+
+ // Save the key of the new open item and update the Element.
+ openKey = providesKey.getKey(value);
+ replaceImageElement(parent, true);
+
+ // Add a tree node for the next level.
+ appendTreeNode(getTreeViewModel().getNodeInfo(value));
+ }
+ return viewData;
+ } else {
+ return cell.onBrowserEvent(getCellParent(parent), value, viewData,
+ event, valueUpdater);
+ }
+ }
+
+ @Override
+ public void render(C value, Void viewData, StringBuilder sb) {
+ boolean isOpen = (openKey == null) ? false
+ : openKey.equals(providesKey.getKey(value));
+ int imageWidth = getImageWidth();
+ sb.append("<div style='position:relative;padding-right:");
+ sb.append(imageWidth);
+ sb.append("px;'");
+ if (isOpen) {
+ sb.append(" class='").append(STYLENAME_OPEN).append("'");
+ sb.append(" id='").append(getOpenId()).append("'");
+ }
+ sb.append(">");
+ if (isOpen) {
+ sb.append(getOpenImageHtml());
+ } else if (getTreeViewModel().isLeaf(value)) {
+ sb.append(LEAF_IMAGE);
+ } else {
+ sb.append(getClosedImageHtml());
+ }
+ sb.append("<div>");
+ cell.render(value, viewData, sb);
+ sb.append("</div></div>");
+ }
+
+ @Override
+ public void setValue(Element parent, C value, Void viewData) {
+ cell.setValue(getCellParent(parent), value, viewData);
+ }
+
+ /**
+ * Get the parent element of the decorated cell.
+ *
+ * @param parent the parent of this cell
+ * @return the decorated cell's parent
+ */
+ private Element getCellParent(Element parent) {
+ return parent.getFirstChildElement().getChild(1).cast();
+ }
+
+ /**
+ * Get the image element of the decorated cell.
+ *
+ * @param parent the parent of this cell
+ * @return the image element
+ */
+ private Element getImageElement(Element parent) {
+ return parent.getFirstChildElement().getFirstChildElement();
+ }
+
+ /**
+ * Get the ID of the open element.
+ *
+ * @return the ID
+ */
+ private String getOpenId() {
+ return ID_PREFIX_OPEN + level + "-" + uniqueId;
+ }
+
+ /**
+ * Replace the image element of a cell.
+ *
+ * @param parent the parent element of the cell
+ * @param open true if open, false if closed
+ */
+ private void replaceImageElement(Element parent, boolean open) {
+ // Update the style name and ID.
+ Element wrapper = parent.getFirstChildElement();
+ if (open) {
+ wrapper.addClassName(STYLENAME_OPEN);
+ wrapper.setId(getOpenId());
+ } else {
+ wrapper.removeClassName(STYLENAME_OPEN);
+ wrapper.setId("");
+ }
+
+ // Replace the image element.
+ String html = open ? getOpenImageHtml() : getClosedImageHtml();
+ Element tmp = Document.get().createDivElement();
+ tmp.setInnerHTML(html);
+ Element imageElem = tmp.getFirstChildElement();
+ Element oldImg = getImageElement(parent);
+ wrapper.replaceChild(imageElem, oldImg);
+ }
+ }
+
+ /**
+ * A node in the tree.
+ *
+ * @param <C> the data type of the children of the node
+ */
+ private class TreeNode<C> {
+ private CellDecorator<C> cell;
+ private ListView<C> listView;
+ private NodeInfo<C> nodeInfo;
+ private Widget widget;
+
+ /**
+ * Construct a new {@link TreeNode}.
+ *
+ * @param nodeInfo the nodeInfo for the children nodes
+ * @param listView the list view assocated with the node
+ * @param widget the widget that represents the list view
+ */
+ public TreeNode(NodeInfo<C> nodeInfo, ListView<C> listView,
+ CellDecorator<C> cell, Widget widget) {
+ this.cell = cell;
+ this.listView = listView;
+ this.nodeInfo = nodeInfo;
+ this.widget = widget;
+ }
+
+ /**
+ * Get the {@link CellDecorator} used to render the node.
+ *
+ * @return the cell decorator
+ */
+ public CellDecorator<C> getCell() {
+ return cell;
+ }
+
+ /**
+ * Get the widget that represents this {@link TreeNode}.
+ *
+ * @return the widget
+ */
+ public Widget getWidget() {
+ return widget;
+ }
+
+ /**
+ * Unregister the list view and remove it from the widget.
+ */
+ void cleanup() {
+ listView.setSelectionModel(null);
+ nodeInfo.unsetView();
+ getSplitLayoutPanel().remove(widget);
+ }
+ }
+
+ /**
+ * The counter used to assigned unique IDs.
+ */
+ private static int NEXT_ID = 0;
+
+ /**
+ * The animation used for scrolling.
+ */
+ private final ScrollAnimation animation = new ScrollAnimation();
+
+ /**
+ * The default width of new columns.
+ */
+ private int defaultWidth = 200;
+
+ /**
+ * The HTML used to generate the closed image.
+ */
+ private String closedImageHtml;
+
+ /**
+ * The unique ID assigned to this tree view.
+ */
+ private final int uniqueId = NEXT_ID++;
+
+ /**
+ * A boolean indicating whether or not animations are enabled.
+ */
+ private boolean isAnimationEnabled;
+
+ /**
+ * The minimum width of new columns.
+ */
+ private int minWidth = getImageWidth() + 20;
+
+ /**
+ * The HTML used to generate the open image.
+ */
+ private String openImageHtml;
+
+ /**
+ * The element used to maintain the scrollbar when columns are removed.
+ */
+ private Element scrollLock;
+
+ /**
+ * The visible {@link TreeNode}.
+ */
+ private List<TreeNode<?>> treeNodes = new ArrayList<TreeNode<?>>();
+
+ /**
* 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
- * @param columnWidth the width of each column
- * @param maxColumns the maximum number of columns to display horizontally
*/
- public <T> SideBySideTreeView(TreeViewModel viewModel, T rootValue,
- int columnWidth, int maxColumns) {
- super(viewModel);
+ public <T> SideBySideTreeView(TreeViewModel viewModel, T rootValue) {
+ super(viewModel, new SplitLayoutPanel());
+ getElement().getStyle().setOverflow(Overflow.AUTO);
+ setStyleName("gwt-SideBySideTreeView");
- this.columnWidth = columnWidth;
- this.maxColumns = maxColumns;
+ // Add a placeholder to maintain the scroll width.
+ scrollLock = Document.get().createDivElement();
+ scrollLock.getStyle().setPosition(Position.ABSOLUTE);
+ scrollLock.getStyle().setVisibility(Visibility.HIDDEN);
+ scrollLock.getStyle().setZIndex(-32767);
+ scrollLock.getStyle().setBackgroundColor("red");
+ scrollLock.getStyle().setTop(0, Unit.PX);
+ scrollLock.getStyle().setLeft(0, Unit.PX);
+ scrollLock.getStyle().setHeight(1, Unit.PX);
+ scrollLock.getStyle().setWidth(1, Unit.PX);
+ getElement().appendChild(scrollLock);
- Element rootElement = Document.get().createDivElement();
- rootElement.setClassName("gwt-sstree");
- Style style = rootElement.getStyle();
- style.setPosition(Position.RELATIVE);
- style.setWidth(columnWidth, Unit.PX);
- setElement(rootElement);
+ // Associate the first ListView with the rootValue.
+ appendTreeNode(viewModel.getNodeInfo(rootValue));
- // Add event handlers.
- sinkEvents(Event.ONCLICK | Event.ONMOUSEDOWN | Event.ONMOUSEUP | Event.ONCHANGE);
+ // Catch scroll events.
+ sinkEvents(Event.ONSCROLL);
+ }
- // Associate a view with the item.
- TreeNodeView<T> root = new SideBySideTreeNodeView<T>(this, null, null,
- rootElement, rootValue, 0, "gwt-sstree", columnWidth, maxColumns);
- setRootNode(root);
- root.setState(true);
+ /**
+ * Get the default width of new columns.
+ *
+ * @return the default width in pixels
+ */
+ public int getDefaultColumnWidth() {
+ return defaultWidth;
+ }
+
+ /**
+ * Get the minimum width of columns.
+ *
+ * @return the minimum width in pixels
+ */
+ public int getMinimumColumnWidth() {
+ return minWidth;
+ }
+
+ public boolean isAnimationEnabled() {
+ return isAnimationEnabled;
}
@Override
public void onBrowserEvent(Event event) {
- super.onBrowserEvent(event);
-
- int eventType = DOM.eventGetType(event);
- switch (eventType) {
- case Event.ONMOUSEUP: case Event.ONCHANGE:
- 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 = getRootTreeNodeView();
- for (String s : path) {
- nodeView = nodeView.getChildTreeNodeView(Integer.parseInt(s));
- }
- if (inCell) {
- nodeView.fireEventToCell(event);
-
- // TODO(jgw): Kind of a hacky way to set selection. Need to generalize
- // this to some sort of keyboard/mouse->selection controller.
- if (getSelectionModel() != null) {
- String type = event.getType();
- if ("mouseup".equals(type)) {
- getSelectionModel().setSelected(nodeView.getValue(), true);
- }
- }
- } else {
- nodeView.setState(!nodeView.getState());
- }
- }
- }
+ switch (DOM.eventGetType(event)) {
+ case Event.ONSCROLL:
+ // Shorten the scroll bar is possible.
+ adjustScrollLock();
break;
}
+ super.onBrowserEvent(event);
}
public void onResize() {
- if (!isAttached()) {
- return;
- }
+ getSplitLayoutPanel().onResize();
+ }
- int height = getElement().getOffsetHeight();
- NodeList<Node> children = getElement().getChildNodes();
- for (int i = 0; i < children.getLength(); ++i) {
- Element child = children.getItem(i).cast();
- child.getStyle().setHeight(height, Unit.PX);
+ public void setAnimationEnabled(boolean enable) {
+ this.isAnimationEnabled = enable;
+ }
+
+ /**
+ * Set the default width of new columns.
+ *
+ * @param width the default width in pixels
+ */
+ public void setDefaultColumnWidth(int width) {
+ this.defaultWidth = width;
+ }
+
+ /**
+ * Set the minimum width of columns.
+ *
+ * @param minWidth the minimum width in pixels
+ */
+ public void setMinimumColumnWidth(int minWidth) {
+ this.minWidth = minWidth;
+ }
+
+ /**
+ * Create a {@link ListView} that will display items. The {@link ListView}
+ * must extend {@link com.google.gwt.user.client.ui.Widget}.
+ *
+ * @param <C> the item type in the list view
+ * @param nodeInfo the node info with child data
+ * @param cell the cell to use in the list view
+ * @return the {@link ListView}
+ */
+ protected <C> ListView<C> createListView(NodeInfo<C> nodeInfo,
+ Cell<C, Void> cell) {
+ SimpleCellList<C> listView = new SimpleCellList<C>(cell, 100, 100);
+ listView.setValueUpdater(nodeInfo.getValueUpdater());
+ return listView;
+ }
+
+ /**
+ * Adjust the size of the scroll lock element based on the new position of the
+ * scroll bar.
+ */
+ private void adjustScrollLock() {
+ int scrollLeft = getElement().getScrollLeft();
+ if (scrollLeft > 0) {
+ int clientWidth = getElement().getClientWidth();
+ scrollLock.getStyle().setWidth(scrollLeft + clientWidth, Unit.PX);
+ } else {
+ scrollLock.getStyle().setWidth(1.0, Unit.PX);
}
}
- @Override
- protected void onLoad() {
- // TODO(jgw): This is a total hack.
- Scheduler.get().scheduleFinally(new Scheduler.ScheduledCommand() {
- public void execute() {
- onResize();
+ /**
+ * Create a new {@link TreeNode} and append it to the end of the LayoutPanel.
+ *
+ * @param <C> the data type of the children
+ * @param nodeInfo the info about the node
+ */
+ private <C> void appendTreeNode(final NodeInfo<C> nodeInfo) {
+ // Create the list view and its scrollable container.
+ final int level = treeNodes.size();
+ CellDecorator<C> cell = new CellDecorator<C>(nodeInfo, level);
+ final ListView<C> listView = createListView(nodeInfo, cell);
+ ScrollPanel scrollable = new ScrollPanel((Widget) listView);
+ scrollable.setStyleName(STYLENAME_COLUMN);
+
+ // Create a delegate list view so we can trap data changes.
+ ListView<C> listViewDelegate = new ListView<C>() {
+ public Range getRange() {
+ return listView.getRange();
}
- });
+
+ public void setData(int start, int length, List<C> values) {
+ // Trim to the current level if the open node no longer exists.
+ TreeNode<?> node = treeNodes.get(level);
+ Object openKey = node.getCell().openKey;
+ if (openKey != null) {
+ boolean stillExists = false;
+ ProvidesKey<C> keyProvider = nodeInfo.getProvidesKey();
+ for (C value : values) {
+ if (openKey.equals(keyProvider.getKey(value))) {
+ stillExists = true;
+ break;
+ }
+ }
+ if (!stillExists) {
+ trimToLevel(level);
+ }
+ }
+
+ // Refresh the list.
+ listView.setData(start, length, values);
+ }
+
+ public void setDataSize(int size, boolean isExact) {
+ listView.setDataSize(size, isExact);
+ }
+
+ public void setDelegate(Delegate<C> delegate) {
+ listView.setDelegate(delegate);
+ }
+
+ public void setSelectionModel(SelectionModel<? super C> selectionModel) {
+ listView.setSelectionModel(selectionModel);
+ }
+ };
+
+ // Create a TreeNode.
+ TreeNode<C> treeNode = new TreeNode<C>(nodeInfo, listViewDelegate, cell,
+ scrollable);
+ treeNodes.add(treeNode);
+
+ // Attach the view to the selection model and node info.
+ listView.setSelectionModel(nodeInfo.getSelectionModel());
+ nodeInfo.setView(listViewDelegate);
+
+ // Add the ListView to the LayoutPanel.
+ SplitLayoutPanel splitPanel = getSplitLayoutPanel();
+ splitPanel.insertWest(scrollable, defaultWidth, null);
+ splitPanel.setWidgetMinSize(scrollable, minWidth);
+ splitPanel.forceLayout();
+
+ // Scroll to the right.
+ animation.scrollToEnd();
+ }
+
+ /**
+ * @return the HTML to render the closed image.
+ */
+ private String getClosedImageHtml() {
+ if (closedImageHtml == null) {
+ AbstractImagePrototype proto = AbstractImagePrototype.create(getResources().treeClosed());
+ closedImageHtml = proto.getHTML().replace("style='",
+ "style='position:absolute;right:0px;top:0px;");
+ }
+ return closedImageHtml;
+ }
+
+ /**
+ * @return the HTML to render the open image.
+ */
+ private String getOpenImageHtml() {
+ if (openImageHtml == null) {
+ AbstractImagePrototype proto = AbstractImagePrototype.create(getResources().treeOpen());
+ openImageHtml = proto.getHTML().replace("style='",
+ "style='position:absolute;right:0px;top:0px;");
+ }
+ return openImageHtml;
+ }
+
+ /**
+ * Get the {@link SplitLayoutPanel} used to lay out the views.
+ *
+ * @return the {@link SplitLayoutPanel}
+ */
+ private SplitLayoutPanel getSplitLayoutPanel() {
+ return (SplitLayoutPanel) getWidget();
+ }
+
+ /**
+ * Reduce the number of {@link ListView} down to the specified level.
+ *
+ * @param level the level to trim to
+ */
+ private void trimToLevel(int level) {
+ // Add a placeholder to maintain the same scroll width.
+ adjustScrollLock();
+
+ // Remove the listViews that are no longer needed.
+ int curLevel = treeNodes.size() - 1;
+ while (curLevel > level) {
+ TreeNode<?> removed = treeNodes.remove(curLevel);
+ removed.cleanup();
+ curLevel--;
+ }
}
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java
index 73d662b..46399e6 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeNodeView.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,122 +15,616 @@
*/
package com.google.gwt.bikeshed.tree.client;
-import com.google.gwt.bikeshed.list.client.HasCell;
+import com.google.gwt.bikeshed.cells.client.Cell;
+import com.google.gwt.bikeshed.list.client.ListView;
+import com.google.gwt.bikeshed.list.client.impl.SimpleCellListImpl;
+import com.google.gwt.bikeshed.list.shared.ProvidesKey;
+import com.google.gwt.bikeshed.list.shared.Range;
import com.google.gwt.bikeshed.list.shared.SelectionModel;
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.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.ui.UIObject;
+import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* A view of a tree node.
- *
- * @param <T> the type that this {@link TreeNodeView} contains
+ *
+ * @param <T> the type that this view contains
*/
-public class StandardTreeNodeView<T> extends TreeNodeView<T> {
+class StandardTreeNodeView<T> extends UIObject {
/**
- * Construct a {@link TreeNodeView}.
- *
- * @param tree the parent {@link TreeView}
- * @param parent the parent {@link TreeNodeView}
+ * 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>";
+
+ /**
+ * Style name applied to selected rows.
+ */
+ private static final String STYLENNAME_SELECTED = "gwt-tree-selectedItem";
+
+ /**
+ * Returns the element that parents the cell contents of the node.
+ *
+ * @param nodeElem the element that represents the node
+ * @return the cell parent within the node
+ */
+ private static Element getCellParent(Element nodeElem) {
+ return nodeElem.getChild(1).cast();
+ }
+
+ /**
+ * Show or hide an element.
+ *
+ * @param element the element to show or hide
+ * @param show true to show, false to hide
+ */
+ private static void showOrHide(Element element, boolean show) {
+ if (show) {
+ element.getStyle().clearDisplay();
+ } else {
+ element.getStyle().setDisplay(Display.NONE);
+ }
+ }
+
+ /**
+ * The {@link ListView} used to show children.
+ *
+ * @param <C> the child item type
+ */
+ private static class NodeListView<C> implements ListView<C> {
+
+ private final SimpleCellListImpl<C> impl;
+ private StandardTreeNodeView<?> nodeView;
+ private Map<Object, StandardTreeNodeView<?>> savedViews;
+
+ public NodeListView(final NodeInfo<C> nodeInfo,
+ final StandardTreeNodeView<?> nodeView) {
+ this.nodeView = nodeView;
+
+ impl = new SimpleCellListImpl<C>(this, nodeInfo.getCell(), 100, 50,
+ nodeView.ensureChildContainer(), nodeView.emptyMessageElem,
+ nodeView.showMoreElem, nodeView.showFewerElem) {
+
+ @Override
+ public void setData(List<C> values, int start) {
+ // Ensure that we have a children array.
+ if (nodeView.children == null) {
+ nodeView.children = new ArrayList<StandardTreeNodeView<?>>();
+ }
+
+ // Construct a map of former child views based on their value keys.
+ int len = values.size();
+ int end = start + len;
+ int childCount = nodeView.getChildCount();
+ Map<Object, StandardTreeNodeView<?>> openNodes = new HashMap<Object, StandardTreeNodeView<?>>();
+ for (int i = start; i < end && i < childCount; i++) {
+ StandardTreeNodeView<?> child = nodeView.getChildNode(i);
+ // Ignore child nodes that are closed.
+ if (child.isOpen()) {
+ openNodes.put(child.getValueKey(), child);
+ }
+ }
+
+ // Hide the child container so we can animate it.
+ if (nodeView.tree.isAnimationEnabled()) {
+ nodeView.ensureAnimationFrame().getStyle().setDisplay(Display.NONE);
+ }
+
+ // Trim the saved views down to the children that still exists.
+ ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
+ savedViews = new HashMap<Object, StandardTreeNodeView<?>>();
+ for (C childValue : values) {
+ // Remove any child elements that correspond to prior children
+ // so the call to setInnerHtml will not destroy them
+ Object key = providesKey.getKey(childValue);
+ StandardTreeNodeView<?> savedView = openNodes.remove(key);
+ if (savedView != null) {
+ savedView.ensureAnimationFrame().removeFromParent();
+ savedViews.put(key, savedView);
+ }
+ }
+
+ // Create the new cells.
+ super.setData(values, start);
+
+ // Create the child TreeNodeViews from the new elements.
+ Element childElem = nodeView.ensureChildContainer().getFirstChildElement();
+ for (int i = start; i < end; i++) {
+ C childValue = values.get(i - start);
+ StandardTreeNodeView<C> child = nodeView.createTreeNodeView(
+ nodeInfo, childElem, childValue, null);
+ StandardTreeNodeView<?> savedChild = savedViews.remove(providesKey.getKey(childValue));
+ // Copy the saved child's state into the new child
+ if (savedChild != null) {
+ child.animationFrame = savedChild.animationFrame;
+ child.contentContainer = savedChild.contentContainer;
+ child.childContainer = savedChild.childContainer;
+ child.children = savedChild.children;
+ child.emptyMessageElem = savedChild.emptyMessageElem;
+ child.nodeInfo = savedChild.nodeInfo;
+ child.nodeInfoLoaded = savedChild.nodeInfoLoaded;
+ child.open = savedChild.open;
+ child.showFewerElem = savedChild.showFewerElem;
+ child.showMoreElem = savedChild.showMoreElem;
+
+ // Swap the node view in the child. We reuse the same NodeListView
+ // so that we don't have to unset and register a new view with the
+ // NodeInfo.
+ savedChild.listView.setNodeView(child);
+
+ // Copy the child container element to the new child
+ child.getElement().appendChild(savedChild.ensureAnimationFrame());
+ }
+
+ if (childCount > i) {
+ if (savedChild == null) {
+ // Cleanup the child node if we aren't going to reuse it.
+ nodeView.children.get(i).cleanup();
+ }
+ nodeView.children.set(i, child);
+ } else {
+ nodeView.children.add(child);
+ }
+ childElem = childElem.getNextSiblingElement();
+ }
+
+ // Clear temporary state.
+ savedViews = null;
+
+ // Animate the child container open.
+ if (nodeView.tree.isAnimationEnabled()) {
+ nodeView.tree.maybeAnimateTreeNode(nodeView);
+ }
+ }
+
+ @Override
+ protected void emitHtml(StringBuilder sb, List<C> values, int start,
+ Cell<C, Void> cell, SelectionModel<? super C> selectionModel) {
+ ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
+ TreeViewModel model = nodeView.tree.getTreeViewModel();
+ int imageWidth = nodeView.tree.getImageWidth();
+ for (C value : values) {
+ Object key = providesKey.getKey(value);
+ sb.append("<div style=\"position:relative;padding-left:");
+ sb.append(imageWidth);
+ sb.append("px;\">");
+ if (savedViews.get(key) != null) {
+ sb.append(nodeView.tree.getOpenImageHtml());
+ } else if (model.isLeaf(value)) {
+ sb.append(LEAF_IMAGE);
+ } else {
+ sb.append(nodeView.tree.getClosedImageHtml());
+ }
+ if (selectionModel != null && selectionModel.isSelected(value)) {
+ sb.append("<div class='").append(STYLENNAME_SELECTED).append("'>");
+ } else {
+ sb.append("<div>");
+ }
+ cell.render(value, null, sb);
+ sb.append("</div></div>");
+ }
+ }
+
+ @Override
+ protected void removeLastItem() {
+ StandardTreeNodeView<?> child = nodeView.children.remove(nodeView.children.size() - 1);
+ child.cleanup();
+ super.removeLastItem();
+ }
+
+ @Override
+ protected void setSelected(Element elem, boolean selected) {
+ setStyleName(getCellParent(elem), STYLENNAME_SELECTED, selected);
+ }
+ };
+ }
+
+ /**
+ * Cleanup this node view.
+ */
+ public void cleanup() {
+ impl.setSelectionModel(null);
+ }
+
+ public Range getRange() {
+ return impl.getRange();
+ }
+
+ public void setData(int start, int length, List<C> values) {
+ impl.setData(values, start);
+ }
+
+ public void setDataSize(int size, boolean isExact) {
+ impl.setDataSize(size);
+ }
+
+ public void setDelegate(Delegate<C> delegate) {
+ impl.setDelegate(delegate);
+ }
+
+ public void setSelectionModel(final SelectionModel<? super C> selectionModel) {
+ impl.setSelectionModel(selectionModel);
+ }
+
+ /**
+ * Assign this {@link ListView} to a new {@link StandardTreeNodeView}.
+ *
+ * @param nodeView the new node view
+ */
+ private void setNodeView(StandardTreeNodeView<?> nodeView) {
+ this.nodeView.listView = null;
+ this.nodeView = nodeView;
+ nodeView.listView = this;
+ }
+ }
+
+ /**
+ * True during the time a node should be animated.
+ */
+ private boolean animate;
+
+ /**
+ * A reference to the element that is used to animate nodes. Parent of the
+ * contentContainer.
+ */
+ private Element animationFrame;
+
+ /**
+ * A reference to the element that contains the children. Parent to the actual
+ * child nodes.
+ */
+ private Element childContainer;
+
+ /**
+ * A list of child views.
+ */
+ private List<StandardTreeNodeView<?>> children;
+
+ /**
+ * A reference to the element that contains all content. Parent of the
+ * childContainer and the show/hide elements.
+ */
+ private Element contentContainer;
+
+ /**
+ * The element used when there are no children to display.
+ */
+ private Element emptyMessageElem;
+
+ /**
+ * The list view used to display the nodes.
+ */
+ private NodeListView<?> listView;
+
+ /**
+ * The info about children of this node.
+ */
+ private NodeInfo<?> nodeInfo;
+
+ /**
+ * Indicates whether or not we've loaded the node info.
+ */
+ private boolean nodeInfoLoaded;
+
+ /**
+ * Indicates whether or not this node is open.
+ */
+ private boolean open;
+
+ /**
+ * The parent {@link StandardTreeNodeView}.
+ */
+ private final StandardTreeNodeView<?> parentNode;
+
+ /**
+ * The {@link NodeInfo} of the parent node.
+ */
+ private final NodeInfo<T> parentNodeInfo;
+
+ /**
+ * The element used to display less children.
+ */
+ private Element showFewerElem;
+
+ /**
+ * The element used to display more children.
+ */
+ private Element showMoreElem;
+
+ /**
+ * The {@link TreeView} that this node belongs to.
+ */
+ private final StandardTreeView tree;
+
+ /**
+ * This node's value.
+ */
+ private T value;
+
+ /**
+ * Construct a {@link StandardTreeNodeView}.
+ *
+ * @param tree the parent {@link StandardTreeNodeView}
+ * @param parent the parent {@link StandardTreeNodeView}
* @param parentNodeInfo the {@link NodeInfo} of the parent
- * @param elem the outer element of this {@link TreeNodeView}
+ * @param elem the outer element of this {@link StandardTreeNodeView}
* @param value the value of this node
*/
- StandardTreeNodeView(final TreeView tree,
+ StandardTreeNodeView(final StandardTreeView tree,
final StandardTreeNodeView<?> parent, NodeInfo<T> parentNodeInfo,
Element elem, T value) {
- super(tree, parent, parentNodeInfo, value);
+ this.tree = tree;
+ this.parentNode = parent;
+ this.parentNodeInfo = parentNodeInfo;
+ this.value = value;
setElement(elem);
}
- @Override
- protected <C> TreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo,
- Element childElem, C childValue, Void viewData, int idx) {
- return new StandardTreeNodeView<C>(getTree(), this, nodeInfo, childElem,
+ public int getChildCount() {
+ return children == null ? 0 : children.size();
+ }
+
+ public StandardTreeNodeView<?> getChildNode(int childIndex) {
+ return children.get(childIndex);
+ }
+
+ /**
+ * Check whether or not this node is open.
+ *
+ * @return true if open, false if closed
+ */
+ public boolean isOpen() {
+ return open;
+ }
+
+ /**
+ * Select this node.
+ */
+ public void select() {
+ SelectionModel<? super T> selectionModel = parentNodeInfo.getSelectionModel();
+ if (selectionModel != null) {
+ selectionModel.setSelected(value, true);
+ }
+ }
+
+ /**
+ * Sets whether this item's children are displayed.
+ *
+ * @param open whether the item is open
+ */
+ public void setOpen(boolean open) {
+ // Early out.
+ if (this.open == open) {
+ return;
+ }
+
+ this.animate = true;
+ this.open = open;
+ if (open) {
+ if (!nodeInfoLoaded) {
+ nodeInfoLoaded = true;
+ nodeInfo = tree.getTreeViewModel().getNodeInfo(value);
+ }
+
+ // If we don't have any nodeInfo, we must be a leaf node.
+ if (nodeInfo != null) {
+ // Add a loading message.
+ ensureChildContainer().setInnerHTML(tree.getLoadingHtml());
+ showOrHide(showFewerElem, false);
+ showOrHide(showMoreElem, false);
+ showOrHide(emptyMessageElem, false);
+ ensureAnimationFrame().getStyle().setProperty("display", "");
+ onOpen(nodeInfo);
+ }
+ } else {
+ cleanup();
+ tree.maybeAnimateTreeNode(this);
+ }
+
+ // Update the image.
+ updateImage();
+ }
+
+ /**
+ * Unregister the list handler and destroy all child nodes.
+ */
+ protected void cleanup() {
+ // Unregister the list handler.
+ if (listView != null) {
+ listView.cleanup();
+ nodeInfo.unsetView();
+ listView = null;
+ }
+
+ // Recursively kill children.
+ if (children != null) {
+ for (StandardTreeNodeView<?> child : children) {
+ child.cleanup();
+ }
+ children = null;
+ }
+ }
+
+ protected boolean consumeAnimate() {
+ boolean hasAnimate = animate;
+ animate = false;
+ return hasAnimate;
+ }
+
+ /**
+ * Returns an instance of TreeNodeView of the same subclass as the calling
+ * object.
+ *
+ * @param <C> the data type of the node's children
+ * @param nodeInfo a NodeInfo object describing the child nodes
+ * @param childElem the DOM element used to parent the new TreeNodeView
+ * @param childValue the child's value
+ * @param viewData view data associated with the node
+ * @return a TreeNodeView of suitable type
+ */
+ protected <C> StandardTreeNodeView<C> createTreeNodeView(
+ NodeInfo<C> nodeInfo, Element childElem, C childValue, Void viewData) {
+ return new StandardTreeNodeView<C>(tree, this, nodeInfo, childElem,
childValue);
}
- @Override
- protected <C> void emitHtml(StringBuilder sb, List<C> childValues,
- List<HasCell<C, ?, Void>> hasCells, List<TreeNodeView<?>> savedViews) {
- TreeView tree = getTree();
- TreeViewModel model = tree.getTreeViewModel();
- int imageWidth = tree.getImageWidth();
-
- SelectionModel<Object> selectionModel = tree.getSelectionModel();
-
- 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, this)) {
- sb.append(LEAF_IMAGE);
- } else {
- sb.append(tree.getClosedImageHtml(0));
- }
- if (selectionModel != null && selectionModel.isSelected(childValue)) {
- sb.append("<div class='gwt-stree-selectedItem'>");
- } else {
- sb.append("<div>");
- }
-
- for (int i = 0; i < hasCells.size(); i++) {
- sb.append("<span __idx='");
- sb.append(i);
- sb.append("'>");
- render(sb, childValue, hasCells.get(i));
- sb.append("</span>");
- }
-
- sb.append("</div></div>");
+ /**
+ * Fire an event to the {@link com.google.gwt.bikeshed.cells.client.Cell}.
+ *
+ * @param event the native event
+ * @return true if the cell consumes the event, false if not
+ */
+ protected boolean fireEventToCell(NativeEvent event) {
+ if (parentNodeInfo != null) {
+ Element cellParent = getCellParent();
+ Cell<T, Void> parentCell = parentNodeInfo.getCell();
+ parentCell.onBrowserEvent(cellParent, value, null, event,
+ parentNodeInfo.getValueUpdater());
+ return parentCell.consumesEvents();
}
+ return false;
}
/**
- * Ensure that the child container exists and return it.
- *
- * @return the child container
+ * Returns the element that parents the cell contents of this node.
*/
- @Override
- protected Element ensureChildContainer() {
- if (getChildContainer() == 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);
- animFrame.setId("animFrame");
- setChildContainer(animFrame.appendChild(Document.get().createDivElement()));
- }
- return getChildContainer();
- }
-
- /**
- * @return the element that contains the rendered cell
- */
- @Override
protected Element getCellParent() {
- return getElement().getChild(1).cast();
+ return getCellParent(getElement());
}
/**
- * @return the image element
+ * Returns the element corresponding to the open/close image.
+ *
+ * @return the open/close image element
*/
- @Override
protected Element getImageElement() {
return getElement().getFirstChildElement();
}
- @Override
- protected void postClose() {
- getTree().maybeAnimateTreeNode(this);
+ /**
+ * Returns the key for the value of this node using the parent's
+ * implementation of NodeInfo.getKey().
+ */
+ protected Object getValueKey() {
+ return parentNodeInfo.getProvidesKey().getKey(value);
+ }
+
+ /**
+ * Set up the node when it is opened.
+ *
+ * @param nodeInfo the {@link NodeInfo} that provides information about the
+ * child values
+ * @param <C> the child data type of the node
+ */
+ protected <C> void onOpen(final NodeInfo<C> nodeInfo) {
+ NodeListView<C> view = new NodeListView<C>(nodeInfo, this);
+ listView = view;
+ view.setSelectionModel(nodeInfo.getSelectionModel());
+ nodeInfo.setView(view);
+ }
+
+ /**
+ * Update the image based on the current state.
+ */
+ protected void updateImage() {
+ // Early out if this is a root node.
+ if (parentNode == null) {
+ return;
+ }
+
+ // Replace the image element with a new one.
+ String html = open ? tree.getOpenImageHtml() : tree.getClosedImageHtml();
+ if (nodeInfoLoaded && nodeInfo == null) {
+ html = LEAF_IMAGE;
+ }
+ Element tmp = Document.get().createDivElement();
+ tmp.setInnerHTML(html);
+ Element imageElem = tmp.getFirstChildElement();
+
+ Element oldImg = getImageElement();
+ oldImg.getParentElement().replaceChild(imageElem, oldImg);
+ }
+
+ /**
+ * Ensure that the animation frame exists and return it.
+ *
+ * @return the animation frame
+ */
+ Element ensureAnimationFrame() {
+ if (animationFrame == null) {
+ animationFrame = Document.get().createDivElement();
+ animationFrame.getStyle().setPosition(Position.RELATIVE);
+ animationFrame.getStyle().setOverflow(Overflow.HIDDEN);
+ animationFrame.setId("animFrame");
+ getElement().appendChild(animationFrame);
+ }
+ return animationFrame;
+ }
+
+ /**
+ * Ensure that the child container exists and return it.
+ *
+ * @return the child container
+ */
+ Element ensureChildContainer() {
+ if (childContainer == null) {
+ childContainer = Document.get().createDivElement();
+ ensureContentContainer().insertFirst(childContainer);
+ }
+ return childContainer;
+ }
+
+ /**
+ * Ensure that the content container exists and return it.
+ *
+ * @return the content container
+ */
+ Element ensureContentContainer() {
+ if (contentContainer == null) {
+ contentContainer = Document.get().createDivElement();
+ ensureAnimationFrame().appendChild(contentContainer);
+
+ emptyMessageElem = Document.get().createDivElement();
+ emptyMessageElem.setInnerHTML("<i>no data</i>");
+ showOrHide(emptyMessageElem, false);
+ contentContainer.appendChild(emptyMessageElem);
+
+ showMoreElem = Document.get().createPushButtonElement();
+ showMoreElem.setInnerText("Show more");
+ showOrHide(showMoreElem, false);
+ contentContainer.appendChild(showMoreElem);
+
+ showFewerElem = Document.get().createPushButtonElement();
+ showFewerElem.setInnerText("Show fewer");
+ showOrHide(showFewerElem, false);
+ contentContainer.appendChild(showFewerElem);
+ }
+ return contentContainer;
+ }
+
+ Element getShowFewerElement() {
+ return showFewerElem;
+ }
+
+ Element getShowMoreElement() {
+ return showMoreElem;
+ }
+
+ void showFewer() {
+ listView.impl.showFewer();
+ }
+
+ void showMore() {
+ listView.impl.showMore();
}
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
index a2a78ff..1be9854 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/StandardTreeView.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,12 +15,13 @@
*/
package com.google.gwt.bikeshed.tree.client;
-import com.google.gwt.dom.client.Document;
+import com.google.gwt.animation.client.Animation;
import com.google.gwt.dom.client.Element;
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.Event;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.HasAnimation;
import java.util.ArrayList;
@@ -31,14 +32,49 @@
public class StandardTreeView extends TreeView implements HasAnimation {
/**
- * A {@link TreeView.TreeNodeViewAnimation} that reveals the contents of child
- * nodes.
+ * A node animation.
*/
- public static class RevealAnimation extends TreeNodeViewAnimation {
+ public abstract static class NodeAnimation extends Animation {
+
+ /**
+ * The default animation delay in milliseconds.
+ */
+ private static final int DEFAULT_ANIMATION_DURATION = 450;
+
+ /**
+ * The duration of the animation.
+ */
+ private int duration = DEFAULT_ANIMATION_DURATION;
+
+ NodeAnimation() {
+ }
+
+ /**
+ * Animate a tree node into its new state.
+ *
+ * @param node the node to animate
+ * @param isAnimationEnabled true to animate
+ */
+ abstract void animate(StandardTreeNodeView<?> node,
+ boolean isAnimationEnabled);
+
+ public int getDuration() {
+ return duration;
+ }
+
+ public void setDuration(int duration) {
+ this.duration = duration;
+ }
+ }
+
+ /**
+ * A {@link NodeAnimation} that reveals the contents of child nodes.
+ */
+ public static class RevealAnimation extends NodeAnimation {
/**
* Create a new {@link RevealAnimation}.
- *
+ *
* @return the new animation
*/
public static RevealAnimation create() {
@@ -46,44 +82,52 @@
}
/**
- * The container that holds the child container.
+ * The container that holds the content, includind the children.
*/
- Element animFrame;
- /**
- * The container that holds the children.
- */
- Element childContainer;
+ Element contentContainer;
/**
* The target height when opening, the start height when closing.
*/
int height;
+
/**
* True if the node is opening, false if closing.
*/
boolean opening;
/**
+ * The container that holds the child container.
+ */
+ private Element animFrame;
+
+ /**
+ * The container that holds the children.
+ */
+ private Element childContainer;
+
+ /**
* Not instantiable.
*/
private RevealAnimation() {
}
/**
- * Animate a {@link TreeNodeView} into its new state.
- *
- * @param node the {@link TreeNodeView} to animate
+ * Animate a {@link StandardTreeNodeView} into its new state.
+ *
+ * @param node the {@link StandardTreeNodeView} to animate
* @param isAnimationEnabled true to animate
*/
@Override
- public void animate(TreeNodeView<?> node, boolean isAnimationEnabled) {
+ void animate(StandardTreeNodeView<?> node, boolean isAnimationEnabled) {
// Cancel any pending animations.
cancel();
// Initialize the fields.
- this.opening = node.getState();
- childContainer = node.getChildContainer();
- animFrame = childContainer.getParentElement();
+ this.opening = node.isOpen();
+ animFrame = node.ensureAnimationFrame();
+ contentContainer = node.ensureContentContainer();
+ childContainer = node.ensureChildContainer();
if (isAnimationEnabled) {
// Animated.
@@ -111,9 +155,9 @@
if (opening) {
animFrame.getStyle().setHeight(1.0, Unit.PX);
animFrame.getStyle().clearDisplay();
- height = childContainer.getScrollHeight();
+ height = contentContainer.getScrollHeight();
} else {
- height = childContainer.getOffsetHeight();
+ height = contentContainer.getOffsetHeight();
}
}
@@ -139,18 +183,19 @@
childContainer.setInnerHTML("");
}
animFrame.getStyle().clearHeight();
+ this.contentContainer = null;
this.childContainer = null;
this.animFrame = null;
}
}
/**
- * A {@link TreeView.TreeNodeViewAnimation} that slides children into view.
+ * A {@link NodeAnimation} that slides children into view.
*/
public static class SlideAnimation extends RevealAnimation {
/**
* Create a new {@link RevealAnimation}.
- *
+ *
* @return the new animation
*/
public static SlideAnimation create() {
@@ -165,9 +210,9 @@
@Override
protected void onComplete() {
- childContainer.getStyle().clearPosition();
- childContainer.getStyle().clearTop();
- childContainer.getStyle().clearWidth();
+ contentContainer.getStyle().clearPosition();
+ contentContainer.getStyle().clearTop();
+ contentContainer.getStyle().clearWidth();
super.onComplete();
}
@@ -175,11 +220,11 @@
protected void onStart() {
super.onStart();
if (opening) {
- childContainer.getStyle().setTop(-height, Unit.PX);
+ contentContainer.getStyle().setTop(-height, Unit.PX);
} else {
- childContainer.getStyle().setTop(0, Unit.PX);
+ contentContainer.getStyle().setTop(0, Unit.PX);
}
- childContainer.getStyle().setPosition(Position.RELATIVE);
+ contentContainer.getStyle().setPosition(Position.RELATIVE);
}
@Override
@@ -187,38 +232,92 @@
super.onUpdate(progress);
if (opening) {
double curTop = (1.0 - progress) * -height;
- childContainer.getStyle().setTop(curTop, Unit.PX);
+ contentContainer.getStyle().setTop(curTop, Unit.PX);
} else {
double curTop = progress * -height;
- childContainer.getStyle().setTop(curTop, Unit.PX);
+ contentContainer.getStyle().setTop(curTop, Unit.PX);
}
}
}
/**
+ * The animation.
+ */
+ private NodeAnimation animation;
+
+ /**
+ * The HTML used to generate the closed image.
+ */
+ private String closedImageHtml;
+
+ /**
+ * Indicates whether or not animations are enabled.
+ */
+ private boolean isAnimationEnabled;
+
+ /**
+ * The message displayed while child nodes are loading.
+ */
+ // TODO(jlabanca): I18N loading HTML, or remove the text.
+ private String loadingHtml = "Loading...";
+
+ /**
+ * The HTML used to generate the open image.
+ */
+ private String openImageHtml;
+
+ /**
+ * The hidden root node in the tree.
+ */
+ private StandardTreeNodeView<?> 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> StandardTreeView(TreeViewModel viewModel, T rootValue) {
super(viewModel);
- setElement(Document.get().createDivElement());
- setStyleName("gwt-TreeView");
+ setStyleName("gwt-StandardTreeView");
// We use one animation for the entire tree.
setAnimation(SlideAnimation.create());
// Add event handlers.
- sinkEvents(Event.ONCLICK | Event.ONMOUSEDOWN | Event.ONMOUSEUP
- | Event.ONCHANGE);
+ sinkEvents(Event.ONCLICK | Event.ONCHANGE | Event.MOUSEEVENTS);
// Associate a view with the item.
- TreeNodeView<T> root = new StandardTreeNodeView<T>(this, null, null,
- getElement(), rootValue);
- setRootNode(root);
- root.setState(true);
+ StandardTreeNodeView<T> root = new StandardTreeNodeView<T>(this, null,
+ null, getElement(), rootValue);
+ rootNode = root;
+ root.setOpen(true);
+ }
+
+ /**
+ * Get the animation used to open and close nodes in this tree if animations
+ * are enabled.
+ *
+ * @return the animation
+ * @see #isAnimationEnabled()
+ */
+ public NodeAnimation 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 boolean isAnimationEnabled() {
+ return isAnimationEnabled;
}
@Override
@@ -226,57 +325,126 @@
super.onBrowserEvent(event);
Element target = event.getEventTarget().cast();
- TreeNodeView<?> rootNode = getRootTreeNodeView();
ArrayList<Element> chain = new ArrayList<Element>();
- ArrayList<String> ids = new ArrayList<String>();
- collectElementChain(chain, ids, getElement(), target);
+ collectElementChain(chain, getElement(), target);
- TreeNodeView<?> nodeView = findItemByChain(chain, 0, rootNode);
+ StandardTreeNodeView<?> nodeView = findItemByChain(chain, 0, rootNode);
if (nodeView != null && nodeView != rootNode) {
- if (nodeView.getImageElement().isOrHasChild(target)) {
- if ("click".equals(event.getType())) {
- nodeView.setState(!nodeView.getState());
+ if ("click".equals(event.getType())) {
+ // Open the node when the open image is clicked.
+ Element showFewerElem = nodeView.getShowFewerElement();
+ Element showMoreElem = nodeView.getShowMoreElement();
+ if (nodeView.getImageElement().isOrHasChild(target)) {
+ nodeView.setOpen(!nodeView.isOpen());
+ return;
+ } else if (showFewerElem != null && showFewerElem.isOrHasChild(target)) {
+ nodeView.showFewer();
+ return;
+ } else if (showMoreElem != null && showMoreElem.isOrHasChild(target)) {
+ nodeView.showMore();
+ return;
}
- } else if (nodeView.getCellParent().isOrHasChild(target)) {
- nodeView.fireEventToCell(event);
+ }
- // TODO(jgw): Kind of a hacky way to set selection. Need to generalize
- // this to some sort of keyboard/mouse->selection controller.
- if (getSelectionModel() != null) {
- if ("click".equals(event.getType())) {
- getSelectionModel().setSelected(nodeView.getValue(), true);
- }
+ // Forward the event to the cell.
+ if (nodeView.getCellParent().isOrHasChild(target)) {
+ boolean consumesEvent = nodeView.fireEventToCell(event);
+ if (!consumesEvent && "click".equals(event.getType())) {
+ nodeView.select();
}
}
}
}
/**
+ * 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 a {@link NodeAnimation}
+ * @see #setAnimationEnabled(boolean)
+ */
+ public void setAnimation(NodeAnimation 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() {
+ if (closedImageHtml == null) {
+ AbstractImagePrototype proto = AbstractImagePrototype.create(getResources().treeClosed());
+ closedImageHtml = proto.getHTML().replace("style='",
+ "style='position:absolute;left:0px;top:0px;");
+ }
+ return closedImageHtml;
+ }
+
+ /**
+ * @return the HTML to render the open image.
+ */
+ String getOpenImageHtml() {
+ if (openImageHtml == null) {
+ AbstractImagePrototype proto = AbstractImagePrototype.create(getResources().treeOpen());
+ openImageHtml = proto.getHTML().replace("style='",
+ "style='position:absolute;left:0px;top:0px;");
+ }
+ return openImageHtml;
+ }
+
+ /**
+ * Animate the current state of a {@link StandardTreeNodeView} in this tree.
+ *
+ * @param node the node to animate
+ */
+ void maybeAnimateTreeNode(StandardTreeNodeView<?> node) {
+ if (animation != null) {
+ animation.animate(node, node.consumeAnimate() && isAnimationEnabled());
+ }
+ }
+
+ /**
* 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) {
+ private void collectElementChain(ArrayList<Element> chain, Element hRoot,
+ Element hElem) {
if ((hElem == null) || (hElem == hRoot)) {
return;
}
- collectElementChain(chain, ids, hRoot, hElem.getParentElement());
+ collectElementChain(chain, hRoot, hElem.getParentElement());
chain.add(hElem);
- ids.add(hElem.getId());
}
- private TreeNodeView<?> findItemByChain(ArrayList<Element> chain, int idx,
- TreeNodeView<?> parent) {
+ private StandardTreeNodeView<?> findItemByChain(ArrayList<Element> chain,
+ int idx, StandardTreeNodeView<?> 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);
+ StandardTreeNodeView<?> child = parent.getChildNode(i);
if (child.getElement() == hCurElem) {
- TreeNodeView<?> retItem = findItemByChain(chain, idx + 1, child);
+ StandardTreeNodeView<?> retItem = findItemByChain(chain, idx + 1, child);
if (retItem == null) {
return child;
}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java
deleted file mode 100644
index 690b556..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNode.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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();
-
- boolean isOpen();
-}
diff --git a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
deleted file mode 100644
index 0def3f8..0000000
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeNodeView.java
+++ /dev/null
@@ -1,566 +0,0 @@
-/*
- * 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.list.client.HasCell;
-import com.google.gwt.bikeshed.list.client.ListView;
-import com.google.gwt.bikeshed.list.shared.ProvidesKey;
-import com.google.gwt.bikeshed.list.shared.Range;
-import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter.DefaultRange;
-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.NativeEvent;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.ui.UIObject;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A view of a tree node.
- *
- * @param <T> the type that this {@link TreeNodeView} contains
- */
-public abstract class TreeNodeView<T> extends UIObject implements TreeNode<T> {
-
- /**
- * A NodeInfo and list of data items of matching type.
- *
- * @param <C> the data type
- */
- static class SavedInfo<C> {
- NodeInfo<C> nodeInfo;
- List<C> values;
- }
-
- /**
- * The element used in place of an image when a node has no children.
- */
- public static final String LEAF_IMAGE = "<div style='position:absolute;display:none;'></div>";
-
- /**
- * True during the time a node should be animated.
- */
- private boolean animate;
-
- /**
- * A reference to the element that contains the children.
- */
- private Element childContainer;
-
- /**
- * A reference to the element that contains the children.
- */
- private List<TreeNodeView<?>> children;
-
- /**
- * The list registration for the list of children.
- */
- private ListView<?> listView;
-
- /**
- * The info about children of this node.
- */
- private NodeInfo<?> nodeInfo;
-
- /**
- * Indicates whether or not we've loaded the node info.
- */
- private boolean nodeInfoLoaded;
-
- /**
- * Indicates whether or not this node is open.
- */
- private boolean open;
-
- /**
- * The parent {@link SideBySideTreeNodeView}.
- */
- private final TreeNodeView<?> parentNode;
-
- /**
- * The parent {@link SideBySideTreeNodeView}.
- */
- private final NodeInfo<T> parentNodeInfo;
-
- /**
- * The NodeInfo for this node along with saved child data values.
- */
- private SavedInfo<?> savedInfo;
-
- /**
- * The info about this node.
- */
- private final TreeView tree;
-
- /**
- * This node's value.
- */
- private T value;
-
- public TreeNodeView(TreeView tree, TreeNodeView<?> parent, NodeInfo<T> parentNodeInfo, T value) {
- this.tree = tree;
- this.parentNode = parent;
- this.parentNodeInfo = parentNodeInfo;
- this.value = value;
- }
-
- public int getChildCount() {
- return children == null ? 0 : children.size();
- }
-
- 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 parentNode;
- }
-
- /**
- * Check whether or not this {@link TreeNodeView} is open.
- *
- * @return true if open, false if closed
- */
- public boolean getState() {
- return open;
- }
-
- /**
- * Get the value contained in this node.
- *
- * @return the value of the node
- */
- public T getValue() {
- return value;
- }
-
- /**
- * Returns true if the node is open.
- */
- public boolean isOpen() {
- return open;
- }
-
- /**
- * 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
- */
- public void setState(boolean open) {
- setState(open, true);
- }
-
- /**
- * Sets whether this item's children are displayed.
- *
- * @param open whether the item is open
- * @param fireEvents <code>true</code> to allow open/close events to be
- */
- public void setState(boolean open, boolean fireEvents) {
- // TODO(jlabanca) - allow people to add open/close handlers.
-
- // Early out.
- if (this.open == open) {
- return;
- }
-
- this.animate = true;
- this.open = open;
- if (open) {
- if (!nodeInfoLoaded) {
- 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();
- postClose();
- }
-
- // Update the image.
- updateImage();
- }
-
- /**
- * Unregister the list handler and destroy all child nodes.
- */
- protected void cleanup() {
- // Unregister the list handler.
- if (listView != null) {
- nodeInfo.unsetView();
- listView = null;
- }
-
- // Recursively kill children.
- if (children != null) {
- for (TreeNodeView<?> child : children) {
- child.cleanup();
- }
- children = null;
- }
- }
-
- protected boolean consumeAnimate() {
- boolean hasAnimate = animate;
- animate = false;
- return hasAnimate;
- }
-
- /**
- * Returns an instance of TreeNodeView of the same subclass as the
- * calling object.
- *
- * @param <C> the data type of the node's children
- * @param nodeInfo a NodeInfo object describing the child nodes
- * @param childElem the DOM element used to parent the new TreeNodeView
- * @param childValue the child's value
- * @param viewData view data associated with the node
- * @param idx the index of the child within its parent node
- * @return a TreeNodeView of suitable type
- */
- protected abstract <C> TreeNodeView<C> createTreeNodeView(NodeInfo<C> nodeInfo,
- Element childElem, C childValue, Void viewData, int idx);
-
- /**
- * Write the HTML for a list of child values into the given StringBuilder.
- *
- * @param <C> the data type of the child nodes
- * @param sb a StringBuilder to write to
- * @param childValues a List of child node values
- * @param hasCells the cells and values to use for rendering each child value
- * @param savedViews a List of TreeNodeView instances corresponding to
- * the child values; a non-null value indicates a TreeNodeView previously
- * associated with a given child value
- */
- protected abstract <C> void emitHtml(StringBuilder sb, List<C> childValues,
- List<HasCell<C, ?, Void>> hasCells, List<TreeNodeView<?>> savedViews);
-
- /**
- * Ensure that the animation frame exists and return it.
- *
- * @return the animation frame
- */
- protected Element ensureAnimationFrame() {
- return ensureChildContainer().getParentElement();
- }
-
- /**
- * Ensure that the child container exists and return it.
- *
- * @return the child container
- */
- 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;
- }
-
- /**
- * Returns the element that contains this node.
- * The default implementation returns the value of getElement().
- */
- protected Element getContainer() {
- return getElement();
- }
-
- /**
- * Returns the element corresponding to the open/close image.
- */
- 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;
- }
-
- /**
- * Returns the key for the value of this node using the parent's
- * implementation of NodeInfo.getKey().
- */
- protected Object getValueKey() {
- return getParentNodeInfo().getProvidesKey().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
- */
- protected <C> void onOpen(final NodeInfo<C> nodeInfo) {
- // Add a loading message.
- ensureChildContainer().setInnerHTML(tree.getLoadingHtml());
- ensureAnimationFrame().getStyle().setProperty("display", "");
-
- // Create a SavedInfo object to store the NodeInfo and data values
- final SavedInfo<C> localSavedInfo = new SavedInfo<C>();
- localSavedInfo.nodeInfo = nodeInfo;
- localSavedInfo.values = new ArrayList<C>();
- TreeNodeView.this.savedInfo = localSavedInfo;
-
- // Get the node info.
- final ProvidesKey<C> providesKey = nodeInfo.getProvidesKey();
- ListView<C> view = new ListView<C>() {
- public Range getRange() {
- return new DefaultRange(0, 100);
- }
-
- public void setData(int start, int length, List<C> values) {
- // TODO - handle event start and length properly
- int end = start + length;
-
- // Ensure savedInfo has a place to store the data values
- while (localSavedInfo.values.size() < end) {
- savedInfo.values.add(null);
- }
- // Save child values into savedInfo
- int index = start;
- for (C childValue : values) {
- localSavedInfo.values.set(index++, childValue);
- }
-
- // Construct a map of former child views based on their value keys.
- Map<Object, TreeNodeView<?>> map = new HashMap<Object, TreeNodeView<?>>();
- if (children != null) {
- for (TreeNodeView<?> child : children) {
- // Ignore child nodes that are closed
- if (child.getState()) {
- map.put(child.getValueKey(), child);
- }
- }
- }
-
- // Hide the child container so we can animate it.
- if (tree.isAnimationEnabled()) {
- ensureAnimationFrame().getStyle().setDisplay(Display.NONE);
- }
-
- List<TreeNodeView<?>> savedViews = new ArrayList<TreeNodeView<?>>();
- for (C childValue : values) {
- // Remove any child elements that correspond to prior children
- // so the call to setInnerHtml will not destroy them
- TreeNodeView<?> savedView = map.get(providesKey.getKey(childValue));
- if (savedView != null) {
- savedView.getContainer().getFirstChild().removeFromParent();
- }
- savedViews.add(savedView);
- }
-
- // Construct the child contents.
- StringBuilder sb = new StringBuilder();
- emitHtml(sb, values, nodeInfo.getHasCells(), 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 : values) {
- TreeNodeView<C> child = createTreeNodeView(nodeInfo, childElem,
- childValue, null, idx);
- TreeNodeView<?> savedChild = map.get(providesKey.getKey(childValue));
- // Copy the saved child's state into the new child
- if (savedChild != null) {
- child.children = savedChild.children;
- child.childContainer = savedChild.childContainer;
- child.nodeInfo = savedChild.nodeInfo;
- child.nodeInfoLoaded = savedChild.nodeInfoLoaded;
- child.open = savedChild.getState();
-
- // Copy the child container element to the new child
- // TODO(rice) clean up this expression
- child.getContainer().appendChild(savedChild.childContainer.getParentElement());
- }
-
- children.add(child);
- childElem = childElem.getNextSiblingElement();
-
- idx++;
- }
-
- // Animate the child container open.
- if (tree.isAnimationEnabled()) {
- tree.maybeAnimateTreeNode(TreeNodeView.this);
- }
- }
-
- public void setDataSize(int size, boolean isExact) {
- if (size == 0 && isExact) {
- ensureChildContainer().setInnerHTML("<i>no data</i>");
- if (children != null) {
- for (TreeNodeView<?> child : children) {
- child.cleanup();
- }
- children = null;
- }
- }
- }
-
- public void setDelegate(ListView.Delegate<C> delegate) {
- // Range never actually changes so no need to store the delegate
- }
- };
- nodeInfo.setView(view);
- this.listView = view;
- }
-
- /**
- * 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() {
- }
-
- /**
- * Refresh any cells that depend on the selection state. The default
- * implementation works for {@link StandardTreeNodeView} and
- * {@link SideBySideTreeNodeView}; other subclasses will need to implement
- * their own versions of this method.
- */
- protected void refreshSelection() {
- refreshSelection(savedInfo);
- }
-
- protected void setChildContainer(Element childContainer) {
- this.childContainer = childContainer;
- }
-
- /**
- * Update the image based on the current state.
- */
- protected void updateImage() {
- // Early out if this is a root node.
- if (isRootNode()) {
- return;
- }
-
- // Replace the image element with a new one.
- int imageLeft = getImageLeft();
- String html = open ? tree.getOpenImageHtml(imageLeft) : tree.getClosedImageHtml(imageLeft);
- if (nodeInfoLoaded && nodeInfo == null) {
- html = LEAF_IMAGE;
- }
- Element tmp = Document.get().createDivElement();
- tmp.setInnerHTML(html);
- Element imageElem = tmp.getFirstChildElement();
- getElement().replaceChild(imageElem, getImageElement());
- }
-
- TreeView getTree() {
- return tree;
- }
-
- <C, X> void render(StringBuilder sb, C childValue,
- HasCell<C, X, Void> hc) {
- hc.getCell().render(hc.getValue(childValue), null, sb);
- }
-
- /**
- * Refresh any cells that depend on the selection state. Note that this
- * implementation is dependent on the particular DOM structure used by
- * {@link StandardTreeNodeView} and {@link SideBySideTreeNodeView}.
- *
- * @param <C> the child data type
- * @param savedInfo a SavedInfo object containing a NodeInfo instance
- * and a list of saved child data values
- */
- private <C> void refreshSelection(SavedInfo<C> savedInfo) {
- List<HasCell<C, ?, Void>> hasCells = savedInfo.nodeInfo.getHasCells();
- Element outerDiv = childContainer.getFirstChildElement();
- int index = 0;
- while (outerDiv != null) {
- C childValue = savedInfo.values.get(index);
- Element span = outerDiv.getFirstChildElement().getNextSiblingElement().getFirstChildElement();
-
- for (HasCell<C, ?, Void> hasCell : hasCells) {
- if (hasCell.dependsOnSelection()) {
- StringBuilder sb = new StringBuilder();
- render(sb, childValue, hasCell);
- span.setInnerHTML(sb.toString());
- }
- span = span.getNextSiblingElement();
- }
- outerDiv = outerDiv.getNextSibling().cast();
- index++;
- }
- }
-}
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 55d9f16..089c4a6 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeView.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,58 +15,22 @@
*/
package com.google.gwt.bikeshed.tree.client;
-import com.google.gwt.animation.client.Animation;
-import com.google.gwt.bikeshed.list.shared.SelectionModel;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
-import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeHandler;
import com.google.gwt.core.client.GWT;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
-import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A view of a tree.
*/
-public abstract class TreeView extends Widget {
-
- /**
- * An Animation of a {@link TreeNodeView}.
- */
- public abstract static class TreeNodeViewAnimation extends Animation {
-
- /**
- * The default animation delay in milliseconds.
- */
- private static final int DEFAULT_ANIMATION_DURATION = 450;
-
- /**
- * The duration of the animation.
- */
- private int duration = DEFAULT_ANIMATION_DURATION;
-
- /**
- * Animate a {@link TreeNodeView} into its new state.
- *
- * @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;
- }
-
- public void setDuration(int duration) {
- this.duration = duration;
- }
- }
+public abstract class TreeView extends Composite {
/**
* A ClientBundle that provides images for this widget.
*/
- interface Resources extends ClientBundle {
+ public static interface Resources extends ClientBundle {
/**
* An image indicating a closed branch.
@@ -79,49 +43,12 @@
ImageResource treeOpen();
}
- private class TreeSelectionHandler implements SelectionChangeHandler {
- public void onSelectionChange(SelectionChangeEvent event) {
- refreshSelection();
- }
- }
-
private static final Resources DEFAULT_RESOURCES = GWT.create(Resources.class);
/**
- * The animation.
+ * The {@link Resources} used in the tree.
*/
- private TreeNodeViewAnimation animation;
-
- /**
- * The HTML used to generate the closed image.
- */
- private String closedImageHtml;
-
- /**
- * Indicates whether or not animations are enabled.
- */
- private boolean isAnimationEnabled;
-
- /**
- * The message displayed while child nodes are loading.
- */
- // TODO(jlabanca): I18N this, or remove the text
- private String loadingHtml = "Loading...";
-
- /**
- * The HTML used to generate the open image.
- */
- private String openImageHtml;
-
- /**
- * The hidden root node in the tree.
- */
- private TreeNodeView<?> rootNode;
-
- /**
- * The {@link SelectionModel} for the tree.
- */
- private SelectionModel<Object> selectionModel;
+ private Resources resources = DEFAULT_RESOURCES;
/**
* The {@link TreeViewModel} that backs the tree.
@@ -130,96 +57,26 @@
/**
* Construct a new {@link TreeView}.
- *
+ *
* @param viewModel the {@link TreeViewModel} that backs the tree
*/
+ // TODO(jlabanca): Should we nuke this class?
public TreeView(TreeViewModel viewModel) {
+ this(viewModel, new SimplePanel());
+ }
+
+ protected TreeView(TreeViewModel viewModel, Widget widget) {
this.viewModel = viewModel;
- }
-
- /**
- * Get the animation used to open and close nodes in this tree if animations
- * are enabled.
- *
- * @return the animation
- * @see #isAnimationEnabled()
- */
- public TreeNodeViewAnimation getAnimation() {
- return animation;
- }
-
- public TreeNode<?> getRootNode() {
- return rootNode;
- }
-
- /**
- * Returns the {@link SelectionModel} containing the selection state for
- * this tree, or null if none is present.
- */
- public SelectionModel<Object> getSelectionModel() {
- return selectionModel;
+ initWidget(widget);
}
public TreeViewModel getTreeViewModel() {
return viewModel;
}
- public boolean isAnimationEnabled() {
- return isAnimationEnabled;
- }
-
- /**
- * Refresh any visible cells of this tree that depend on the selection state.
- */
- public void refreshSelection() {
- refreshSelection(rootNode);
- }
-
- /**
- * 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 a {@link TreeNodeViewAnimation}
- * @see #setAnimationEnabled(boolean)
- */
- 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();
- }
- }
-
- public void setSelectionModel(SelectionModel<Object> selectionModel) {
- this.selectionModel = selectionModel;
- // Attach a selection handler.
- if (selectionModel != null) {
- selectionModel.addSelectionChangeHandler(new TreeSelectionHandler());
- }
- }
-
- /**
- * @return the HTML to render the closed image.
- */
- protected String getClosedImageHtml(int left) {
- if (closedImageHtml == null) {
- AbstractImagePrototype proto =
- AbstractImagePrototype.create(DEFAULT_RESOURCES.treeClosed());
- // CHECKSTYLE_OFF
- closedImageHtml = proto.getHTML().replace("style='",
- "style='position:absolute;left:" + left + "px;top:0px;");
- // CHECKSTYLE_ON
- }
- return closedImageHtml;
- }
-
/**
* Get the width required for the images.
- *
+ *
* @return the maximum width required for images.
*/
protected int getImageWidth() {
@@ -228,67 +85,11 @@
}
/**
- * Get the HTML string that is displayed while nodes wait for their children
- * to load.
- *
- * @return the loading HTML string
+ * Get the {@link Resources} used in the tree.
+ *
+ * @return the resources
*/
- protected String getLoadingHtml() {
- return loadingHtml;
- }
-
- /**
- * @return the HTML to render the open image.
- */
- protected String getOpenImageHtml(int left) {
- if (openImageHtml == null) {
- AbstractImagePrototype proto =
- AbstractImagePrototype.create(DEFAULT_RESOURCES.treeOpen());
- // CHECKSTYLE_OFF
- openImageHtml = proto.getHTML().replace("style='",
- "style='position:absolute;left:" + left + "px;top:0px;");
- // CHECKSTYLE_ON
- }
- return openImageHtml;
- }
-
- protected TreeNodeView<?> getRootTreeNodeView() {
- return rootNode;
- }
-
- /**
- * Animate the current state of a {@link TreeNodeView} in this tree.
- *
- * @param node the node to animate
- */
- protected void maybeAnimateTreeNode(TreeNodeView<?> node) {
- if (animation != null) {
- animation.animate(node, node.consumeAnimate() && isAnimationEnabled());
- }
- }
-
- /**
- * Set the HTML string that will be displayed when a node is waiting for its
- * child nodes to load.
- *
- * @param loadingHtml the HTML string
- */
- protected void setLoadingHtml(String loadingHtml) {
- this.loadingHtml = loadingHtml;
- }
-
- protected void setRootNode(TreeNodeView<?> rootNode) {
- this.rootNode = rootNode;
- }
-
- private void refreshSelection(TreeNodeView<?> node) {
- node.refreshSelection();
- int count = node.getChildCount();
- for (int i = 0; i < count; i++) {
- TreeNodeView<?> child = node.getChildTreeNodeView(i);
- if (child.isOpen()) {
- refreshSelection(child);
- }
- }
+ protected Resources getResources() {
+ return resources;
}
}
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 332b6dc..85df9db 100644
--- a/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/bikeshed/tree/client/TreeViewModel.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -16,17 +16,12 @@
package com.google.gwt.bikeshed.tree.client;
import com.google.gwt.bikeshed.cells.client.Cell;
-import com.google.gwt.bikeshed.cells.client.FieldUpdater;
import com.google.gwt.bikeshed.cells.client.ValueUpdater;
-import com.google.gwt.bikeshed.list.client.HasCell;
import com.google.gwt.bikeshed.list.client.ListView;
import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
import com.google.gwt.bikeshed.list.shared.ProvidesKey;
-import com.google.gwt.dom.client.Element;
-import com.google.gwt.dom.client.NativeEvent;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.google.gwt.bikeshed.list.shared.SelectionModel;
+import com.google.gwt.bikeshed.list.shared.SingleSelectionModel;
/**
* A model of a tree.
@@ -37,87 +32,57 @@
* Default implementation of {@link NodeInfo}.
*/
class DefaultNodeInfo<T> implements NodeInfo<T> {
- private List<HasCell<T, ?, Void>> hasCells = new ArrayList<HasCell<T, ?, Void>>();
+
+ private Cell<T, Void> cell;
private AbstractListViewAdapter<T> listViewAdapter;
+ private SelectionModel<? super T> selectionModel;
+ private ValueUpdater<T, Void> valueUpdater;
private ListView<T> view;
/**
* Construct a new {@link DefaultNodeInfo}.
- *
+ *
* @param adapter the {@link AbstractListViewAdapter} that provides the
* child values
* @param cell the {@link Cell} used to render the child values
- * @param dependsOnSelection true if the contents of the cell may require
- * an update when the selection changes
*/
public DefaultNodeInfo(AbstractListViewAdapter<T> adapter,
- Cell<T, Void> cell, boolean dependsOnSelection) {
- this(adapter, cell, dependsOnSelection, null);
+ Cell<T, Void> cell) {
+ this(adapter, cell, new SingleSelectionModel<T>(), null);
}
/**
- * Construct a new {@link DefaultNodeInfo} with a single cell and a
- * {@link ValueUpdater}.
- *
+ * Construct a new {@link DefaultNodeInfo}.
+ *
* @param adapter the {@link AbstractListViewAdapter} that provides the
* child values
- * @param cell the {@link Cell} used to render the child values
- * @param dependsOnSelection true if the contents of the cell may require
- * an update when the selection changes
+ * @param cell the {@link Cell} used to render the child values update when
+ * the selection changes
* @param valueUpdater the {@link ValueUpdater}
*/
public DefaultNodeInfo(AbstractListViewAdapter<T> adapter,
- final Cell<T, Void> cell, final boolean dependsOnSelection,
+ final Cell<T, Void> cell, SelectionModel<? super T> selectionModel,
final ValueUpdater<T, Void> valueUpdater) {
- hasCells.add(new HasCell<T, T, Void>() {
- public boolean dependsOnSelection() {
- return dependsOnSelection;
- }
-
- public Cell<T, Void> getCell() {
- return cell;
- }
-
- public FieldUpdater<T, T, Void> getFieldUpdater() {
- return valueUpdater == null ? null : new FieldUpdater<T, T, Void>() {
- public void update(int index, T object, T value, Void viewData) {
- valueUpdater.update(value, viewData);
- }
- };
- }
-
- public T getValue(T object) {
- return object;
- }
- });
this.listViewAdapter = adapter;
+ this.cell = cell;
+ this.selectionModel = selectionModel;
+ this.valueUpdater = valueUpdater;
}
- public DefaultNodeInfo(AbstractListViewAdapter<T> adapter,
- List<HasCell<T, ?, Void>> hasCells) {
- this.hasCells.addAll(hasCells);
- this.listViewAdapter = adapter;
- }
-
- public List<HasCell<T, ?, Void>> getHasCells() {
- return hasCells;
+ public Cell<T, Void> getCell() {
+ return cell;
}
public ProvidesKey<T> getProvidesKey() {
return listViewAdapter;
}
- public void onBrowserEvent(Element elem, final T object, NativeEvent event) {
- Element target = event.getEventTarget().cast();
- String idxString = "";
- while ((target != null)
- && ((idxString = target.getAttribute("__idx")).length() == 0)) {
- target = target.getParentElement();
- }
- if (idxString.length() > 0) {
- int idx = Integer.parseInt(idxString);
- dispatch(target, object, event, hasCells.get(idx));
- }
+ public SelectionModel<? super T> getSelectionModel() {
+ return selectionModel;
+ }
+
+ public ValueUpdater<T, Void> getValueUpdater() {
+ return valueUpdater;
}
public void setView(ListView<T> view) {
@@ -128,39 +93,56 @@
public void unsetView() {
listViewAdapter.removeView(view);
}
-
- private <X> void dispatch(Element target, final T object,
- NativeEvent event, HasCell<T, X, Void> hc) {
- final FieldUpdater<T, X, Void> fieldUpdater = hc.getFieldUpdater();
- hc.getCell().onBrowserEvent(target, hc.getValue(object), null, event,
- fieldUpdater == null ? null : new ValueUpdater<X, Void>() {
- public void update(X value, Void viewData) {
- fieldUpdater.update(0, object, value, viewData);
- }
- });
- }
}
/**
- * The info needed to create a {@link TreeNodeView}.
+ * The info needed to create the children of a tree node.
*/
interface NodeInfo<T> {
- List<HasCell<T, ?, Void>> getHasCells();
+ /**
+ * Get the {@link Cell} used to render the children of this node.
+ *
+ * @return the {@link Cell}
+ */
+ Cell<T, Void> getCell();
+ /**
+ * Return the key provider for children of this node.
+ *
+ * @return the {@link ProvidesKey}
+ */
ProvidesKey<T> getProvidesKey();
/**
- * Handle an event that is fired on one of the children of this item.
- *
- * @param elem the parent element of the item
- * @param object the data value of the item
- * @param event the event that was fired
+ * Get the {@link SelectionModel} used for the children of this node. To
+ * unify selection across all items of the same type, or across the entire
+ * tree, return the same instance of {@link SelectionModel} from all
+ * {@link NodeInfo}.
+ *
+ * @return the {@link SelectionModel}
*/
- void onBrowserEvent(Element elem, final T object, NativeEvent event);
+ SelectionModel<? super T> getSelectionModel();
+ /**
+ * Get the value updater associated with the cell.
+ *
+ * @return the value updater
+ */
+ ValueUpdater<T, Void> getValueUpdater();
+
+ /**
+ * Set the view that is listening to this {@link NodeInfo}. The
+ * implementation should attach the view to the source of data.
+ *
+ * @param view the {@link ListView}
+ */
void setView(ListView<T> view);
+ /**
+ * Unset the view from the {@link NodeInfo}. The implementation should
+ * detach the view from the source of data.
+ */
void unsetView();
}
@@ -168,20 +150,18 @@
* Get the {@link NodeInfo} that will provide the {@link ProvidesKey},
* {@link Cell}, and {@link ListView}s to retrieve and display the children of
* the specified value.
- *
+ *
* @param value the value in the parent node
- * @param treeNode the {@link TreeNode} that contains the value
* @return the {@link NodeInfo}
*/
- <T> NodeInfo<?> getNodeInfo(T value, TreeNode<T> treeNode);
+ <T> NodeInfo<?> getNodeInfo(T value);
/**
* Check if the value is known to be a leaf node.
- *
+ *
* @param value the value at the node
- * @param treeNode the {@link TreeNode} that contains the value
- *
+ *
* @return true if the node is known to be a leaf node, false otherwise
*/
- boolean isLeaf(Object value, TreeNode<?> treeNode);
+ boolean isLeaf(Object value);
}
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java
index 1fd42e9..b163ded 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/BasicTreeRecipe.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,6 +15,7 @@
*/
package com.google.gwt.sample.bikeshed.cookbook.client;
+import com.google.gwt.bikeshed.list.shared.MultiSelectionModel;
import com.google.gwt.bikeshed.list.shared.SelectionModel;
import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
import com.google.gwt.bikeshed.tree.client.StandardTreeView;
@@ -37,17 +38,15 @@
FlowPanel p = new FlowPanel();
final Label label = new Label();
- final MultiSelectionModel<Object> selectionModel = new MultiSelectionModel<Object>();
+ final MultiSelectionModel<String> selectionModel = new MultiSelectionModel<String>();
selectionModel.addSelectionChangeHandler(new SelectionModel.SelectionChangeHandler() {
public void onSelectionChange(SelectionChangeEvent event) {
- label.setText("Selected "
- + selectionModel.getSelectedSet().toString());
+ label.setText("Selected " + selectionModel.getSelectedSet().toString());
}
});
StandardTreeView stree = new StandardTreeView(new MyTreeViewModel(
selectionModel), "...");
- stree.setSelectionModel(selectionModel);
stree.setAnimationEnabled(true);
p.add(stree);
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.java
index 8d12fd1..5c32123 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/Cookbook.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -18,9 +18,9 @@
import com.google.gwt.bikeshed.cells.client.Cell;
import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
import com.google.gwt.bikeshed.list.shared.SelectionModel;
+import com.google.gwt.bikeshed.list.shared.SingleSelectionModel;
import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
import com.google.gwt.bikeshed.tree.client.StandardTreeView;
-import com.google.gwt.bikeshed.tree.client.TreeNode;
import com.google.gwt.bikeshed.tree.client.TreeViewModel;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
@@ -58,21 +58,27 @@
private static final class RecipeTreeModel implements TreeViewModel {
private ListViewAdapter<Category> adapter = new ListViewAdapter<Category>();
+ private SelectionModel<Object> selectionModel;
- public <T> NodeInfo<?> getNodeInfo(T value, TreeNode<T> treeNode) {
+ public RecipeTreeModel(SelectionModel<Object> selectionModel) {
+ this.selectionModel = selectionModel;
+ }
+
+ public <T> NodeInfo<?> getNodeInfo(T value) {
if (value == null) {
// Categories at the root.
- return new DefaultNodeInfo<Category>(adapter, new CategoryCell(), false);
+ return new DefaultNodeInfo<Category>(adapter, new CategoryCell(),
+ selectionModel, null);
} else if (value instanceof Category) {
// Demos for each category.
Category category = (Category) value;
return new DefaultNodeInfo<Recipe>(new ListViewAdapter<Recipe>(
- category.getRecipes()), new RecipeCell(), false);
+ category.getRecipes()), new RecipeCell(), selectionModel, null);
}
return null;
}
- public boolean isLeaf(Object value, TreeNode<?> treeNode) {
+ public boolean isLeaf(Object value) {
// The root and categories have children.
if (value == null || value instanceof Category) {
return false;
@@ -89,17 +95,18 @@
@UiField StandardTreeView recipeTree;
@UiField LayoutPanel container;
- private RecipeTreeModel recipeTreeModel = new RecipeTreeModel();
- private SingleSelectionModel<Object> treeSelection;
+ private RecipeTreeModel recipeTreeModel;
private SimpleCellListRecipe defaultRecipe;
private Recipe curRecipe;
public void onModuleLoad() {
+ // Initialize the UI.
+ final SingleSelectionModel<Object> treeSelection = new SingleSelectionModel<Object>();
+ recipeTreeModel = new RecipeTreeModel(treeSelection);
RootLayoutPanel.get().add(binder.createAndBindUi(this));
createRecipes(recipeTreeModel.adapter.getList());
- treeSelection = new SingleSelectionModel<Object>();
- recipeTree.setSelectionModel(treeSelection);
+ // Select a recipe on selection.
treeSelection.addSelectionChangeHandler(new SelectionModel.SelectionChangeHandler() {
public void onSelectionChange(SelectionChangeEvent event) {
Object o = treeSelection.getSelectedObject();
@@ -120,21 +127,13 @@
private void createRecipes(List<Category> cats) {
defaultRecipe = new SimpleCellListRecipe();
- cats.add(new Category("Lists", new Recipe[] {
- defaultRecipe
- }));
+ cats.add(new Category("Lists", new Recipe[] {defaultRecipe}));
cats.add(new Category("Tables", new Recipe[] {
- new BasicTableRecipe(),
- new EditableTableRecipe(),
- }));
+ new BasicTableRecipe(), new EditableTableRecipe(),}));
cats.add(new Category("Trees", new Recipe[] {
- new BasicTreeRecipe(),
- new SideBySideTreeRecipe(),
- }));
+ new BasicTreeRecipe(), new SideBySideTreeRecipe(),}));
cats.add(new Category("Other", new Recipe[] {
- new ValidationRecipe(),
- new MailRecipe(),
- }));
+ new ValidationRecipe(), new MailRecipe(),}));
}
private void showRecipe(Recipe recipe) {
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MyTreeViewModel.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MyTreeViewModel.java
index 6783775..93fe842 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MyTreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/MyTreeViewModel.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -18,13 +18,13 @@
import com.google.gwt.bikeshed.cells.client.ButtonCell;
import com.google.gwt.bikeshed.cells.client.Cell;
import com.google.gwt.bikeshed.cells.client.CheckboxCell;
+import com.google.gwt.bikeshed.cells.client.CompositeCell;
import com.google.gwt.bikeshed.cells.client.FieldUpdater;
import com.google.gwt.bikeshed.cells.client.ValueUpdater;
import com.google.gwt.bikeshed.list.client.HasCell;
import com.google.gwt.bikeshed.list.client.ListView;
import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
import com.google.gwt.bikeshed.list.shared.SelectionModel;
-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;
@@ -39,7 +39,8 @@
*/
public class MyTreeViewModel implements TreeViewModel {
- private static class IntegerListViewAdapter extends AbstractListViewAdapter<Integer> {
+ private static class IntegerListViewAdapter extends
+ AbstractListViewAdapter<Integer> {
int wordLength;
public IntegerListViewAdapter(int wordLength) {
@@ -55,7 +56,8 @@
}
}
- private static class StringListViewAdapter extends AbstractListViewAdapter<String> {
+ private static class StringListViewAdapter extends
+ AbstractListViewAdapter<String> {
String value;
public StringListViewAdapter(final String value) {
@@ -103,17 +105,19 @@
}
};
- private List<HasCell<String, ?, Void>> hasCells =
- new ArrayList<HasCell<String, ?, Void>>();
+ private CompositeCell<String, Void> compositeCell = new CompositeCell<String, Void>();
+ private SelectionModel<String> selectionModel;
- public MyTreeViewModel(final SelectionModel<Object> selectionModel) {
- hasCells.add(new HasCell<String, Boolean, Void>() {
- public boolean dependsOnSelection() {
- return true;
- }
-
+ public MyTreeViewModel(final SelectionModel<String> selectionModel) {
+ this.selectionModel = selectionModel;
+ compositeCell.addHasCell(new HasCell<String, Boolean, Void>() {
public Cell<Boolean, Void> getCell() {
- return new CheckboxCell();
+ return new CheckboxCell() {
+ @Override
+ public boolean dependsOnSelection() {
+ return true;
+ }
+ };
}
public FieldUpdater<String, Boolean, Void> getFieldUpdater() {
@@ -129,11 +133,7 @@
return selectionModel.isSelected(object);
}
});
- hasCells.add(new HasCell<String, String, Void>() {
- public boolean dependsOnSelection() {
- return false;
- }
-
+ compositeCell.addHasCell(new HasCell<String, String, Void>() {
public Cell<String, Void> getCell() {
return ButtonCell.getInstance();
}
@@ -153,7 +153,7 @@
});
}
- public <T> NodeInfo<?> getNodeInfo(T value, TreeNode<T> treeNode) {
+ public <T> NodeInfo<?> getNodeInfo(T value) {
if (value instanceof String) {
return getNodeInfoHelper((String) value);
}
@@ -163,17 +163,20 @@
throw new IllegalArgumentException("Unsupported object type: " + type);
}
- public boolean isLeaf(Object value, TreeNode<?> parentNode) {
+ public boolean isLeaf(Object value) {
return value instanceof Integer;
}
private NodeInfo<?> getNodeInfoHelper(final String value) {
if (value.endsWith("...")) {
- AbstractListViewAdapter<String> adapter = new StringListViewAdapter(value.toString());
- return new DefaultNodeInfo<String>(adapter, hasCells);
+ AbstractListViewAdapter<String> adapter = new StringListViewAdapter(
+ value.toString());
+ return new DefaultNodeInfo<String>(adapter, compositeCell,
+ selectionModel, null);
} else {
- AbstractListViewAdapter<Integer> adapter = new IntegerListViewAdapter(value.length());
- return new DefaultNodeInfo<Integer>(adapter, INTEGER_CELL, false,
+ AbstractListViewAdapter<Integer> adapter = new IntegerListViewAdapter(
+ value.length());
+ return new DefaultNodeInfo<Integer>(adapter, INTEGER_CELL, null,
new ValueUpdater<Integer, Void>() {
public void update(Integer value, Void viewData) {
Window.alert("Integer = " + value);
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SideBySideTreeRecipe.java b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SideBySideTreeRecipe.java
index 8e7e234..5c18d2e 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SideBySideTreeRecipe.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/cookbook/client/SideBySideTreeRecipe.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -15,6 +15,7 @@
*/
package com.google.gwt.sample.bikeshed.cookbook.client;
+import com.google.gwt.bikeshed.list.shared.MultiSelectionModel;
import com.google.gwt.bikeshed.list.shared.SelectionModel;
import com.google.gwt.bikeshed.list.shared.SelectionModel.SelectionChangeEvent;
import com.google.gwt.bikeshed.tree.client.SideBySideTreeView;
@@ -37,7 +38,7 @@
FlowPanel p = new FlowPanel();
final Label label = new Label();
- final MultiSelectionModel<Object> selectionModel = new MultiSelectionModel<Object>();
+ final MultiSelectionModel<String> selectionModel = new MultiSelectionModel<String>();
selectionModel.addSelectionChangeHandler(new SelectionModel.SelectionChangeHandler() {
public void onSelectionChange(SelectionChangeEvent event) {
label.setText("Selected " + selectionModel.getSelectedSet().toString());
@@ -45,8 +46,8 @@
});
SideBySideTreeView sstree = new SideBySideTreeView(new MyTreeViewModel(
- selectionModel), "...", 100, 4);
- sstree.setSelectionModel(selectionModel);
+ selectionModel), "...");
+ sstree.setAnimationEnabled(true);
sstree.setHeight("200px");
p.add(sstree);
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
index cbc7b94..d7e66ee 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/StocksDesktop.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -21,7 +21,6 @@
import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
import com.google.gwt.bikeshed.list.shared.Range;
import com.google.gwt.bikeshed.tree.client.SideBySideTreeView;
-import com.google.gwt.bikeshed.tree.client.TreeNode;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.logical.shared.CloseEvent;
@@ -54,7 +53,8 @@
*/
public class StocksDesktop implements EntryPoint, Updater {
- interface Binder extends UiBinder<Widget, StocksDesktop> { }
+ interface Binder extends UiBinder<Widget, StocksDesktop> {
+ }
private static final Binder binder = GWT.create(Binder.class);
@@ -67,13 +67,19 @@
return NumberFormat.getCurrencyFormat("USD").format(price / 100.0);
}
- @UiField Label cashLabel;
+ @UiField
+ Label cashLabel;
- @UiField FavoritesWidget favoritesWidget;
- @UiField Label netWorthLabel;
- @UiField PlayerScoresWidget playerScoresWidget;
- @UiField StockQueryWidget queryWidget;
- @UiField SideBySideTreeView transactionTree;
+ @UiField
+ FavoritesWidget favoritesWidget;
+ @UiField
+ Label netWorthLabel;
+ @UiField
+ PlayerScoresWidget playerScoresWidget;
+ @UiField
+ StockQueryWidget queryWidget;
+ @UiField
+ SideBySideTreeView transactionTree;
/**
* The popup used to purchase stock.
@@ -84,8 +90,7 @@
private AsyncListViewAdapter<StockQuote> favoritesListViewAdapter;
private AsyncListViewAdapter<PlayerInfo> playerScoresListViewAdapter;
private AsyncListViewAdapter<StockQuote> searchListViewAdapter;
- private Map<String, ListViewAdapter<Transaction>> transactionListViewAdaptersByTicker =
- new HashMap<String, ListViewAdapter<Transaction>>();
+ private Map<String, ListViewAdapter<Transaction>> transactionListViewAdaptersByTicker = new HashMap<String, ListViewAdapter<Transaction>>();
private ListViewAdapter<Transaction> transactionListViewAdapter;
private List<Transaction> transactions;
@@ -145,19 +150,22 @@
// Hook up handlers to columns and the buy/sell popup.
Columns.favoriteColumn.setFieldUpdater(new FieldUpdater<StockQuote, Boolean, Void>() {
- public void update(int index, StockQuote object, Boolean value, Void viewData) {
+ public void update(int index, StockQuote object, Boolean value,
+ Void viewData) {
setFavorite(object.getTicker(), value);
}
});
Columns.buyColumn.setFieldUpdater(new FieldUpdater<StockQuote, String, Void>() {
- public void update(int index, StockQuote quote, String value, Void viewData) {
+ public void update(int index, StockQuote quote, String value,
+ Void viewData) {
buy(quote);
}
});
Columns.sellColumn.setFieldUpdater(new FieldUpdater<StockQuote, String, Void>() {
- public void update(int index, StockQuote quote, String value, Void viewData) {
+ public void update(int index, StockQuote quote, String value,
+ Void viewData) {
sell(quote);
}
});
@@ -176,7 +184,7 @@
/**
* Process the {@link StockResponse} from the server.
- *
+ *
* @param response the stock response
*/
public void processStockResponse(StockResponse response) {
@@ -212,7 +220,7 @@
/**
* Set or unset a ticker symbol as a 'favorite'.
- *
+ *
* @param ticker the ticker symbol
* @param favorite if true, make the stock a favorite
*/
@@ -230,7 +238,8 @@
}
});
} else {
- dataService.removeFavorite(ticker, favoritesListViewAdapter.getRanges()[0],
+ dataService.removeFavorite(ticker,
+ favoritesListViewAdapter.getRanges()[0],
new AsyncCallback<StockResponse>() {
public void onFailure(Throwable caught) {
handleRpcError(caught, "Error removing favorite");
@@ -289,9 +298,7 @@
Range[] searchRanges = searchListViewAdapter.getRanges();
Range[] favoritesRanges = favoritesListViewAdapter.getRanges();
- String sectorName = getSectorName();
- SectorListViewAdapter sectorListViewAdapter = sectorName != null
- ? treeModel.getSectorListViewAdapter(sectorName) : null;
+ SectorListViewAdapter sectorListViewAdapter = treeModel.getSectorListViewAdapter();
Range[] sectorRanges = sectorListViewAdapter == null ? null
: sectorListViewAdapter.getRanges();
@@ -303,8 +310,8 @@
String searchQuery = queryWidget.getSearchQuery();
StockRequest request = new StockRequest(searchQuery,
- sectorListViewAdapter != null ? sectorListViewAdapter.getSector() : null,
- searchRanges[0], favoritesRanges[0], sectorRanges != null
+ sectorListViewAdapter != null ? sectorListViewAdapter.getSector()
+ : null, searchRanges[0], favoritesRanges[0], sectorRanges != null
&& sectorRanges.length > 0 ? sectorRanges[0] : null);
dataService.getStockQuotes(request, new AsyncCallback<StockResponse>() {
public void onFailure(Throwable caught) {
@@ -336,26 +343,15 @@
@UiFactory
SideBySideTreeView createTransactionTree() {
- return new SideBySideTreeView(treeModel, null, 200);
- }
-
- // Hack - walk the transaction tree to find the current viewed sector
- private String getSectorName() {
- int children = transactionTree.getRootNode().getChildCount();
- for (int i = 0; i < children; i++) {
- TreeNode<?> childNode = transactionTree.getRootNode().getChildNode(i);
- if (childNode.isOpen()) {
- return (String) childNode.getValue();
- }
- }
-
- return null;
+ SideBySideTreeView treeView = new SideBySideTreeView(treeModel, null);
+ treeView.setAnimationEnabled(true);
+ return treeView;
}
/**
* Display a message to the user when an RPC call fails.
- *
- * @param caughtbad the exception
+ *
+ * @param caught the exception
* @param displayMessage the message to display to the user, or null to
* display a default message
* @return true if recoverable, false if not
@@ -395,7 +391,7 @@
// Update the sector list.
StockQuoteList sectorList = response.getSector();
if (sectorList != null) {
- SectorListViewAdapter sectorListViewAdapter = treeModel.getSectorListViewAdapter(getSectorName());
+ SectorListViewAdapter sectorListViewAdapter = treeModel.getSectorListViewAdapter();
if (sectorListViewAdapter != null) {
sectorListViewAdapter.updateDataSize(response.getNumSector(), true);
sectorListViewAdapter.updateViewData(sectorList.getStartIndex(),
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/TransactionTreeViewModel.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/TransactionTreeViewModel.java
index c213e10..943370c 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/TransactionTreeViewModel.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/client/TransactionTreeViewModel.java
@@ -1,12 +1,12 @@
/*
* 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
@@ -23,7 +23,8 @@
import com.google.gwt.bikeshed.list.shared.AbstractListViewAdapter;
import com.google.gwt.bikeshed.list.shared.AsyncListViewAdapter;
import com.google.gwt.bikeshed.list.shared.ListViewAdapter;
-import com.google.gwt.bikeshed.tree.client.TreeNode;
+import com.google.gwt.bikeshed.list.shared.ProvidesKey;
+import com.google.gwt.bikeshed.list.shared.SingleSelectionModel;
import com.google.gwt.bikeshed.tree.client.TreeViewModel;
import com.google.gwt.sample.bikeshed.stocks.shared.StockQuote;
import com.google.gwt.sample.bikeshed.stocks.shared.Transaction;
@@ -57,13 +58,9 @@
}
}
- static class TransactionCell extends Cell<Transaction, Void> {
- @Override
- public void render(Transaction value, Void viewData, StringBuilder sb) {
- sb.append(value.toString());
- }
- }
-
+ /**
+ * A {@link Cell} used to render a {@link StockQuote}.
+ */
private static final Cell<StockQuote, Void> STOCK_QUOTE_CELL = new Cell<StockQuote, Void>() {
@Override
public void render(StockQuote value, Void viewData, StringBuilder sb) {
@@ -71,21 +68,52 @@
}
};
- private static final Cell<Transaction, Void> TRANSACTION_CELL = new TransactionCell();
+ /**
+ * A {@link Cell} used to render a {@link Transaction}.
+ */
+ private static final Cell<Transaction, Void> TRANSACTION_CELL = new Cell<Transaction, Void>() {
+ @Override
+ public void render(Transaction value, Void viewData, StringBuilder sb) {
+ sb.append(value.toString());
+ }
+ };
+
+ /**
+ * The last {@link StockQuote} that was opened.
+ */
+ private StockQuote lastStockQuote;
+
+ /**
+ * The last Sector that was opened.
+ */
+ private String lastSector;
private Map<String, SectorListViewAdapter> sectorListViewAdapters = new HashMap<String, SectorListViewAdapter>();
private AbstractListViewAdapter<StockQuote> stockQuoteListViewAdapter;
private ListViewAdapter<String> topLevelListViewAdapter = new ListViewAdapter<String>();
+ private SingleSelectionModel<Object> selectionModel = new SingleSelectionModel<Object>();
private Map<String, ListViewAdapter<Transaction>> transactionListViewAdaptersByTicker;
private Updater updater;
- public TransactionTreeViewModel(Updater updater,
+ public TransactionTreeViewModel(
+ Updater updater,
AbstractListViewAdapter<StockQuote> stockQuoteListViewAdapter,
Map<String, ListViewAdapter<Transaction>> transactionListViewAdaptersByTicker) {
+ this.selectionModel.setKeyProvider(new ProvidesKey<Object>() {
+ public Object getKey(Object item) {
+ if (item instanceof StockQuote) {
+ return StockQuote.KEY_PROVIDER.getKey((StockQuote) item);
+ } else {
+ return item;
+ }
+ }
+ });
this.updater = updater;
this.stockQuoteListViewAdapter = stockQuoteListViewAdapter;
+
+ // Setup the sector list.
List<String> topLevelList = topLevelListViewAdapter.getList();
topLevelList.add("Favorites");
topLevelList.add("Dow Jones Industrials");
@@ -93,60 +121,73 @@
this.transactionListViewAdaptersByTicker = transactionListViewAdaptersByTicker;
}
- public <T> NodeInfo<?> getNodeInfo(T value, final TreeNode<T> treeNode) {
+ public <T> NodeInfo<?> getNodeInfo(T value) {
if (value == null) {
+ // Return list of sectors.
return new TreeViewModel.DefaultNodeInfo<String>(topLevelListViewAdapter,
- TextCell.getInstance(), false);
+ TextCell.getInstance(), selectionModel, null);
} else if ("Favorites".equals(value)) {
- return new TreeViewModel.DefaultNodeInfo<StockQuote>(stockQuoteListViewAdapter,
- STOCK_QUOTE_CELL, false);
+ // Return favorites.
+ return new TreeViewModel.DefaultNodeInfo<StockQuote>(
+ stockQuoteListViewAdapter, STOCK_QUOTE_CELL, selectionModel, null);
} else if ("History".equals(value)) {
- String ticker = ((StockQuote) treeNode.getParentNode().getValue()).getTicker();
+ // Return history of the current stock quote.
+ String ticker = lastStockQuote.getTicker();
ListViewAdapter<Transaction> adapter = transactionListViewAdaptersByTicker.get(ticker);
if (adapter == null) {
adapter = new ListViewAdapter<Transaction>();
transactionListViewAdaptersByTicker.put(ticker, adapter);
}
return new TreeViewModel.DefaultNodeInfo<Transaction>(adapter,
- TRANSACTION_CELL, false);
+ TRANSACTION_CELL, selectionModel, null);
} else if ("Actions".equals(value)) {
+ // Return the actions for the current stock quote.
ListViewAdapter<String> adapter = new ListViewAdapter<String>();
List<String> list = adapter.getList();
list.add("Buy");
list.add("Sell");
return new TreeViewModel.DefaultNodeInfo<String>(adapter,
- ButtonCell.getInstance(), false, new ValueUpdater<String, Void>() {
+ ButtonCell.getInstance(), selectionModel,
+ new ValueUpdater<String, Void>() {
public void update(String value, Void viewData) {
- StockQuote stockQuote = (StockQuote) treeNode.getParentNode().getValue();
if ("Buy".equals(value)) {
- updater.buy(stockQuote);
+ updater.buy(lastStockQuote);
} else {
- updater.sell(stockQuote);
+ updater.sell(lastStockQuote);
}
}
});
} else if (value instanceof String) {
- SectorListViewAdapter adapter = new SectorListViewAdapter((String) value);
- sectorListViewAdapters.put((String) value, adapter);
+ // Return the stocks for a given sector.
+ lastSector = (String) value;
+ SectorListViewAdapter adapter = new SectorListViewAdapter(lastSector);
+ sectorListViewAdapters.put(lastSector, adapter);
return new TreeViewModel.DefaultNodeInfo<StockQuote>(adapter,
- STOCK_QUOTE_CELL, false);
+ STOCK_QUOTE_CELL, selectionModel, null);
} else if (value instanceof StockQuote) {
+ // Return the submenu for a stock quote.
+ lastStockQuote = (StockQuote) value;
ListViewAdapter<String> adapter = new ListViewAdapter<String>();
List<String> list = adapter.getList();
list.add("Actions");
list.add("History");
return new TreeViewModel.DefaultNodeInfo<String>(adapter,
- TextCell.getInstance(), false);
+ TextCell.getInstance(), selectionModel, null);
}
throw new IllegalArgumentException(value.toString());
}
- public SectorListViewAdapter getSectorListViewAdapter(String value) {
- return sectorListViewAdapters.get(value);
+ /**
+ * Get the {@link SectorListViewAdapter} for the last selected sector.
+ *
+ * @return the {@link SectorListViewAdapter}
+ */
+ public SectorListViewAdapter getSectorListViewAdapter() {
+ return lastSector == null ? null : sectorListViewAdapters.get(lastSector);
}
- public boolean isLeaf(Object value, final TreeNode<?> parentNode) {
+ public boolean isLeaf(Object value) {
if (value instanceof Transaction || "Buy".equals(value)
|| "Sell".equals(value)) {
return true;
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
index a9a6bb1..c7db58b 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/stocks/server/StockServiceImpl.java
@@ -360,7 +360,7 @@
}
}
- return new Result(toRet, toRet.size());
+ return new Result(toRet, symbols.size());
}
// If a query is alpha-only ([A-Za-z]+), return stocks for which:
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/Styles.java b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/Styles.java
index 0e823a0..c4d0816 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/Styles.java
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/Styles.java
@@ -20,8 +20,14 @@
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.resources.client.CssResource.NotStrict;
+/**
+ * The Styles used in bikeshed.
+ */
public class Styles {
+ /**
+ * Common styles.
+ */
public interface Common extends CssResource {
String box();
String header();
@@ -31,6 +37,9 @@
String table();
}
+ /**
+ * Shared resources.
+ */
public interface Resources extends ClientBundle {
@NotStrict
@Source("common.css")
diff --git a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/common.css b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/common.css
index 5551e36..5162d1e 100644
--- a/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/common.css
+++ b/bikeshed/src/com/google/gwt/sample/bikeshed/style/client/common.css
@@ -71,18 +71,19 @@
background-color: rgb(56, 117, 215);
}
-/* ss tree */
-.gwt-sstree {
+/** Applies to StandardTreeView **/
+.gwt-tree-selectedItem {
+ background-color: rgb(56, 117, 215);
+}
+
+
+/** Applies to SideBySideTreeView **/
+.gwt-SideBySideTreeView {
}
.gwt-sstree-column {
- overflow-y: scroll;
- overflow-x: auto;
- position: relative;
-}
-
-.gwt-sstree-selectedItem {
- background-color: rgb(56, 117, 215);
+ border-right: 1px solid #aaa;
+ border-left: 1px solid #aaa;
}
.gwt-sstree-evenRow {
@@ -93,6 +94,10 @@
background-color: rgb(220, 220, 220);
}
+.gwt-sstree-selectedItem {
+ background-color: rgb(56, 117, 215);
+}
+
/* date picker */
.gwt-DatePicker {
border: 1px solid #A2BBDD;