Adds UiBinder support for ListBox.
Fixes http://code.google.com/p/google-web-toolkit/issues/detail?id=4654

Patch by http://code.google.com/u/markovuksanovic/
Review by rjrjr@google.com http://gwt-code-reviews.appspot.com/153804


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7619 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/uibinder/elementparsers/ListBoxParser.java b/user/src/com/google/gwt/uibinder/elementparsers/ListBoxParser.java
new file mode 100644
index 0000000..3a9993a
--- /dev/null
+++ b/user/src/com/google/gwt/uibinder/elementparsers/ListBoxParser.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2009 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.uibinder.elementparsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.uibinder.rebind.UiBinderWriter;
+import com.google.gwt.uibinder.rebind.XMLElement;
+
+/**
+ * A parser for ListBox items.
+ */
+public class ListBoxParser implements ElementParser {
+  
+  private static final String ITEM_TAG = "item";
+
+  public void parse(XMLElement elem, String fieldName, JClassType type,
+      UiBinderWriter writer) throws UnableToCompleteException {
+    // Parse children.
+    for (XMLElement child : elem.consumeChildElements()) {
+      String tagName = child.getLocalName();
+      if (!tagName.equals(ITEM_TAG)) {
+        writer.die("Invalid ListBox child element: " + tagName);
+      }
+      String value = child.consumeStringAttribute("value");
+      String innerText = child.consumeInnerTextEscapedAsHtmlStringLiteral(new TextInterpreter(writer));
+      if (value != null) {
+        writer.addStatement("%s.addItem(\"%s\", %s);", fieldName, innerText, value);
+      } else {
+        writer.addStatement("%s.addItem(\"%s\");", fieldName, innerText);
+      }
+    }
+  }
+
+}
diff --git a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
index f168d1a..a013d27 100644
--- a/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
+++ b/user/src/com/google/gwt/uibinder/rebind/UiBinderWriter.java
@@ -949,6 +949,7 @@
     addWidgetParser("StackLayoutPanel");
     addWidgetParser("TabLayoutPanel");
     addWidgetParser("Image");
+    addWidgetParser("ListBox");
   }
 
   /**
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 3029329..4715f94 100644
--- a/user/src/com/google/gwt/user/client/ui/ListBox.java
+++ b/user/src/com/google/gwt/user/client/ui/ListBox.java
@@ -41,6 +41,29 @@
  * <h3>Example</h3>
  * {@example com.google.gwt.examples.ListBoxExample}
  * </p>
+ * 
+ * <h3>Use in UiBinder Templates</h3>
+ * <p>
+ * The items of a ListBox element are laid out in &lt;g:item> elements.
+ * Each item contains text that will be added to the list of available
+ * items that will be shown, either in the drop down or list. (Note that
+ * the tags of the item elements are not capitalized. This is meant to
+ * signal that the item is not a runtime object, and so cannot have a
+ * <code>ui:field</code> attribute.) It is also possible to explicitly
+ * specify item's value using value attribute as shown below.
+ * <p>
+ * For example:
+ * 
+ * <pre>
+ * &lt;g:ListBox>
+ *  &lt;g:item>
+ *    first
+ *  &lt;/g:item>
+ *  &lt;g:item value='2'>
+ *    second
+ *  &lt;/g:item>
+ * &lt;/g:ListBox>
+ * </pre>
  */
 @SuppressWarnings("deprecation")
 public class ListBox extends FocusWidget implements SourcesChangeEvents,
diff --git a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
index 9ff047d..e828dba 100644
--- a/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
+++ b/user/test/com/google/gwt/uibinder/UiBinderJreSuite.java
@@ -27,6 +27,7 @@
 import com.google.gwt.uibinder.elementparsers.ImageParserTest;
 import com.google.gwt.uibinder.elementparsers.IsEmptyParserTest;
 import com.google.gwt.uibinder.elementparsers.LayoutPanelParserTest;
