Add setting to control whether tree items are scrolled into view when selected.
Adapted from patch http://gwt-code-reviews.appspot.com/1819803/
Credit for the original patch goes to Marko Krajnc (marko.krajnc@cursor.si)

Fixes issue 2467.

Review by: mdempsky@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11465 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/Tree.java b/user/src/com/google/gwt/user/client/ui/Tree.java
index a83796c..5d062a5 100644
--- a/user/src/com/google/gwt/user/client/ui/Tree.java
+++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -243,6 +243,8 @@
 
   private TreeItem root;
 
+  private boolean scrollOnSelectEnabled = true;
+  
   private boolean useLeafImages;
 
   /**
@@ -635,6 +637,13 @@
     return isAnimationEnabled;
   }
 
+  /**
+   * Determines whether selecting a tree item will scroll it into view.
+   */
+  public boolean isScrollOnSelectEnabled() {
+    return scrollOnSelectEnabled;
+  }
+  
   @Override
   public Iterator<Widget> iterator() {
     final Widget[] widgets = new Widget[childWidgets.size()];
@@ -853,6 +862,14 @@
   }
 
   /**
+   * Enable or disable scrolling a tree item into view when it is selected. Scrolling into view is
+   * enabled by default.
+   */
+  public void setScrollOnSelectEnabled(boolean enable) {
+    scrollOnSelectEnabled = enable;
+  }
+  
+  /**
    * Selects a specified item.
    *
    * @param item the item to be selected, or <code>null</code> to deselect all
@@ -1221,35 +1238,39 @@
     Focusable focusableWidget = curSelection.getFocusable();
     if (focusableWidget != null) {
       focusableWidget.setFocus(true);
-      DOM.scrollIntoView(((Widget) focusableWidget).getElement());
-    } else {
-      // Get the location and size of the given item's content element relative
-      // to the tree.
-      Element selectedElem = curSelection.getContentElem();
-      int containerLeft = getAbsoluteLeft();
-      int containerTop = getAbsoluteTop();
-
-      int left = DOM.getAbsoluteLeft(selectedElem) - containerLeft;
-      int top = DOM.getAbsoluteTop(selectedElem) - containerTop;
-      int width = DOM.getElementPropertyInt(selectedElem, "offsetWidth");
-      int height = DOM.getElementPropertyInt(selectedElem, "offsetHeight");
-
-      // If the item is not visible, quite here
-      if (width == 0 || height == 0) {
-        DOM.setIntStyleAttribute(focusable, "left", 0);
-        DOM.setIntStyleAttribute(focusable, "top", 0);
-        return;
+      if (scrollOnSelectEnabled) {
+        DOM.scrollIntoView(((Widget) focusableWidget).getElement());
       }
+    } else {
+      if (scrollOnSelectEnabled) {
+        // Get the location and size of the given item's content element relative
+        // to the tree.
+        Element selectedElem = curSelection.getContentElem();
+        int containerLeft = getAbsoluteLeft();
+        int containerTop = getAbsoluteTop();
+  
+        int left = DOM.getAbsoluteLeft(selectedElem) - containerLeft;
+        int top = DOM.getAbsoluteTop(selectedElem) - containerTop;
+        int width = DOM.getElementPropertyInt(selectedElem, "offsetWidth");
+        int height = DOM.getElementPropertyInt(selectedElem, "offsetHeight");
 
-      // Set the focusable element's position and size to exactly underlap the
-      // item's content element.
-      DOM.setStyleAttribute(focusable, "left", left + "px");
-      DOM.setStyleAttribute(focusable, "top", top + "px");
-      DOM.setStyleAttribute(focusable, "width", width + "px");
-      DOM.setStyleAttribute(focusable, "height", height + "px");
-
-      // Scroll it into view.
-      DOM.scrollIntoView(focusable);
+        // If the item is not visible, quite here
+        if (width == 0 || height == 0) {
+          DOM.setIntStyleAttribute(focusable, "left", 0);
+          DOM.setIntStyleAttribute(focusable, "top", 0);
+          return;
+        }
+  
+        // Set the focusable element's position and size to exactly underlap the
+        // item's content element.
+        DOM.setStyleAttribute(focusable, "left", left + "px");
+        DOM.setStyleAttribute(focusable, "top", top + "px");
+        DOM.setStyleAttribute(focusable, "width", width + "px");
+        DOM.setStyleAttribute(focusable, "height", height + "px");
+  
+        // Scroll it into view.
+        DOM.scrollIntoView(focusable);
+      }
 
       // Update ARIA attributes to reflect the information from the
       // newly-selected item.
diff --git a/user/test/com/google/gwt/user/client/ui/TreeTest.java b/user/test/com/google/gwt/user/client/ui/TreeTest.java
index 212b901..dfcda3a 100644
--- a/user/test/com/google/gwt/user/client/ui/TreeTest.java
+++ b/user/test/com/google/gwt/user/client/ui/TreeTest.java
@@ -15,6 +15,8 @@
  */
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.junit.DoNotRunWith;
+import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.safehtml.shared.SafeHtmlUtils;
 import com.google.gwt.user.client.DOM;
@@ -344,6 +346,55 @@
       // Expected.
     }
   }
+  
+  @DoNotRunWith(Platform.HtmlUnitLayout)
+  public void testScrollOnSelectEnabledFalse() {
+    // With scrolling disabled.
+    Tree tree = createTree();
+    tree.setScrollOnSelectEnabled(false);
+    assertScrollingOnSelection(tree, false);
+  }
+
+  @DoNotRunWith(Platform.HtmlUnitLayout)
+  public void testScrollOnSelectEnabledTrue() {
+    // With scrolling enabled (default)
+    Tree tree = createTree();
+    assertTrue(tree.isScrollOnSelectEnabled());
+    assertScrollingOnSelection(tree, true);
+  }
+
+  private void assertScrollingOnSelection(Tree tree, boolean shouldScroll) {
+    tree.addItem(new Label("hello1"));
+    tree.addItem(new Label("hello2"));
+    TreeItem levelZeroTreeItem = tree.addItem(new Label("level0"));
+    TreeItem selectedItem = levelZeroTreeItem.addItem(new Label("level1"));
+    selectedItem.addItem(SafeHtmlUtils.fromString("level2"));
+
+    // For the tree to be opened. Otherwise, all sizes will be zero and no scrolling would occur,
+    // regardless of the mode.
+    levelZeroTreeItem.setState(true);
+    selectedItem.setState(true);
+
+    ScrollPanel panel = new ScrollPanel();
+    RootPanel.get().add(panel);
+    panel.setWidget(tree);
+    
+    // Set a size that is smaller than the content to allow scrolling
+    panel.setPixelSize(40, 90);
+
+    assertEquals(0, panel.getVerticalScrollPosition());
+    assertEquals(0, panel.getHorizontalScrollPosition());
+
+    tree.setSelectedItem(selectedItem);
+
+    if (shouldScroll) {
+      assertTrue("Expected vertical scroll", panel.getVerticalScrollPosition() != 0);
+      assertTrue("Expected horizontal scroll", panel.getHorizontalScrollPosition() != 0);
+    } else {
+      assertEquals("Expected no vertical scroll", 0, panel.getVerticalScrollPosition());
+      assertEquals("Expected no horizontal scroll", 0, panel.getHorizontalScrollPosition());
+    }
+  }
 
   public void testSwap() {
     Tree t = createTree();