Intruducing AutoDirHandler, a handler for automatically adjusting the direction
of an object (typically a TextBox variant) while text is being entered. This
handler is then used to add BiDi support to TextBox and TextArea.

Review at http://gwt-code-reviews.appspot.com/386803

Review by: jat@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8286 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwBasicText.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwBasicText.java
index d6979c8..7533b09 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwBasicText.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/text/CwBasicText.java
@@ -22,6 +22,7 @@
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.event.dom.client.KeyUpHandler;
 import com.google.gwt.i18n.client.Constants;
+import com.google.gwt.i18n.shared.AnyRtlDirectionEstimator;
 import com.google.gwt.sample.showcase.client.ContentWidget;
 import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseData;
 import com.google.gwt.sample.showcase.client.ShowcaseAnnotations.ShowcaseSource;
@@ -103,6 +104,10 @@
     // Add a normal and disabled text box
     TextBox normalText = new TextBox();
     normalText.ensureDebugId("cwBasicText-textbox");
+    // Set the normal text box to automatically adjust its direction according
+    // to the input text. Use the Any-RTL heuristic, which sets an RTL direction
+    // iff the text contains at least one RTL character.
+    normalText.setDirectionEstimator(AnyRtlDirectionEstimator.get());
     TextBox disabledText = new TextBox();
     disabledText.ensureDebugId("cwBasicText-textbox-disabled");
     disabledText.setText(constants.cwBasicTextReadOnly());
diff --git a/user/src/com/google/gwt/i18n/I18N.gwt.xml b/user/src/com/google/gwt/i18n/I18N.gwt.xml
index 23d1c29..9d0c77a 100644
--- a/user/src/com/google/gwt/i18n/I18N.gwt.xml
+++ b/user/src/com/google/gwt/i18n/I18N.gwt.xml
@@ -17,7 +17,7 @@
 <module>
   <inherits name="com.google.gwt.regexp.RegExp"/>
   <source path="" includes="client/,shared/" />
-  
+
   <!-- Browser-sensitive code should use the 'locale' client property. -->
   <!-- 'default' is always defined.                                    -->
   <define-property name="locale" values="default" />
@@ -54,7 +54,7 @@
       } else {
         $wnd['__gwt_Locale'] = locale || defaultLocale;
       }
-      
+
       if (locale == null) {
         return defaultLocale;
       }
@@ -95,7 +95,7 @@
       the locale of the current permutation will actually be included.  Note
       that currently only number/date format constants, locale names, and
       currency data will support runtime locales - everything else will just
-      reference the compile-time locale set in the "locale" property. 
+      reference the compile-time locale set in the "locale" property.
    -->
   <define-configuration-property name="runtime.locales" is-multi-valued="true"/>
   <set-configuration-property name="runtime.locales" value=""/>
@@ -111,4 +111,17 @@
       be generated as the locale specified here.
   -->
   <set-property-fallback name="locale" value="default"/>
+
+  <!-- Force BiDi policy to be enabled -->
+  <define-property name="gwt.forceBidi" values="true, false"/>
+
+  <!-- Default to not forced -->
+  <set-property name="gwt.forceBidi" value="false"/>
+
+  <!-- Replace the BidiPolicyImpl -->
+  <replace-with class="com.google.gwt.i18n.client.BidiPolicy.BidiPolicyImplOn">
+    <when-type-is class="com.google.gwt.i18n.client.BidiPolicy.BidiPolicyImpl"/>
+    <when-property-is name="gwt.forceBidi" value="true"/>
+  </replace-with>
 </module>
