Revision: 9408
Author: [email protected]
Date: Mon Dec 13 13:39:16 2010
Log: Cherry picking r9403 into release branch.

http://code.google.com/p/google-web-toolkit/source/detail?r=9408

Modified:
/releases/2.1/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ShowMorePagerPanel.java /releases/2.1/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java /releases/2.1/user/test/com/google/gwt/user/cellview/client/AbstractCellTreeTestBase.java /releases/2.1/user/test/com/google/gwt/user/cellview/client/CellBrowserTest.java /releases/2.1/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java

=======================================
--- /releases/2.1/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ShowMorePagerPanel.java Tue Aug 17 10:14:36 2010 +++ /releases/2.1/samples/showcase/src/com/google/gwt/sample/showcase/client/content/cell/ShowMorePagerPanel.java Mon Dec 13 13:39:16 2010
@@ -54,6 +54,9 @@
   public ShowMorePagerPanel() {
     initWidget(scrollable);

+    // Do not let the scrollable take tab focus.
+    scrollable.getElement().setTabIndex(-1);
+
     // Handle scroll events.
     scrollable.addScrollHandler(new ScrollHandler() {
       public void onScroll(ScrollEvent event) {
=======================================
--- /releases/2.1/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java Wed Nov 24 05:29:07 2010 +++ /releases/2.1/user/src/com/google/gwt/user/cellview/client/HasDataPresenter.java Mon Dec 13 13:39:16 2010
@@ -181,6 +181,8 @@
     boolean rowCountIsExact = false;
     final List<T> rowData = new ArrayList<T>();
     final Set<Integer> selectedRows = new HashSet<Integer>();
+    T selectedValue = null;
+    boolean viewTouched;

     public DefaultState(int pageSize) {
       this.pageSize = pageSize;
@@ -217,6 +219,10 @@
     public List<T> getRowDataValues() {
       return Collections.unmodifiableList(rowData);
     }
+
+    public T getSelectedValue() {
+      return selectedValue;
+    }

     public boolean isRowCountExact() {
       return rowCountIsExact;
@@ -233,6 +239,10 @@
     public boolean isRowSelected(int index) {
       return selectedRows.contains(index);
     }
+
+    public boolean isViewTouched() {
+      return viewTouched;
+    }
   }

   /**
@@ -271,6 +281,8 @@
       this.pageStart = state.getPageStart();
       this.rowCount = state.getRowCount();
       this.rowCountIsExact = state.isRowCountExact();
+      this.selectedValue = state.getSelectedValue();
+      this.viewTouched = state.isViewTouched();

       // Copy the row data.
       int rowDataSize = state.getRowDataSize();
@@ -342,6 +354,11 @@
      */
     List<T> getRowDataValues();

+    /**
+     * Get the value that is selected in the {...@link SelectionModel}.
+     */
+    T getSelectedValue();
+
     /**
* Get a boolean indicating whether the row count is exact or an estimate.
      */
@@ -354,6 +371,13 @@
      * @return true if selected, false if not
      */
     boolean isRowSelected(int index);
+
+    /**
+ * Check if the user interacted with the view at some point. Selection is + * not bound to the keyboard selected row until the view is touched. Once
+     * touched, selection is bound from then on.
+     */
+    boolean isViewTouched();
   }

   /**
@@ -727,10 +751,13 @@
     if (KeyboardSelectionPolicy.DISABLED == keyboardSelectionPolicy) {
       return;
     }
+
+    // The user touched the view.
+    ensurePendingState().viewTouched = true;

     /*
* Early exit if the keyboard selected row has not changed and the keyboard
-     * selected value is already set.
+     * selected value is already set.
      */
     if (!forceUpdate && getKeyboardSelectedRow() == index
         && getKeyboardSelectedRowValue() != null) {
@@ -1162,24 +1189,38 @@

     /*
* Update the SelectionModel based on the keyboard selected value. This must
-     * happen before we read the selection state.
+     * happen before we read the selection state. We only bind to selection
+     * after the user has interacted with the widget at least once. This
+     * prevents values from being selected by default.
      */
if (KeyboardSelectionPolicy.BOUND_TO_SELECTION == keyboardSelectionPolicy
-        && selectionModel != null) {
-      T oldValue = oldState.getRowDataSize() > 0
- ? oldState.getRowDataValue(oldState.getKeyboardSelectedRow()) : null;
+        && selectionModel != null && pending.viewTouched) {
+      T oldValue = oldState.getSelectedValue();
       Object oldKey = getRowValueKey(oldValue);
       T newValue = rowDataCount > 0
? pending.getRowDataValue(pending.getKeyboardSelectedRow()) : null;
       Object newKey = getRowValueKey(newValue);
-      if ((oldKey == null) ? newKey != null : !oldKey.equals(newKey)) {
+      /*
+ * Do not deselect the old value unless we have a new value to select, or + * we will have a null selection event while we wait for asynchronous data
+       * to load.
+       */
+      if (newKey != null && !newKey.equals(oldKey)) {
+        // Check both values for selection before setting selection, or the
+        // selection model may resolve state early.
+        boolean oldValueWasSelected = (oldValue == null) ? false
+            : selectionModel.isSelected(oldValue);
+        boolean newValueWasSelected = (newValue == null) ? false
+            : selectionModel.isSelected(newValue);
+
         // Deselect the old value.
-        if (oldValue != null && selectionModel.isSelected(oldValue)) {
+        if (oldValueWasSelected) {
           selectionModel.setSelected(oldValue, false);
         }

         // Select the new value.
-        if (newValue != null && !selectionModel.isSelected(newValue)) {
+        pending.selectedValue = newValue;
+        if (newValue != null && !newValueWasSelected) {
           selectionModel.setSelected(newValue, true);
         }
       }
=======================================
--- /releases/2.1/user/test/com/google/gwt/user/cellview/client/AbstractCellTreeTestBase.java Wed Dec 1 07:51:56 2010 +++ /releases/2.1/user/test/com/google/gwt/user/cellview/client/AbstractCellTreeTestBase.java Mon Dec 13 13:39:16 2010
@@ -27,6 +27,7 @@
 import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
 import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.view.client.ListDataProvider;
+import com.google.gwt.view.client.MultiSelectionModel;
 import com.google.gwt.view.client.ProvidesKey;
 import com.google.gwt.view.client.TreeViewModel;

@@ -49,6 +50,8 @@
    */
   protected class MockTreeViewModel implements TreeViewModel {

+    private static final int MAX_DEPTH = 4;
+
     /**
      * The cell used to render all nodes in the tree.
      */
@@ -58,17 +61,31 @@
      * The root data provider.
      */
private final ListDataProvider<String> rootDataProvider = createDataProvider("");
+
+    /**
+     * The selection models at each level of the tree.
+     */
+ private final List<MultiSelectionModel<String>> selectionModels = new ArrayList<MultiSelectionModel<String>>();
+
+    public MockTreeViewModel() {
+      for (int i = 0; i < MAX_DEPTH; i++) {
+        selectionModels.add(new MultiSelectionModel<String>());
+      }
+    }

     public <T> NodeInfo<?> getNodeInfo(T value) {
       if (value == ROOT_VALUE) {
-        return new DefaultNodeInfo<String>(rootDataProvider, cell);
+        return new DefaultNodeInfo<String>(rootDataProvider, cell,
+            selectionModels.get(0), null);
       } else if (value instanceof String) {
         String prefix = (String) value;
-        if (prefix.length() > 3) {
-          throw new IllegalStateException(
-              "Prefix should never exceed four characters.");
-        }
- return new DefaultNodeInfo<String>(createDataProvider(prefix), cell);
+        int depth = prefix.length();
+        if (depth >= MAX_DEPTH) {
+          throw new IllegalStateException("Prefix should never exceed "
+              + MAX_DEPTH + " characters.");
+        }
+ return new DefaultNodeInfo<String>(createDataProvider(prefix), cell,
+            selectionModels.get(depth), null);
       }
       throw new IllegalArgumentException("Unrecognized value type");
     }
@@ -77,12 +94,12 @@
       if (value == ROOT_VALUE) {
         return false;
       } else if (value instanceof String) {
-        String s = (String) value;
-        if (s.length() > 4) {
+        int depth = ((String) value).length();
+        if (depth > MAX_DEPTH) {
           throw new IllegalStateException(
               "value should never exceed five characters.");
         }
-        return ((String) value).length() == 4;
+        return depth == MAX_DEPTH;
       }
       throw new IllegalArgumentException("Unrecognized value type");
     }
@@ -90,6 +107,16 @@
     public ListDataProvider<String> getRootDataProvider() {
       return rootDataProvider;
     }
+
+    /**
+ * Get the {...@link MultiSelectionModel} for the nodes at the specified depth.
+     *
+     * @param depth the depth of the node
+     * @return the {...@link MultiSelectionModel} at that depth
+     */
+    public MultiSelectionModel<String> getSelectionModel(int depth) {
+      return selectionModels.get(depth);
+    }

     /**
      * Create a data provider that extends the prefix by one letter.
=======================================
--- /releases/2.1/user/test/com/google/gwt/user/cellview/client/CellBrowserTest.java Wed Nov 10 04:31:36 2010 +++ /releases/2.1/user/test/com/google/gwt/user/cellview/client/CellBrowserTest.java Mon Dec 13 13:39:16 2010
@@ -117,6 +117,37 @@
     assertEquals(2, browser.treeNodes.get(0).getFocusedKey());
     assertTrue(browser.treeNodes.get(0).isFocusedOpen());
   }
+
+  /**
+ * Test that even when keyboard selection is bound to the selection model, we + * do not automatically select items in child lists until the child list is
+   * actually touched.
+   */
+  public void testSetKeyboardSelectionPolicyBound() {
+    CellBrowser browser = (CellBrowser) tree;
+
+    // Bind keyboard selection to the selection model.
+ browser.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.BOUND_TO_SELECTION);
+    assertEquals(KeyboardSelectionPolicy.BOUND_TO_SELECTION,
+        browser.getKeyboardSelectionPolicy());
+
+    // Select an item at depth 0. Nothing should be selected at depth 1.
+    BrowserCellList<?> list0 = browser.treeNodes.get(0).getDisplay();
+    list0.getPresenter().setKeyboardSelectedRow(1, false, false);
+    list0.getPresenter().flush();
+    browser.treeNodes.get(1).getDisplay().getPresenter().flush();
+    assertEquals(1, model.getSelectionModel(0).getSelectedSet().size());
+    assertEquals(0, model.getSelectionModel(1).getSelectedSet().size());
+
+    // Select an item at depth 1. Nothing should be selected at depth 2.
+    BrowserCellList<?> list1 = browser.treeNodes.get(1).getDisplay();
+    list1.getPresenter().setKeyboardSelectedRow(2, false, false);
+    list1.getPresenter().flush();
+    browser.treeNodes.get(2).getDisplay().getPresenter().flush();
+    assertEquals(1, model.getSelectionModel(0).getSelectedSet().size());
+    assertEquals(1, model.getSelectionModel(1).getSelectedSet().size());
+    assertEquals(0, model.getSelectionModel(2).getSelectedSet().size());
+  }

   public void testSetKeyboardSelectionPolicyDisabled() {
     CellBrowser browser = (CellBrowser) tree;
=======================================
--- /releases/2.1/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java Wed Nov 24 05:29:07 2010 +++ /releases/2.1/user/test/com/google/gwt/user/cellview/client/HasDataPresenterTest.java Mon Dec 13 13:39:16 2010
@@ -31,6 +31,7 @@
 import com.google.gwt.view.client.MockHasData.MockRangeChangeHandler;
 import com.google.gwt.view.client.MockHasData.MockRowCountChangeHandler;
 import com.google.gwt.view.client.MockSelectionModel;
+import com.google.gwt.view.client.ProvidesKey;
 import com.google.gwt.view.client.Range;
 import com.google.gwt.view.client.RangeChangeEvent;
 import com.google.gwt.view.client.SelectionChangeEvent;
@@ -45,6 +46,49 @@
  * Tests for {...@link HasDataPresenter}.
  */
 public class HasDataPresenterTest extends GWTTestCase {
+
+  /**
+   * A mock {...@link SelectionChangeEvent.Handler} used for testing.
+   */
+  private static class MockSelectionChangeHandler implements
+      SelectionChangeEvent.Handler {
+
+    private boolean eventFired;
+
+    /**
+     * Assert that a {...@link SelectionChangeEvent} was fired and clear the
+     * boolean.
+     *
+     * @param expected the expected value
+     */
+    public void assertEventFired(boolean expected) {
+      assertEquals(expected, eventFired);
+      eventFired = false;
+    }
+
+    public void onSelectionChange(SelectionChangeEvent event) {
+      assertFalse(eventFired);
+      eventFired = true;
+    }
+  }
+
+  /**
+ * A mock {...@link SelectionModel} used for testing without used any GWT client
+   * code.
+   *
+   * @param <T> the selection type
+   */
+ private class MockSingleSelectionModel<T> extends SingleSelectionModel<T> {
+
+    public MockSingleSelectionModel(ProvidesKey<T> keyProvider) {
+      super(keyProvider);
+    }
+
+    @Override
+    public void fireSelectionChangeEvent() {
+      super.fireSelectionChangeEvent();
+    }
+  }

   /**
    * A mock view used for testing.
@@ -794,6 +838,16 @@
     presenter.flush();
     assertEquals(0, model.getSelectedSet().size());

+ // Clear the data and push new data. This should not select an item because
+    // there has not been any user interaction.
+    presenter.setRowCount(0, true);
+    presenter.flush();
+    presenter.setRowCount(100, false);
+    presenter.flush();
+    populatePresenter(presenter);
+    presenter.flush();
+    assertEquals(0, model.getSelectedSet().size());
+
     // Select an element.
     presenter.setKeyboardSelectedRow(5, false, false);
     presenter.flush();
@@ -809,14 +863,71 @@
     // Select an element on another page.
     presenter.setKeyboardSelectedRow(11, false, false);
     presenter.flush();
-    // Nothing is selected yet because we don't have data.
-    assertEquals(0, model.getSelectedSet().size());
+    // The previous value is still selected because we don't have new data.
+    assertEquals(1, model.getSelectedSet().size());
+    assertTrue(model.isSelected("test 9"));
     populatePresenter(presenter);
     presenter.flush();
     // Once data is pushed, the selection model should be populated.
     assertEquals(1, model.getSelectedSet().size());
     assertTrue(model.isSelected("test 10"));
   }
+
+  /**
+ * Test that we only get one selection event when keyboard selection changes.
+   */
+  public void testSetKeyboardSelectedRowFiresOneSelectionEvent() {
+    HasData<String> listView = new MockHasData<String>();
+    MockView<String> view = new MockView<String>();
+ HasDataPresenter<String> presenter = new HasDataPresenter<String>(listView,
+        view, 10, null);
+    presenter.setVisibleRange(new Range(0, 10));
+    populatePresenter(presenter);
+    presenter.flush();
+
+    // Bind keyboard selection to the selection model.
+ presenter.setKeyboardSelectionPolicy(KeyboardSelectionPolicy.BOUND_TO_SELECTION);
+    presenter.setKeyboardPagingPolicy(KeyboardPagingPolicy.CHANGE_PAGE);
+
+    // Add a selection model.
+ MockSingleSelectionModel<String> model = new MockSingleSelectionModel<String>(
+        null);
+    presenter.setSelectionModel(model);
+    presenter.flush();
+    assertNull(model.getSelectedObject());
+
+    // Add an selection event handler.
+    MockSelectionChangeHandler handler = new MockSelectionChangeHandler();
+    model.addSelectionChangeHandler(handler);
+
+    // Select an element.
+    presenter.setKeyboardSelectedRow(5, false, false);
+    presenter.flush();
+    model.fireSelectionChangeEvent();
+    handler.assertEventFired(true);
+    assertEquals("test 5", model.getSelectedObject());
+
+    // Select another element.
+    presenter.setKeyboardSelectedRow(9, false, false);
+    presenter.flush();
+    model.fireSelectionChangeEvent();
+    handler.assertEventFired(true);
+    assertEquals("test 9", model.getSelectedObject());
+
+    // Select an element on another page.
+    presenter.setKeyboardSelectedRow(11, false, false);
+    presenter.flush();
+    model.fireSelectionChangeEvent();
+    // The previous value is still selected because we don't have new data.
+    handler.assertEventFired(false);
+    assertEquals("test 9", model.getSelectedObject());
+    populatePresenter(presenter);
+    presenter.flush();
+    model.fireSelectionChangeEvent();
+    // Once data is pushed, the selection model should be populated.
+    assertEquals("test 10", model.getSelectedObject());
+    handler.assertEventFired(true);
+  }

   public void testSetKeyboardSelectedRowChangePage() {
     HasData<String> listView = new MockHasData<String>();

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to