Revision: 9347
Author: [email protected]
Date: Thu Dec  2 08:13:42 2010
Log: Bidi support for ListBox

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

Review by: [email protected]
http://code.google.com/p/google-web-toolkit/source/detail?r=9347

Modified:
 /trunk/tools/api-checker/config/gwt21_22userApi.conf
 /trunk/user/src/com/google/gwt/user/client/ui/ListBox.java
 /trunk/user/test/com/google/gwt/user/client/ui/ListBoxTest.java

=======================================
--- /trunk/tools/api-checker/config/gwt21_22userApi.conf Thu Dec 2 03:34:25 2010 +++ /trunk/tools/api-checker/config/gwt21_22userApi.conf Thu Dec 2 08:13:42 2010
@@ -273,3 +273,7 @@
com.google.gwt.user.cellview.client.Header::onBrowserEvent(Lcom/google/gwt/dom/client/Element;Lcom/google/gwt/dom/client/NativeEvent;) MISSING com.google.gwt.user.cellview.client.Header::render(Lcom/google/gwt/safehtml/shared/SafeHtmlBuilder;) MISSING

+# These are supposedly ambiguous with other overloads on null values.
+com.google.gwt.user.client.ui.ListBox::addItem(Ljava/lang/String;Ljava/lang/String;) OVERLOADED_METHOD_CALL +com.google.gwt.user.client.ui.ListBox::insertItem(Ljava/lang/String;Ljava/lang/String;I) OVERLOADED_METHOD_CALL
+
=======================================
--- /trunk/user/src/com/google/gwt/user/client/ui/ListBox.java Thu Feb 25 12:09:02 2010 +++ /trunk/user/src/com/google/gwt/user/client/ui/ListBox.java Thu Dec 2 08:13:42 2010
@@ -23,6 +23,13 @@
 import com.google.gwt.event.dom.client.ChangeHandler;
 import com.google.gwt.event.dom.client.HasChangeHandlers;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.i18n.client.HasDirection.Direction;
+import com.google.gwt.i18n.shared.BidiFormatter;
+import com.google.gwt.i18n.shared.DirectionEstimator;
+import com.google.gwt.i18n.shared.HasDirectionEstimator;
+import com.google.gwt.i18n.shared.WordCountDirectionEstimator;
+
+import java.util.ArrayList;

 /**
* A widget that presents a list of choices to the user, either as a list box or
@@ -41,7 +48,14 @@
  * <h3>Example</h3>
  * {...@example com.google.gwt.examples.ListBoxExample}
  * </p>
- *
+ *
+ * <p>
+ * <h3>Built-in Bidi Text Support</h3>
+ * This widget is capable of automatically adjusting its direction according to + * its content. This feature is controlled by {...@link #setDirectionEstimator},
+ * and is off by default.
+ * </p>
+ *
  * <h3>Use in UiBinder Templates</h3>
  * <p>
  * The items of a ListBox element are laid out in &lt;g:item> elements.
@@ -67,7 +81,10 @@
  */
 @SuppressWarnings("deprecation")
 public class ListBox extends FocusWidget implements SourcesChangeEvents,