+
diff --git a/user/src/com/google/gwt/i18n/client/AutoDirectionHandler.java b/user/src/com/google/gwt/i18n/client/AutoDirectionHandler.java
new file mode 100644
index 0000000..09ad8b7
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/AutoDirectionHandler.java
@@ -0,0 +1,180 @@
+/*
+ * 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.i18n.client;
+
+import com.google.gwt.event.dom.client.HasKeyUpHandlers;
+import com.google.gwt.event.dom.client.KeyUpEvent;
+import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.i18n.client.HasDirection.Direction;
+import com.google.gwt.i18n.shared.DirectionEstimator;
+import com.google.gwt.i18n.shared.HasDirectionEstimator;
+import com.google.gwt.i18n.shared.WordCountDirectionEstimator;
+import com.google.gwt.user.client.ui.HasText;
+
+/**
+ * Utility class for handling auto-direction adjustment.
+ *
+ * This class is useful for automatically adjusting the direction of an object
+ * that takes text input, while the text is being entered.
+ */
+public class AutoDirectionHandler implements KeyUpHandler,
+    HasDirectionEstimator {
+
+  /**
+   * The interface an object must implement in order to add an
+   * AutoDirectionHandler to it.
+   *
+   * TODO(tomerigo): add Paste and Input events once they're available in GWT.
+   */
+  public interface Target extends HasText, HasDirection, HasKeyUpHandlers {
+  }
+
+  /**
+   * Operates like {@link #addTo(Target, DirectionEstimator)}, but always uses
+   * a default DirectionEstimator, {@link
+   * com.google.gwt.i18n.shared.WordCountDirectionEstimator}.
+   *
+   * @param target Object whose direction should be automatically adjusted on
+   *     relevant events.
+   * @return AutoDirectionHandler An instance of AutoDirectionHandler for the
+   *     given object.
+   */
+  public static AutoDirectionHandler addTo(Target target) {
+    return addTo(target, true);
+  }
+
+  /**
+   * Operates like {@link #addTo(Target, DirectionEstimator)}, but uses a
+   * default DirectionEstimator, {@link
+   * com.google.gwt.i18n.shared.WordCountDirectionEstimator} if {@code enabled},
+   * or else a null DirectionEstimator, which means disabling direction
+   * estimation.
+   *
+   * @param target Object whose direction should be automatically adjusted on
+   *     relevant events.
+   * @param enabled Whether the handler is enabled upon creation.
+   * @return AutoDirectionHandler An instance of AutoDirectionHandler for the
+   *     given object.
+   */
+  public static AutoDirectionHandler addTo(Target target, boolean enabled) {
+    return addTo(target, enabled ? WordCountDirectionEstimator.get() : null);
+  }
+
+  /**
+   * Adds auto-direction adjustment to a given object:
+   * - Creates an AutoDirectionHandler.
+   * - Initializes it with the given DirectionEstimator.
+   * - Adds it as an event handler for the relevant events on the given object.
+   * - Returns the AutoDirectionHandler, so its setAutoDir() method can be
+   * called when the object's text changes by means other than the handled
+   * events.
+   *
+   * @param target Object whose direction should be automatically adjusted on
+   *     relevant events.
+   * @param directionEstimator A DirectionEstimator object used for direction
+   *     estimation (use null to disable direction estimation).
+   * @return AutoDirectionHandler An instance of AutoDirectionHandler for the
+   *     given object.
+   */
+  public static AutoDirectionHandler addTo(Target target, DirectionEstimator
+      directionEstimator) {
+    AutoDirectionHandler autoDirHandler = new AutoDirectionHandler(target,
+        directionEstimator);
+    return autoDirHandler;
+  }
+
+  /**
+   * A DirectionEstimator object used for direction estimation.
+   */
+  private DirectionEstimator directionEstimator;
+
+  /**
+   * A HandlerRegistration object used to remove this handler.
+   */
+  private HandlerRegistration handlerRegistration;
+
+  /**
+   * The object being handled.
+   */
+  private Target target;
+
+  /**
+   * Private constructor. Instantiate using one of the addTo() methods.
+   *
+   * @param target Object whose direction should be automatically adjusted on
+   *     relevant events.
+   * @param directionEstimator A DirectionEstimator object used for direction
+   *     estimation.
+   */
+  private AutoDirectionHandler(Target target, DirectionEstimator
+      directionEstimator) {
+    this.target = target;
+    this.handlerRegistration = null;
+    setDirectionEstimator(directionEstimator);
+  }
+
+  /**
+   * Returns the DirectionEstimator object.
+   */
+  public DirectionEstimator getDirectionEstimator() {
+    return directionEstimator;
+  }
+
+  /**
+   * Automatically adjusts hasDirection's direction on KeyUpEvent events.
+   * Implementation of KeyUpHandler interface method.
+   */
+  public void onKeyUp(KeyUpEvent event) {
+    refreshDirection();
+  }
+
+  /**
+   * Adjusts target's direction according to the estimated direction of the text
+   * it supplies.
+   */
+  public void refreshDirection() {
+    if (directionEstimator != null) {
+      Direction dir = directionEstimator.estimateDirection(target.getText());
+      if (dir != target.getDirection()) {
+        target.setDirection(dir);
+      }
+    }
+  }
+
+  /**
+   * Toggles direction estimation on (using a default estimator) and off.
+   */
+  public void setDirectionEstimator(boolean enabled) {
+    setDirectionEstimator(enabled ? WordCountDirectionEstimator.get() : null);
+  }
+
+  /**
+   * Sets the DirectionEstimator object.
+   */
+  public void setDirectionEstimator(DirectionEstimator directionEstimator) {
+    this.directionEstimator = directionEstimator;
+    if ((directionEstimator == null) != (handlerRegistration == null)) {
+      if (directionEstimator == null) {
+        handlerRegistration.removeHandler();
+        handlerRegistration = null;
+      } else {
+        handlerRegistration = target.addKeyUpHandler(this);
+      }
+    }
+    refreshDirection();
+  }
+}
diff --git a/user/src/com/google/gwt/i18n/client/BidiPolicy.java b/user/src/com/google/gwt/i18n/client/BidiPolicy.java
new file mode 100644
index 0000000..5a0427e
--- /dev/null
+++ b/user/src/com/google/gwt/i18n/client/BidiPolicy.java
@@ -0,0 +1,52 @@
+/*
+ * 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.i18n.client;
+
+import com.google.gwt.core.client.GWT;
+
+/**
+ * Provides low-level functionality to determine whether to support bidi.
+ */
+public class BidiPolicy {
+  /**
+   * Implementation class for {@link BidiPolicy}.
+   */
+  public static class BidiPolicyImpl {
+    public boolean isBidiEnabled() {
+      return LocaleInfo.hasAnyRTL();
+    }
+  }
+
+  /**
+   * Implementation class for {@link BidiPolicy} used when bidi is always on.
+   */
+  @SuppressWarnings("unused")
+  public static class BidiPolicyImplOn extends BidiPolicyImpl {
+    @Override
+    public boolean isBidiEnabled() {
+      return true;
+    }
+  }
+
+  private static BidiPolicyImpl impl = GWT.create(BidiPolicyImpl.class);
+
+  /**
+   * @return true if bidi is enabled, false if disabled.
+   */
+  public static boolean isBidiEnabled() {
+    return impl.isBidiEnabled();
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/TextArea.java b/user/src/com/google/gwt/user/client/ui/TextArea.java
index 09c7fff..0cd79a4 100644
--- a/user/src/com/google/gwt/user/client/ui/TextArea.java
+++ b/user/src/com/google/gwt/user/client/ui/TextArea.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2008 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,36 +18,34 @@
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.TextAreaElement;
-import com.google.gwt.i18n.client.HasDirection;
-import com.google.gwt.i18n.client.BidiUtils;
 
 /**
  * A text box that allows multiple lines of text to be entered.
- * 
+ *
  * <p>
  * <img class='gallery' src='doc-files/TextArea.png'/>
  * </p>
- * 
+ *
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-TextArea { primary style }</li>
  * <li>.gwt-TextArea-readonly { dependent style set when the text area is read-only }</li>
  * </ul>
- * 
+ *
  * <p>
  * <h3>Example</h3> {@example com.google.gwt.examples.TextBoxExample}
  * </p>
  */
-public class TextArea extends TextBoxBase implements HasDirection {
+public class TextArea extends TextBoxBase {
 
   /**
    * Creates a TextArea widget that wraps an existing &lt;textarea&gt;
    * element.
-   * 
+   *
    * This element must already be attached to the document. If the element is
    * removed from the document, you must call
    * {@link RootPanel#detachNow(Widget)}.
-   * 
+   *
    * @param element the element to be wrapped
    */
   public static TextArea wrap(Element element) {
@@ -74,7 +72,7 @@
   /**
    * This constructor may be used by subclasses to explicitly use an existing
    * element. This element must be a &lt;textarea&gt; element.
-   * 
+   *
    * @param element the element to be used
    */
   protected TextArea(Element element) {
@@ -85,7 +83,7 @@
   /**
    * Gets the requested width of the text box (this is not an exact value, as
    * not all characters are created equal).
-   * 
+   *
    * @return the requested width, in characters
    */
   public int getCharacterWidth() {
@@ -97,10 +95,6 @@
     return getImpl().getTextAreaCursorPos(getElement());
   }
 
-  public Direction getDirection() {
-    return BidiUtils.getDirectionOnElement(getElement());
-  }
-
   @Override
   public int getSelectionLength() {
     return getImpl().getTextAreaSelectionLength(getElement());
@@ -108,7 +102,7 @@
 
   /**
    * Gets the number of text lines that are visible.
-   * 
+   *
    * @return the number of visible lines
    */
   public int getVisibleLines() {
@@ -118,20 +112,16 @@
   /**
    * Sets the requested width of the text box (this is not an exact value, as
    * not all characters are created equal).
-   * 
+   *
    * @param width the requested width, in characters
    */
   public void setCharacterWidth(int width) {
     getTextAreaElement().setCols(width);
   }
 
-  public void setDirection(Direction direction) {
-    BidiUtils.setDirectionOnElement(getElement(), direction);
-  }
-
   /**
    * Sets the number of text lines that are visible.
-   * 
+   *
    * @param lines the number of visible lines
    */
   public void setVisibleLines(int lines) {
diff --git a/user/src/com/google/gwt/user/client/ui/TextBox.java b/user/src/com/google/gwt/user/client/ui/TextBox.java
index bfcff46..baec618 100644
--- a/user/src/com/google/gwt/user/client/ui/TextBox.java
+++ b/user/src/com/google/gwt/user/client/ui/TextBox.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2008 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,38 +18,36 @@
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.InputElement;
-import com.google.gwt.i18n.client.BidiUtils;
-import com.google.gwt.i18n.client.HasDirection;
 
 /**
  * A standard single-line text box.
- * 
+ *
  * <p>
  * <img class='gallery' src='doc-files/TextBox.png'/>
  * </p>
- * 
+ *
  * <h3>CSS Style Rules</h3>
  * <ul class='css'>
  * <li>.gwt-TextBox { primary style }</li>
  * <li>.gwt-TextBox-readonly { dependent style set when the text box is
  * read-only }</li>
  * </ul>
- * 
+ *
  * <p>
  * <h3>Example</h3>
  * {@example com.google.gwt.examples.TextBoxExample}
  * </p>
  */
-public class TextBox extends TextBoxBase implements HasDirection {
+public class TextBox extends TextBoxBase {
 
   /**
    * Creates a TextBox widget that wraps an existing &lt;input type='text'&gt;
    * element.
-   * 
+   *
    * This element must already be attached to the document. If the element is
    * removed from the document, you must call
    * {@link RootPanel#detachNow(Widget)}.
-   * 
+   *
    * @param element the element to be wrapped
    */
   public static TextBox wrap(Element element) {
@@ -76,7 +74,7 @@
    * This constructor may be used by subclasses to explicitly use an existing
    * element. This element must be an &lt;input&gt; element whose type is
    * 'text'.
-   * 
+   *
    * @param element the element to be used
    */
   protected TextBox(Element element) {
@@ -91,13 +89,9 @@
     }
   }
 
-  public Direction getDirection() {
-    return BidiUtils.getDirectionOnElement(getElement());
-  }
-
   /**
    * Gets the maximum allowable length of the text box.
-   * 
+   *
    * @return the maximum length, in characters
    */
   public int getMaxLength() {
@@ -106,20 +100,16 @@
 
   /**
    * Gets the number of visible characters in the text box.
-   * 
+   *
    * @return the number of visible characters
    */
   public int getVisibleLength() {
     return getInputElement().getSize();
   }
 
-  public void setDirection(Direction direction) {
-    BidiUtils.setDirectionOnElement(getElement(), direction);
-  }
-
   /**
    * Sets the maximum allowable length of the text box.
-   * 
+   *
    * @param length the maximum length, in characters
    */
   public void setMaxLength(int length) {
@@ -128,7 +118,7 @@
 
   /**
    * Sets the number of visible characters in the text box.
-   * 
+   *
    * @param length the number of visible characters
    */
   public void setVisibleLength(int length) {
diff --git a/user/src/com/google/gwt/user/client/ui/ValueBoxBase.java b/user/src/com/google/gwt/user/client/ui/ValueBoxBase.java
index 10e7199..dbee95f 100644
--- a/user/src/com/google/gwt/user/client/ui/ValueBoxBase.java
+++ b/user/src/com/google/gwt/user/client/ui/ValueBoxBase.java
@@ -23,6 +23,12 @@
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.i18n.client.AutoDirectionHandler;
+import com.google.gwt.i18n.client.BidiPolicy;
+import com.google.gwt.i18n.client.BidiUtils;
+import com.google.gwt.i18n.client.HasDirection;
+import com.google.gwt.i18n.shared.DirectionEstimator;
+import com.google.gwt.i18n.shared.HasDirectionEstimator;
 import com.google.gwt.text.shared.Parser;
 import com.google.gwt.text.shared.Renderer;
 import com.google.gwt.user.client.DOM;
@@ -43,10 +49,13 @@
  */
 @SuppressWarnings("deprecation")
 public class ValueBoxBase<T> extends FocusWidget implements
-    SourcesChangeEvents, HasChangeHandlers, HasText, HasName, HasValue<T> {
+    SourcesChangeEvents, HasChangeHandlers, HasText, HasName, HasDirection,
+    HasDirectionEstimator, HasValue<T>, AutoDirectionHandler.Target {
 
   private static TextBoxImpl impl = GWT.create(TextBoxImpl.class);
 
+  private AutoDirectionHandler autoDirHandler;
+
   private final Parser<T> parser;
   private final Renderer<T> renderer;
 
@@ -61,6 +70,8 @@
    */
   protected ValueBoxBase(Element elem, Renderer<T> renderer, Parser<T> parser) {
     super(elem);
+    autoDirHandler = AutoDirectionHandler.addTo(this,
+        BidiPolicy.isBidiEnabled());
     this.renderer = renderer;
     this.parser = parser;
   }
@@ -111,6 +122,17 @@
     return impl.getCursorPos(getElement());
   }
 
+  public Direction getDirection() {
+    return BidiUtils.getDirectionOnElement(getElement());
+  }
+  
+  /**
+   * Gets the direction estimation model of the auto-dir handler.
+   */
+  public DirectionEstimator getDirectionEstimator() {
+    return autoDirHandler.getDirectionEstimator();
+  }
+  
   public String getName() {
     return DOM.getElementProperty(getElement(), "name");
   }
@@ -229,6 +251,24 @@
     setSelectionRange(pos, 0);
   }
 
+  public void setDirection(Direction direction) {
+    BidiUtils.setDirectionOnElement(getElement(), direction);
+  }
+  
+  /**
+   * Toggles on / off direction estimation.
+   */
+  public void setDirectionEstimator(boolean enabled) {
+    autoDirHandler.setDirectionEstimator(enabled);
+  }
+  
+  /**
+   * Sets the direction estimation model of the auto-dir handler.
+   */
+  public void setDirectionEstimator(DirectionEstimator directionEstimator) {
+    autoDirHandler.setDirectionEstimator(directionEstimator);
+  }
+  
   /**
    * If a keyboard event is currently being handled by the text box, this method
    * replaces the unicode character or key code associated with it. This allows
@@ -302,6 +342,7 @@
    */
   public void setText(String text) {
     DOM.setElementProperty(getElement(), "value", text != null ? text : "");
+    autoDirHandler.refreshDirection();
   }
 
   /**
@@ -330,4 +371,10 @@
   protected TextBoxImpl getImpl() {
     return impl;
   }
+  
+  @Override
+  protected void onLoad() {
+    super.onLoad();
+    autoDirHandler.refreshDirection();
+  }
 }
diff --git a/user/test/com/google/gwt/user/client/ui/TextBoxBaseTestBase.java b/user/test/com/google/gwt/user/client/ui/TextBoxBaseTestBase.java
index ee46262..4ab8784 100644
--- a/user/test/com/google/gwt/user/client/ui/TextBoxBaseTestBase.java
+++ b/user/test/com/google/gwt/user/client/ui/TextBoxBaseTestBase.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2007 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,6 +17,7 @@
 
 import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.logical.shared.ValueChangeHandler;
+import com.google.gwt.i18n.client.HasDirection.Direction;
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
@@ -27,34 +28,11 @@
  */
 public abstract class TextBoxBaseTestBase extends GWTTestCase {
 
-  public String getModuleName() {
-    return "com.google.gwt.user.User";
-  }
+  private static class Handler implements ValueChangeHandler<String> {
+    String received = null;
 
-  protected abstract TextBoxBase createTextBoxBase();
-
-  /**
-   * Tests that {@link TextArea#setText(String)} appropriately converts nulls to
-   * empty strings.
-   */
-  public void testNullMeansEmptyString() {
-    TextBoxBase area = createTextBoxBase();
-    area.setText(null);
-    assertEquals("setText(null) should result in empty string", "",
-        area.getText());
-  }
-
-  /**
-   * Tests that {@link TextArea#setCursorPos(int)} updates the cursor position
-   * correctly.
-   */
-  public void testMovingCursor() {
-    TextBoxBase area = createTextBoxBase();
-    RootPanel.get().add(area);
-    area.setText("abcd");
-    for (int i = 0; i < 4; i++) {
-      area.setCursorPos(i);
-      assertEquals(i, area.getCursorPos());
+    public void onValueChange(ValueChangeEvent<String> event) {
+      received = event.getValue();
     }
   }
 
@@ -97,23 +75,79 @@
       area.selectAll();
     }
   }
-  
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.user.User";
+  }
+
   /**
-   * Failed in all modes due to HtmlUnit bug.
+   * Tests that auto-dir works correctly.
+   */
+  public void testAutoDir() {
+    TextBoxBase tb = createTextBoxBase();
+    String rtlText = "\u05e0\u05e1";
+    String ltrText = "left to right";
+
+    // Direction should not be affected when auto-dir is disabled.
+    tb.setDirectionEstimator(false);
+    tb.setText(rtlText);
+    assertEquals("Direction should not be affected when auto-dir is disabled",
+        tb.getDirection(), Direction.DEFAULT);
+
+    // Direction should be instantly updated as auto-dir is enabled.
+    tb.setDirectionEstimator(true);
+    assertEquals("Direction should be instantly updated as auto-dir is enabled",
+        tb.getDirection(), Direction.RTL);
+
+    // Direction should be updated on setText when auto-dir is enabled.
+    tb.setText(ltrText);
+    assertEquals(
+        "Direction should be updated on setText when auto-dir is enabled",
+        tb.getDirection(), Direction.LTR);
+  }
+
+  /**
+   * Tests that {@link TextArea#setCursorPos(int)} updates the cursor position
+   * correctly.
+   */
+  public void testMovingCursor() {
+    TextBoxBase area = createTextBoxBase();
+    RootPanel.get().add(area);
+    area.setText("abcd");
+    for (int i = 0; i < 4; i++) {
+      area.setCursorPos(i);
+      assertEquals(i, area.getCursorPos());
+    }
+  }
+
+  /**
+   * Tests that {@link TextArea#setText(String)} appropriately converts nulls to
+   * empty strings.
+   */
+  public void testNullMeansEmptyString() {
+    TextBoxBase area = createTextBoxBase();
+    area.setText(null);
+    assertEquals("setText(null) should result in empty string", "",
+        area.getText());
+  }
+
+  /**
+   * Failed in all modes due to HtmlUnit bug:
    */
   @DoNotRunWith({Platform.HtmlUnitBug})
   public void testValueChangeEvent() {
     TextBoxBase tb = createTextBoxBase();
     // To work cross-platform, the tb must be added to the root panel.
     RootPanel.get().add(tb);
-    
+
     Handler h = new Handler();
     tb.addValueChangeHandler(h);
-    
+
     tb.setValue(null);
     assertEquals("", tb.getValue());
     assertNull(h.received);
-    
+
     tb.setText("able");
     assertEquals("able", tb.getValue());
     assertNull(h.received);
@@ -135,12 +169,6 @@
     tb.setValue("baker", true);
     assertEquals("baker", h.received);
   }
-  
-  private static class Handler implements ValueChangeHandler<String> {
-    String received = null;
-    
-    public void onValueChange(ValueChangeEvent<String> event) {
-      received = event.getValue();
-    }
-  }
+
+  protected abstract TextBoxBase createTextBoxBase();
 }