+import com.google.gwt.uibinder.elementparsers.ListBoxParserTest;
 import com.google.gwt.uibinder.elementparsers.StackLayoutPanelParserTest;
 import com.google.gwt.uibinder.elementparsers.TabLayoutPanelParserTest;
 import com.google.gwt.uibinder.elementparsers.UIObjectParserTest;
@@ -76,6 +77,7 @@
     suite.addTestSuite(ImageParserTest.class);
     suite.addTestSuite(IsEmptyParserTest.class);
     suite.addTestSuite(LayoutPanelParserTest.class);
+    suite.addTestSuite(ListBoxParserTest.class);
     suite.addTestSuite(StackLayoutPanelParserTest.class);
     suite.addTestSuite(TabLayoutPanelParserTest.class);
     suite.addTestSuite(UIObjectParserTest.class);
diff --git a/user/test/com/google/gwt/uibinder/elementparsers/ListBoxParserTest.java b/user/test/com/google/gwt/uibinder/elementparsers/ListBoxParserTest.java
new file mode 100644
index 0000000..b90eb0b
--- /dev/null
+++ b/user/test/com/google/gwt/uibinder/elementparsers/ListBoxParserTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2009 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.uibinder.elementparsers;
+
+import com.google.gwt.core.ext.UnableToCompleteException;
+
+import junit.framework.TestCase;
+
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.Iterator;
+
+/**
+ * A unit test. Guess what of.
+ */
+public class ListBoxParserTest extends TestCase {
+  private static final String PARSED_TYPE = "com.google.gwt.user.client.ui.ListBox";
+
+  private ElementParserTester tester;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    tester = new ElementParserTester(PARSED_TYPE, new ListBoxParser());
+  }
+  
+  public void testChildWithWhitespaces() throws UnableToCompleteException, SAXException, IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:ListBox>");
+    b.append("  <g:item>    </g:item>");
+    b.append("</g:ListBox>");
+    
+    String[] expected = {
+        "fieldName.addItem(\"\");" };
+    
+    tester.parse(b.toString());
+    
+    Iterator<String> i = tester.writer.statements.iterator();
+    for (String e : expected) {
+      assertEquals(e, i.next());
+    }
+    assertFalse(i.hasNext());
+    assertNull(tester.logger.died);
+  }
+  
+  public void testEmtpyChild() throws UnableToCompleteException, SAXException, IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:ListBox>");
+    b.append("  <g:item></g:item>");
+    b.append("</g:ListBox>");
+    
+    String[] expected = {
+        "fieldName.addItem(\"\");" };
+    
+    tester.parse(b.toString());
+    
+    Iterator<String> i = tester.writer.statements.iterator();
+    for (String e : expected) {
+      assertEquals(e, i.next());
+    }
+    assertFalse(i.hasNext());
+    assertNull(tester.logger.died);
+  }
+  
+  public void testInvalidChild() throws SAXException, IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:ListBox>");
+    b.append("  <foo/>");
+    b.append("</g:ListBox>");
+    
+    try {
+      tester.parse(b.toString());
+      fail();
+    } catch (UnableToCompleteException e) {
+      assertNotNull(tester.logger.died);
+    }
+  }
+  
+  public void testValidChild() throws UnableToCompleteException, SAXException,
+  IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:ListBox>");
+    b.append("  <g:item>");
+    b.append("    test item");
+    b.append("  </g:item>");
+    
+    b.append("  <g:item value='testValue'>");
+    b.append("    item with test value");
+    b.append("  </g:item>");
+    b.append("</g:ListBox>");
+    
+    String[] expected = {
+        "fieldName.addItem(\"test item\");",
+        "fieldName.addItem(\"item with test value\", \"testValue\");" };
+    
+    tester.parse(b.toString());
+    
+    Iterator<String> i = tester.writer.statements.iterator();
+    for (String e : expected) {
+      assertEquals(e, i.next());
+    }
+    assertFalse(i.hasNext());
+    assertNull(tester.logger.died);
+  }
+  
+  public void testWidgetAsChild() throws SAXException, IOException {
+    StringBuffer b = new StringBuffer();
+    b.append("<g:ListBox>");
+    b.append("  <g:item>");
+    b.append("    <g:Label> Test Label </g:Label>");
+    b.append("  </g:item>");
+    b.append("</g:ListBox>");
+    
+    try {
+      tester.parse(b.toString());
+      fail();
+    } catch (UnableToCompleteException e) {
+      assertNotNull(tester.logger.died);
+    }
+  }
+}
diff --git a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
index cb93041..861a0a7 100644
--- a/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
+++ b/user/test/com/google/gwt/uibinder/test/UiJavaResources.java
@@ -221,6 +221,17 @@
       return code;
     }
   };
