Adding ItemPicker, ColorPicker, SuggestPicker, AbstractItemPicker

Reviewed by: knorton


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@740 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/client/ui/AbstractItemPicker.java b/user/src/com/google/gwt/user/client/ui/AbstractItemPicker.java
new file mode 100644
index 0000000..f5fa331
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/AbstractItemPicker.java
@@ -0,0 +1,264 @@
+/*

+ * Copyright 2006 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.user.client.ui;

+

+import com.google.gwt.user.client.DOM;

+import com.google.gwt.user.client.Element;

+import com.google.gwt.user.client.Event;

+

+import java.util.ArrayList;

+import java.util.Collection;

+

+/**

+ * Helpful base implementation of the {@link ItemPicker} interface.

+ */

+abstract class AbstractItemPicker extends Composite implements ItemPicker {

+  /*

+   * Implementation note:AbstractItemPicker is package protected because we are

+   * hoping we might eventually be able to slip in a more efficient

+   * implementation of this class, so do not want to be bound by this

+   * implementation.

+   */

+

+  /**

+   * Selectable item.

+   */

+  class Item extends Widget {

+    private int index;

+

+    /**

+     * 

+     * Constructor for <code>Item</code>.

+     * 

+     * @param index index associated with item

+     */

+    Item(int index) {

+      setElement(DOM.createDiv());

+      sinkEvents(Event.ONMOUSEDOWN | Event.ONMOUSEOVER);

+      this.index = index;

+      this.setStyleName(itemStyleName);

+      items.add(index, this);

+    }

+

+    public void onBrowserEvent(Event event) {

+      switch (DOM.eventGetType(event)) {

+        case Event.ONMOUSEOVER:

+          setSelection(this);

+          break;

+        case Event.ONMOUSEDOWN:

+          commitSelection();

+          break;

+      }

+    }

+

+    public String toString() {

+      return "value: " + getValue(getIndex()) + " index: " + this.getIndex();

+    }

+

+    /**

+     * Gets the index of the item.

+     * 

+     * @return the item's index

+     */

+    int getIndex() {

+      return index;

+    }

+

+    AbstractItemPicker getOwner() {

+      return AbstractItemPicker.this;

+    }

+  }

+

+  private static final String STYLENAME_SELECTED_ITEM = "selected";

+  private static final String STYLENAME_ITEM = "item";

+

+  private ChangeListenerCollection changeListeners = new ChangeListenerCollection();

+  private Item selectedItem;

+  private final String selectedStyleName;

+  private final String itemStyleName;

+  private final ArrayList items;

+

+  /**

+   * Constructor for <code>ItemPicker</code>. Provides "item" as the default

+   * item style name, and "selected" as the default selected item style name.

+   */

+  public AbstractItemPicker() {

+    this(STYLENAME_ITEM, STYLENAME_SELECTED_ITEM);

+  }

+

+  /**

+   * 

+   * Constructor for <code>ItemPicker</code>.

+   * 

+   * @param itemStyleName CSS style name for default items

+   * @param selectedItemStyleName CSS style name for the currently selected item

+   */

+  public AbstractItemPicker(String itemStyleName, String selectedItemStyleName) {

+    initWidget(new FlexTable());

+    this.selectedStyleName = selectedItemStyleName;

+    this.itemStyleName = itemStyleName;

+

+    // CSS does not effect padding and spacing correctly. So setting to 0

+    // here.

+    getLayout().setCellPadding(0);

+    getLayout().setCellSpacing(0);

+    items = new ArrayList();

+  }

+

+  public final void addChangeListener(ChangeListener listener) {

+    if (changeListeners == null) {

+      changeListeners = new ChangeListenerCollection();

+    }

+    changeListeners.add(listener);

+  }

+

+  public void commitSelection() {

+    if (selectedItem == null) {

+      throw new IllegalStateException("No element is selected");

+    }

+    changeListeners.fireChange(this);

+  }

+

+  public abstract boolean delegateKeyDown(char keyCode);

+

+  public int getItemCount() {

+    return items.size();

+  }

+

+  public final int getSelectedIndex() {

+    Item item = getSelectedItem();

+    if (item == null) {

+      return -1;

+    }

+    return item.getIndex();

+  }

+

+  public final Object getSelectedValue() {

+    int index = getSelectedIndex();

+    if (index == -1) {

+      return null;

+    }

+    return getValue(index);

+  }

+

+  public Object getValue(int index) {

+    return getValue(getItem(index).getElement());

+  }

+

+  public final void removeChangeListener(ChangeListener listener) {

+    this.changeListeners.remove(listener);

+  }

+

+  public abstract void setItems(Collection items);

+

+  public final void setSelectedIndex(int index) {

+    Item item = getItem(index);

+    setSelection(item);

+  }

+

+  /**

+   * Formats the displayed element using the information given by the user

+   * supplied item.

+   * 

+   * @param displayedElement the element used to the display the item

+   * @param item the user supplied item information

+   */

+  protected void format(Element displayedElement, Object item) {

+    DOM.setInnerHTML(displayedElement, item.toString());

+  }

+

+  /**

+   * Gets the value from a given element. By default this method is used by

+   * {@link AbstractItemPicker#getValue(int)} to compute the value that should

+   * be returned to the user.

+   * 

+   * @param displayedElement displayed element

+   * @return the value associated with the given displayed element

+   */

+  protected Object getValue(Element displayedElement) {

+    return DOM.getInnerText(displayedElement);

+  }

+

+  void clearItems() {

+    items.clear();

+    getLayout().clear();

+  }

+

+  /**

+   * Gets the ith item.

+   * 

+   * @param index index of item

+   * @return the ith item

+   */

+  Item getItem(int index) {

+    return (Item) items.get(index);

+  }

+

+  FlexTable getLayout() {

+    return (FlexTable) getWidget();

+  }

+

+  /**

+   * Gets the currently selected item.

+   * 

+   * @return selected item

+   */

+  Item getSelectedItem() {

+    return selectedItem;

+  }

+

+  /**

+   * Sets the current selection.

+   * 

+   * @param item item to set

+   */

+  void setSelection(Item item) {

+    if (selectedItem == item) {

+      return;

+    }

+

+    // Remove old selected item.

+    if (selectedItem != null) {

+      selectedItem.removeStyleName(selectedStyleName);

+      selectedItem.addStyleName(itemStyleName);

+    }

+

+    // Add new selected item.

+    selectedItem = item;

+    if (selectedItem != null) {

+      selectedItem.removeStyleName(itemStyleName);

+      selectedItem.addStyleName(selectedStyleName);

+    }

+  }

+

+  /**

+   * Shifts the current selection by the given amount, unless that would make

+   * the selection invalid.

+   * 

+   * @param shift the amount to shift the current selection by

+   */

+  void shiftSelection(int shift) {

+    int newIndex = getSelectedIndex() + shift;

+    if (newIndex < 0 || newIndex >= getItemCount()) {

+      return;

+    } else {

+      Item item = getItem(newIndex);

+      setSelection(item);

+    }

+  }

+

+}