-    HasChangeHandlers, HasName {
+    HasChangeHandlers, HasName, HasDirectionEstimator {
+
+  public static final DirectionEstimator DEFAULT_DIRECTION_ESTIMATOR =
+    WordCountDirectionEstimator.get();

   private static final int INSERT_AT_END = -1;

@@ -93,6 +110,9 @@

     return listBox;
   }
+
+  private DirectionEstimator estimator;
+  private ArrayList<String> itemTexts = new ArrayList<String>();

   /**
    * Creates an empty list box in single selection mode.
@@ -147,6 +167,21 @@
   public void addItem(String item) {
     insertItem(item, INSERT_AT_END);
   }
+
+  /**
+ * Adds an item to the list box, specifying its direction. This method has the
+   * same effect as
+   *
+   * <pre>
+   * addItem(item, dir, item)
+   * </pre>
+   *
+   * @param item the text of the item to be added
+   * @param dir the item's direction
+   */
+  public void addItem(String item, Direction dir) {
+    insertItem(item, dir, INSERT_AT_END);
+  }

   /**
* Adds an item to the list box, specifying an initial value for the item.
@@ -158,6 +193,19 @@
   public void addItem(String item, String value) {
     insertItem(item, value, INSERT_AT_END);
   }
+
+  /**
+ * Adds an item to the list box, specifying its direction and an initial value
+   * for the item.
+   *
+   * @param item the text of the item to be added
+   * @param dir the item's direction
+   * @param value the item's value, to be submitted if it is part of a
+   *          {...@link FormPanel}; cannot be <code>null</code>
+   */
+  public void addItem(String item, Direction dir, String value) {
+    insertItem(item, dir, value, INSERT_AT_END);
+  }

   /**
    * Removes all items from the list box.
@@ -165,6 +213,10 @@
   public void clear() {
     getSelectElement().clear();
   }
+
+  public DirectionEstimator getDirectionEstimator() {
+    return estimator;
+  }

   /**
    * Gets the number of items present in the list box.
@@ -184,7 +236,7 @@
    */
   public String getItemText(int index) {
     checkIndex(index);
-    return getSelectElement().getOptions().getItem(index).getText();
+    return itemTexts.get(index);
   }

   public String getName() {
@@ -237,11 +289,30 @@
   public void insertItem(String item, int index) {
     insertItem(item, item, index);
   }
+
+  /**
+ * Inserts an item into the list box, specifying its direction. Has the same
+   * effect as
+   *
+   * <pre>
+   * insertItem(item, dir, item, index)
+   * </pre>
+   *
+   * @param item the text of the item to be inserted
+   * @param dir the item's direction
+   * @param index the index at which to insert it
+   */
+  public void insertItem(String item, Direction dir, int index) {
+    insertItem(item, dir, item, index);
+  }

   /**
    * Inserts an item into the list box, specifying an initial value for the
-   * item. If the index is less than zero, or greater than or equal to the
- * length of the list, then the item will be appended to the end of the list.
+   * item. Has the same effect as
+   *
+   * <pre>
+   * insertItem(item, null, value, index)
+   * </pre>
    *
    * @param item the text of the item to be inserted
    * @param value the item's value, to be submitted if it is part of a
@@ -249,12 +320,35 @@
    * @param index the index at which to insert it
    */
   public void insertItem(String item, String value, int index) {
+    insertItem(item, null, value, index);
+  }
+
+  /**
+ * Inserts an item into the list box, specifying its direction and an initial
+   * value for the item. If the index is less than zero, or greater than or
+ * equal to the length of the list, then the item will be appended to the end
+   * of the list.
+   *
+   * @param item the text of the item to be inserted
+ * @param dir the item's direction. If {...@code null}, the item is displayed in + * the widget's overall direction, or, if a direction estimator has
+   *          been set, in the item's estimated direction.
+   * @param value the item's value, to be submitted if it is part of a
+   *          {...@link FormPanel}.
+   * @param index the index at which to insert it
+   */
+ public void insertItem(String item, Direction dir, String value, int index) {
     SelectElement select = getSelectElement();
     OptionElement option = Document.get().createOptionElement();
-    option.setText(item);
+    option.setText(unicodeWrapIfNeeded(item, dir));
     option.setValue(value);

-    if ((index == -1) || (index == select.getLength())) {
+    int itemCount = select.getLength();
+    if (index < 0 || index > itemCount) {
+      index = itemCount;
+    }
+    itemTexts.add(index, item);
+    if (index == itemCount) {
       select.add(option, null);
     } else {
       OptionElement before = select.getOptions().getItem(index);
@@ -294,13 +388,31 @@

   /**
    * Removes the item at the specified index.
-   *
+   *
    * @param index the index of the item to be removed
    * @throws IndexOutOfBoundsException if the index is out of range
    */
   public void removeItem(int index) {
     checkIndex(index);
     getSelectElement().remove(index);
+    itemTexts.remove(index);
+  }
+
+  /**
+   * {...@inheritdoc}
+   * See note at
+ * {...@link #setDirectionEstimator(com.google.gwt.i18n.shared.DirectionEstimator)}
+   */
+  public void setDirectionEstimator(boolean enabled) {
+    setDirectionEstimator(enabled ? DEFAULT_DIRECTION_ESTIMATOR : null);
+  }
+
+  /**
+   * {...@inheritdoc}
+   * Note: this does not affect the direction of already-existing content.
+   */
+ public void setDirectionEstimator(DirectionEstimator directionEstimator) {
+    estimator = directionEstimator;
   }

   /**
@@ -328,11 +440,25 @@
    * @throws IndexOutOfBoundsException if the index is out of range
    */
   public void setItemText(int index, String text) {
+    setItemText(index, text, null);
+  }
+
+  /**
+   * Sets the text associated with the item at a given index.
+   *
+   * @param index the index of the item to be set
+   * @param text the item's new text
+   * @param dir the item's direction.
+   * @throws IndexOutOfBoundsException if the index is out of range
+   */
+  public void setItemText(int index, String text, Direction dir) {
     checkIndex(index);
     if (text == null) {
throw new NullPointerException("Cannot set an option to have null text");
     }
-    getSelectElement().getOptions().getItem(index).setText(text);
+ getSelectElement().getOptions().getItem(index).setText(unicodeWrapIfNeeded(
+        text, dir));
+    itemTexts.set(index, text);
   }

   /**
@@ -424,4 +550,13 @@
   private SelectElement getSelectElement() {
     return getElement().cast();
   }
-}
+
+  private String unicodeWrapIfNeeded(String text, Direction dir) {
+    if (dir == null && estimator != null) {
+      dir = estimator.estimateDirection(text);
+    }
+    return dir == null ? text :
+ BidiFormatter.getInstanceForCurrentLocale().unicodeWrapWithKnownDir(dir,
+            text, false /* isHtml */, false /* dirReset */);
+  }
+}
=======================================
--- /trunk/user/test/com/google/gwt/user/client/ui/ListBoxTest.java Tue Aug 10 10:18:55 2010 +++ /trunk/user/test/com/google/gwt/user/client/ui/ListBoxTest.java Thu Dec 2 08:13:42 2010
@@ -15,6 +15,9 @@
  */
 package com.google.gwt.user.client.ui;

+import com.google.gwt.dom.client.SelectElement;
+import com.google.gwt.i18n.client.HasDirection.Direction;
+import com.google.gwt.i18n.shared.BidiFormatter;
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.user.client.Command;
 import com.google.gwt.user.client.DeferredCommand;
@@ -24,6 +27,12 @@
  */
 public class ListBoxTest extends GWTTestCase {

+  private final String RTL_TEXT = "\u05e0 \u05e0\u05e0\u05e0\u05e0\u05e0" +
+ "\u05e0\u05e0\u05e0 \u05e0\u05e0\u05e0\u05e0\u05e0 \u05e0\u05e0\u05e0" +
+      "\u05e0\u05e0\u05e0 \u05e0\u05e0\u05e0 \u05e0\u05e0\u05e0";
+  private final String LTR_TEXT = "The quick brown fox jumps over the" +
+      "lazy dog. The lazy dog seems quite amused.";
+
   @Override
   public String getModuleName() {
     return "com.google.gwt.user.DebugTest";
@@ -52,9 +61,9 @@
     delayTestFinish(5000);
     DeferredCommand.addCommand(new Command() {
       public void execute() {
-        UIObjectTest.assertDebugIdContents("myList-item0", "option0");
-        UIObjectTest.assertDebugIdContents("myList-item1", "option1");
-        UIObjectTest.assertDebugIdContents("myList-item2", "option2");
+        UIObjectTest.assertDebugIdContents("myList-item0", "option0");
+        UIObjectTest.assertDebugIdContents("myList-item1", "option1");
+        UIObjectTest.assertDebugIdContents("myList-item2", "option2");
         UIObjectTest.assertDebugIdContents("myList-item3", "option3");
         finishTest();
       }
@@ -107,6 +116,46 @@
       assertEquals("b", lb.getItemText(1));
       assertEquals("c", lb.getItemText(2));
     }
+
+    // Insert items of different directions
+    {
+      // Explicit direction, no direction estimation
+      ListBox lb = new ListBox();
+      lb.insertItem(RTL_TEXT, Direction.RTL, 0);
+      assertEquals(RTL_TEXT, lb.getItemText(0));
+ assertOptionText(BidiFormatter.getInstanceForCurrentLocale().unicodeWrap(
+          RTL_TEXT, false /* isHtml */, false /* dirReset */), lb, 0);
+      lb.insertItem(LTR_TEXT, Direction.LTR, 0);
+      assertEquals(LTR_TEXT, lb.getItemText(0));
+ assertOptionText(BidiFormatter.getInstanceForCurrentLocale().unicodeWrap(
+          LTR_TEXT, false /* isHtml */, false /* dirReset */), lb, 0);
+      lb.clear();
+
+      // Direction estimation
+      lb.setDirectionEstimator(true);
+      lb.addItem(RTL_TEXT);
+      assertEquals(RTL_TEXT, lb.getItemText(0));
+ assertOptionText(BidiFormatter.getInstanceForCurrentLocale().unicodeWrap(
+          RTL_TEXT, false /* isHtml */, false /* dirReset */), lb, 0);
+      lb.addItem(LTR_TEXT);
+      assertEquals(LTR_TEXT, lb.getItemText(1));
+ assertOptionText(BidiFormatter.getInstanceForCurrentLocale().unicodeWrap(
+          LTR_TEXT, false /* isHtml */, false /* dirReset */), lb, 1);
+
+      // Explicit direction which is opposite to the estimated direction
+      lb.insertItem(RTL_TEXT, Direction.LTR, 0);
+      assertEquals(RTL_TEXT, lb.getItemText(0));
+      assertOptionText(
+ BidiFormatter.getInstanceForCurrentLocale().unicodeWrapWithKnownDir( + Direction.LTR, RTL_TEXT, false /* isHtml */, false /* dirReset */),
+          lb, 0);
+      lb.insertItem(LTR_TEXT, Direction.RTL, 1);
+      assertEquals(LTR_TEXT, lb.getItemText(1));
+      assertOptionText(
+ BidiFormatter.getInstanceForCurrentLocale().unicodeWrapWithKnownDir( + Direction.RTL, LTR_TEXT, false /* isHtml */, false /* dirReset */),
+          lb, 1);
+    }
   }

   public void testRemove() {
@@ -214,6 +263,46 @@
     assertEquals("bc", box.getItemText(1));
     box.setItemText(0, "");
     assertEquals("", box.getItemText(0));
+
+    // Text of different directions
+    {
+      ListBox lb = new ListBox();
+      // Explicit direction, no direction estimation
+      lb.insertItem(RTL_TEXT, Direction.RTL, 0);
+      assertEquals(RTL_TEXT, lb.getItemText(0));
+ assertOptionText(BidiFormatter.getInstanceForCurrentLocale().unicodeWrap(
+          RTL_TEXT, false /* isHtml */, false /* dirReset */), lb, 0);
+      lb.insertItem(LTR_TEXT, Direction.LTR, 0);
+      assertEquals(LTR_TEXT, lb.getItemText(0));
+ assertOptionText(BidiFormatter.getInstanceForCurrentLocale().unicodeWrap(
+          LTR_TEXT, false /* isHtml */, false /* dirReset */), lb, 0);
+
+      // Direction estimation
+      lb.setDirectionEstimator(true);
+      lb.setItemText(0, RTL_TEXT);
+      assertEquals(RTL_TEXT, lb.getItemText(0));
+ assertOptionText(BidiFormatter.getInstanceForCurrentLocale().unicodeWrap(
+          RTL_TEXT, false /* isHtml */, false /* dirReset */), lb, 0);
+      lb.setItemText(0, LTR_TEXT);
+      assertEquals(LTR_TEXT, lb.getItemText(0));
+ assertOptionText(BidiFormatter.getInstanceForCurrentLocale().unicodeWrap(
+          LTR_TEXT, false /* isHtml */, false /* dirReset */), lb, 0);
+
+      // Explicit direction which is opposite to the estimated direction
+      lb.setItemText(0, LTR_TEXT, Direction.RTL);
+      assertEquals(LTR_TEXT, lb.getItemText(0));
+      assertOptionText(
+ BidiFormatter.getInstanceForCurrentLocale().unicodeWrapWithKnownDir( + Direction.RTL, LTR_TEXT, false /* isHtml */, false /* dirReset */),
+          lb, 0);
+      lb.setItemText(0, RTL_TEXT, Direction.LTR);
+      assertEquals(RTL_TEXT, lb.getItemText(0));
+      assertOptionText(
+ BidiFormatter.getInstanceForCurrentLocale().unicodeWrapWithKnownDir( + Direction.LTR, RTL_TEXT, false /* isHtml */, false /* dirReset */),
+          lb, 0);
+    }
+
     try {
       box.setItemText(0, null);
       fail("Should have thrown Null Pointer");
@@ -262,4 +351,9 @@
       assertEquals("item text", box.getItemText(1));
     }
   }
-}
+
+ private void assertOptionText(String expected, ListBox listBox, int index) {
+    SelectElement select = listBox.getElement().cast();
+    assertEquals(expected, select.getOptions().getItem(index).getText());
+  }
+}

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

Reply via email to