Bidi support for ListBox
Review at http://gwt-code-reviews.appspot.com/1137801
Review by: aharon@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9347 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/tools/api-checker/config/gwt21_22userApi.conf b/tools/api-checker/config/gwt21_22userApi.conf
index e700609..a15c6b7 100644
--- a/tools/api-checker/config/gwt21_22userApi.conf
+++ b/tools/api-checker/config/gwt21_22userApi.conf
@@ -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
+
diff --git a/user/src/com/google/gwt/user/client/ui/ListBox.java b/user/src/com/google/gwt/user/client/ui/ListBox.java
index 4715f94..371e71c 100644
--- a/user/src/com/google/gwt/user/client/ui/ListBox.java
+++ b/user/src/com/google/gwt/user/client/ui/ListBox.java
@@ -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 <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;
@@ -94,6 +111,9 @@
return listBox;
}
+ private DirectionEstimator estimator;
+ private ArrayList<String> itemTexts = new ArrayList<String>();
+
/**
* Creates an empty list box in single selection mode.
*/
@@ -149,6 +169,21 @@
}
/**
+ * 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.
*
* @param item the text of the item to be added
@@ -160,12 +195,29 @@
}
/**
+ * 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.
*/
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() {
@@ -239,9 +291,28 @@
}
/**
+ * 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 */);
+ }
}
diff --git a/user/test/com/google/gwt/user/client/ui/ListBoxTest.java b/user/test/com/google/gwt/user/client/ui/ListBoxTest.java
index eb06f9e..0168129 100644
--- a/user/test/com/google/gwt/user/client/ui/ListBoxTest.java
+++ b/user/test/com/google/gwt/user/client/ui/ListBoxTest.java
@@ -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());
+ }
}