diff --git a/user/src/com/google/gwt/user/client/ui/ColorPicker.java b/user/src/com/google/gwt/user/client/ui/ColorPicker.java
new file mode 100644
index 0000000..ee0052e
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/ColorPicker.java
@@ -0,0 +1,202 @@
+/*

+ * 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

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+

+package com.google.gwt.user.client.ui;

+

+import com.google.gwt.user.client.DOM;

+import com.google.gwt.user.client.Element;

+

+import java.util.Arrays;

+import java.util.Collection;

+import java.util.Iterator;

+import java.util.List;

+

+/**

+ * Color picker. Each "item" represents a CSS color.

+ */

+public class ColorPicker extends AbstractItemPicker {

+

+  class ColorItem extends Item {

+    private Object color;

+

+    ColorItem(int index, Object color) {

+      super(index);

+      this.color = color;

+    }

+

+    Object getColor() {

+      return color;

+    }

+  }

+

+  /**

+   * Default colors supplied to the color picker popup. The default colors are a

+   * selection of 60 web-safe CSS color styles.

+   */

+  public static final List DEFAULT_COLORS;

+

+  private static final String STYLENAME_DEFAULT = "gwt-ColorPicker ";

+

+  static {

+    String[] baseColors = {

+        "#ffffcc", "#ffff66", "#ffcc66", "#F2984C", "#E1771E", "#B47B10",

+        "#A9501B", "#6F3C1B", "#804000", "#CC0000", "#940F04", "#660000",

+        "#C3D9FF", "#99C9FF", "#66B5FF", "#3D81EE", "#0066CC", "#6C82B5",

+        "#32527A", "#2D6E89", "#006699", "#215670", "#003366", "#000033",

+        "#CAF99B", "#80FF00", "#00FF80", "#78B749", "#2BA94F", "#38B63C",

+        "#0D8F63", "#2D8930", "#1B703A", "#11593C", "#063E3F", "#002E3F",

+        "#FFBBE8", "#E895CC", "#FF6FCF", "#C94093", "#9D1961", "#800040",

+        "#800080", "#72179D", "#6728B2", "#6131BD", "#341473", "#400058",

+        "#ffffff", "#e6e6e6", "#cccccc", "#b3b3b3", "#999999", "#808080",

+        "#7f7f7f", "#666666", "#4c4c4c", "#333333", "#191919", "#000000"};

+    DEFAULT_COLORS = Arrays.asList(baseColors);

+  }

+

+  private int columnsPerRow = -1;

+

+  /**

+   * Constructor for {@link ColorPicker}.

+   */

+  public ColorPicker() {

+    this(12);

+  }

+

+  /**

+   * Constructor for {@link ColorPicker}.

+   * 

+   * @param numColumns number of columns to be displayed

+   */

+  public ColorPicker(int numColumns) {

+    this(DEFAULT_COLORS, numColumns);

+  }

+

+  /**

+   * 

+   * Constructor for {@link ColorPicker}. The passed in {@link Collection}

+   * should contain objects such that the {@link String#toString()} method

+   * returns a representation of a CSS color.

+   * 

+   * @param colors color collection

+   * @param numColumns number of columns to be displayed

+   */

+  public ColorPicker(Collection colors, int numColumns) {

+    this.setColumnsPerRow(numColumns);

+    setStyleName(STYLENAME_DEFAULT);

+    setItems(colors);

+  }

+

+  public boolean delegateKeyDown(char keyCode) {

+    if (isAttached()) {

+      switch (keyCode) {

+        case KeyboardListener.KEY_DOWN:

+          shiftSelection(getColumnsPerRow());

+          break;

+        case KeyboardListener.KEY_UP:

+          shiftSelection(-getColumnsPerRow());

+          break;

+        case KeyboardListener.KEY_LEFT:

+          shiftSelection(-1);

+          break;

+        case KeyboardListener.KEY_RIGHT:

+          shiftSelection(1);

+          break;

+        case KeyboardListener.KEY_ENTER:

+          commitSelection();

+          break;

+        default:

+          // Avoid shared post processing.

+          return false;

+      }

+      return true;

+    } else {

+      return false;

+    }

+  }

+

+  /**

+   * Gets the number of columns to display per row of colors.

+   * 

+   * @return numColumns number of columns

+   */

+  public int getColumnsPerRow() {

+    return columnsPerRow;

+  }

+

+  public Object getValue(int i) {

+    ColorItem item = (ColorItem) getItem(i);

+    if (item == null) {

+      return null;

+    } else {

+      return item.getColor();

+    }

+  }

+

+  /**

+   * Sets the number of columns to display per row of colors.

+   * 

+   * @param numColumns number of columns

+   */

+  public void setColumnsPerRow(int numColumns) {

+    if (numColumns <= 0) {

+      throw new IllegalStateException("Cannot use " + numColumns

+          + " as the number of columns per row");

+    }

+    this.columnsPerRow = numColumns;

+  }

+

+  /**

+   * Sets the items in the {@link ColorPicker}. Each color's {@link String#toString()}

+   *          method should return a representation of a CSS color.

+   * 

+   * @param colors collection of colors.

+   */

+  public final void setItems(Collection colors) {

+    setItems(colors.iterator());

+  }

+

+  protected void format(Element e, Object item) {

+    DOM.setStyleAttribute(e, "background", item.toString());

+  }

+

+  /**

+   * Sets the items in the {@link ColorPicker}.

+   */

+  private final void setItems(Iterator colors) {

+    clearItems();

+    int row = 0;

+    int i = 0;

+    while (true) {

+

+      for (int column = 0; column < getColumnsPerRow(); column++) {

+        if (!colors.hasNext()) {

+          if (i == 0) {

+            throw new IllegalStateException(

+                "Cannot populate a color picker with 0 colors!");

+          }

+          // All items have been placed.

+          return;

+        }

+

+        ColorItem item = new ColorItem(i, colors.next());

+        format(item.getElement(), item.getColor());

+        getLayout().setWidget(row, column, item);

+        ++i;

+      }

+      ++row;

+    }

+  }

+

+}