+  public static final MockJavaResource LIST_BOX = new MockJavaResource(
+    "com.google.gwt.user.client.ui.ListBox") {
+    @Override
+    protected CharSequence getContent() {
+      StringBuffer code = new StringBuffer();
+      code.append("package com.google.gwt.user.client.ui;\n");
+      code.append("public class ListBox extends Widget {\n");
+      code.append("}\n");
+      return code;
+    }
+  };
   public static final MockJavaResource MOUSE_OVER_EVENT = new MockJavaResource(
       "com.google.gwt.event.dom.client.MouseOverEvent") {
     @Override
@@ -360,6 +371,7 @@
     rtn.add(HAS_VERTICAL_ALIGNMENT);
     rtn.add(LABEL);
     rtn.add(LAYOUT_PANEL);
+    rtn.add(LIST_BOX);
     rtn.add(MOUSE_OVER_EVENT);
     rtn.add(MOUSE_OVER_HANDLER);
     rtn.add(SPLIT_LAYOUT_PANEL);
diff --git a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
index e149b21..9e19e29 100644
--- a/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
+++ b/user/test/com/google/gwt/uibinder/test/client/UiBinderTest.java
@@ -294,6 +294,14 @@
   public void testFieldInPlaceholderedElement() {
     assertEquals("named portions", widgetUi.spanInMsg.getInnerText());
   }
+  
+  public void testListBox() {
+    assertEquals(2, widgetUi.fooListBox.getItemCount());
+    assertEquals("bar", widgetUi.fooListBox.getItemText(0));
+    assertEquals("bar", widgetUi.fooListBox.getValue(0));
+    assertEquals("bar 2", widgetUi.fooListBox.getItemText(1));
+    assertEquals("bar2", widgetUi.fooListBox.getValue(1));
+  }
 
   public void testMenuAttributes() {
     assertEquals(widgetUi.dropdownMenuBar.getStyleName(),
diff --git a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
index ca6cdb0..a74ceac 100644
--- a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
+++ b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.java
@@ -39,6 +39,7 @@
 import com.google.gwt.user.client.ui.HasHTML;
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.ListBox;
 import com.google.gwt.user.client.ui.MenuBar;
 import com.google.gwt.user.client.ui.MenuItem;
 import com.google.gwt.user.client.ui.PushButton;
@@ -154,6 +155,7 @@
   @UiField ToggleButton toggle;
   @UiField HTML styleLess;
   @UiField FooDialog fooDialog;
+  @UiField ListBox fooListBox;
 
   public WidgetBasedUi() {
     external.style().ensureInjected();
diff --git a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
index fb21f67..476437b 100644
--- a/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
+++ b/user/test/com/google/gwt/uibinder/test/client/WidgetBasedUi.ui.xml
@@ -596,6 +596,11 @@
        <gwt:Button ui:field='ok'>Okay</gwt:Button>
      </gwt:HTMLPanel>
    </gwt:DialogBox>
+   
+   <gwt:ListBox ui:field='fooListBox'>
+     <gwt:item>bar</gwt:item>
+     <gwt:item value='bar2'>bar 2</gwt:item>
+   </gwt:ListBox>
 
    </gwt:HTMLPanel>
   </gwt:Dock>