diff --git a/user/src/com/google/gwt/user/client/ui/ItemPicker.java b/user/src/com/google/gwt/user/client/ui/ItemPicker.java
new file mode 100644
index 0000000..1734a17
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/ItemPicker.java
@@ -0,0 +1,99 @@
+/*

+ * Copyright 2006 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.user.client.ui;

+

+import java.util.Collection;

+

+/**

+ * Represents a pickable list of items. Each {@link ItemPicker} should be able

+ * to respond to mouse and keyboard events.

+ * 

+ */

+public interface ItemPicker extends SourcesChangeEvents {

+  

+  /**

+   * Commits the current selection. Any relevant change listeners are fired.

+   */

+  public void commitSelection();

+

+  /**

+   * Allows the {@link ItemPicker} to be controlled via keyboard input. This

+   * method should be hooked up to an appropriate

+   * {@link KeyboardListener#onKeyDown(Widget, char, int)} method.

+   * 

+   * @param keyCode key code

+   * @return <code>true</code> if the key code was consumed by the picker,

+   *         <code>false</code> otherwise

+   */

+  public boolean delegateKeyDown(char keyCode);

+

+  /**

+   * Gets the number of items.

+   * 

+   * @return number of items

+   */

+  public int getItemCount();

+

+  /**

+   * Gets the currently selected index.

+   * 

+   * @return selected index, or -1 if no index is selected

+   */

+  public int getSelectedIndex();

+

+  /**

+   * Gets the value associated with the currently selected index.

+   * 

+   * <p>

+   * The value should be convertible into a human readable {@link String} by

+   * calling the {@link String#toString()} method.

+   * </p>

+   * 

+   * @return current selected value, or null if no value is selected

+   */

+  public Object getSelectedValue();

+

+  /**

+   * Gets the value associated with the given index.

+   * <p>

+   * The value should be convertible into a human readable {@link String} by

+   * calling the {@link String#toString()}.

+   * </p>

+   * 

+   * @param index index

+   * @return the value associated with <code>index</code>.

+   */

+  // Design note: Object is returned rather than String in order to allow the

+  // user to return type-safe enumerations.

+  public Object getValue(int index);

+

+  /**

+   * Sets the items to be displayed. The expected type of each item should be

+   * clearly documented in each class which implements this interface.  

+   * 

+   * @param items items to be displayed

+   */

+  public void setItems(Collection items);

+

+  /**

+   * Sets the currently selected index.

+   * 

+   * @param index new selected index

+   */

+  public void setSelectedIndex(int index);

+

+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/user/client/ui/SuggestPicker.java b/user/src/com/google/gwt/user/client/ui/SuggestPicker.java
new file mode 100644
index 0000000..9c30670
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/SuggestPicker.java
@@ -0,0 +1,143 @@
+/*

+ * 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

+ * License for the specific language governing permissions and limitations under

+ * the License.

+ */

+

+package com.google.gwt.user.client.ui;

+

+import java.util.Collection;

+import java.util.Iterator;

+

+/**

+ * Suggestion picker. Each "item" represents a suggestion.

+ */

+public class SuggestPicker extends AbstractItemPicker {

+

+  /**

+   * Default style for the picker.

+   */

+  private static final String STYLENAME_DEFAULT = "gwt-SuggestPicker";

+

+  private int startInvisible = Integer.MAX_VALUE;

+

+  /**

+   * Constructor for <code>SuggestPicker</code>.

+   */

+  public SuggestPicker() {

+    setStyleName(STYLENAME_DEFAULT);

+  }

+

+  public boolean delegateKeyDown(char keyCode) {

+    if (isAttached()) {

+      switch (keyCode) {

+        case KeyboardListener.KEY_DOWN:

+          shiftSelection(1);

+          return true;

+        case KeyboardListener.KEY_UP:

+          shiftSelection(-1);

+          return true;

+        case KeyboardListener.KEY_ENTER:

+          commitSelection();

+          return true;

+      }

+    }

+    return false;

+  }

+

+  public int getItemCount() {

+    if (startInvisible == Integer.MAX_VALUE) {

+      return 0;

+    } else {

+      return startInvisible;

+    }

+  }

+

+  /**

+   * Sets the suggestions associated with this picker.

+   * 

+   * @param suggestions suggestions for this picker; the suggestions must have

+   *          valid {@link String#toString()} methods

+   */

+  public final void setItems(Collection suggestions) {

+    setItems(suggestions.iterator());

+  }

+

+  void shiftSelection(int shift) {

+    int newSelect = getSelectedIndex() + shift;

+    if (newSelect >= super.getItemCount() || newSelect < 0

+        || newSelect >= startInvisible) {

+      return;

+    }

+    setSelection(getItem(newSelect));

+  }

+

+  /**

+   * Ensures the existence of the given item and returns it.

+   * 

+   * @param itemIndex item index to ensure

+   * @return associated item

+   */

+  private Item ensureItem(int itemIndex) {

+    for (int i = super.getItemCount(); i <= itemIndex; i++) {

+      Item item = new Item(i);

+      getLayout().setWidget(i, 0, item);

+    }

+    return getItem(itemIndex);

+  }

+

+  /**

+   * Sets the suggestions associated with this picker.

+   * 

+   */

+  private final void setItems(Iterator suggestions) {

+    int itemCount = 0;

+

+    // Ensure all needed items exist and set each item's html to the given

+    // suggestion.

+    while (suggestions.hasNext()) {

+      Item item = ensureItem(itemCount);

+      format(item.getElement(), suggestions.next());

+      ++itemCount;

+    }

+

+    if (itemCount == 0) {

+      throw new IllegalStateException(

+          "Must set at least one item in a SuggestPicker");

+    }

+

+    // Render visible all needed cells.

+    int min = Math.min(itemCount, super.getItemCount());

+    for (int i = startInvisible; i < min; i++) {

+      setVisible(i, true);

+    }

+

+    // Render invisible all useless cells.

+    startInvisible = itemCount;

+    for (int i = itemCount; i < super.getItemCount(); i++) {

+      setVisible(i, false);

+    }

+  }

+

+  /**

+   * Sets whether the given item is visible.

+   * 

+   * @param itemIndex item index

+   * @param visible visible boolean

+   */

+  private void setVisible(int itemIndex, boolean visible) {

+    UIObject.setVisible(getLayout().getRowFormatter().getElement(itemIndex),

+        visible);

+  }

+

+}
\ No newline at end of file