Fixes Issue #1030.
Converts Tree's internal images to use an ImageBundle and deprecates
setImageBase and getImageBase (though they still function properly for
backwards compatibility). It also adds an example to the doc to illustrate
how to override Tree's default images.

Review by: bruce



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1053 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/javadoc/com/google/gwt/examples/TreeImagesExample.java b/user/javadoc/com/google/gwt/examples/TreeImagesExample.java
new file mode 100644
index 0000000..1b711cc
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/TreeImagesExample.java
@@ -0,0 +1,34 @@
+package com.google.gwt.examples;
+
+import com.google.gwt.core.client.EntryPoint;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.user.client.ui.AbstractImagePrototype;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.Tree;
+import com.google.gwt.user.client.ui.TreeImages;
+
+public class TreeImagesExample implements EntryPoint {
+  
+  /**
+   * Allows us to override Tree default images. If we don't override one of the
+   * methods, the default will be used.
+   */
+  interface MyTreeImages extends TreeImages {
+    
+    /**
+     * @gwt.resource downArrow.png
+     */
+    AbstractImagePrototype treeOpen();
+    
+    /** 
+     * @gwt.resource rightArrow.png
+     */
+    AbstractImagePrototype treeClosed();
+  }
+  
+  public void onModuleLoad() {
+    TreeImages images = (TreeImages)GWT.create(MyTreeImages.class);
+    Tree tree = new Tree(images);
+    RootPanel.get().add(tree);
+  }
+}
diff --git a/user/src/com/google/gwt/user/client/ui/Tree.java b/user/src/com/google/gwt/user/client/ui/Tree.java
index 56e22fe..77bdc5c 100644
--- a/user/src/com/google/gwt/user/client/ui/Tree.java
+++ b/user/src/com/google/gwt/user/client/ui/Tree.java
@@ -49,13 +49,73 @@
     HasFocus {
 
   /**
+   * Provides images to support the the deprecated case where a url prefix is
+   * passed in through {@link Tree#setImageBase(String)}. This class is used
+   * in such a way that it will be completely removed by the compiler if the
+   * deprecated methods, {@link Tree#setImageBase(String)} and
+   * {@link Tree#getImageBase()}, are not called.
+   */
+  private static class ImagesFromImageBase implements TreeImages {
+    /**
+     * A convience image prototype that implements
+     * {@link AbstractImagePrototype#applyTo(Image)} for a specified image
+     * name.
+     */
+    private class Prototype extends AbstractImagePrototype {
+      private final String imageUrl;
+
+      Prototype(String url) {
+        imageUrl = url;
+      }
+
+      public void applyTo(Image image) {
+        image.setUrl(baseUrl + imageUrl);
+      }
+
+      public Image createImage() {
+        // NOTE: This class is only used internally and, therefore only needs
+        // to support applyTo(Image).
+        throw new UnsupportedOperationException("createImage is unsupported.");
+      }
+
+      public String getHTML() {
+        // NOTE: This class is only used internally and, therefore only needs
+        // to support applyTo(Image).
+        throw new UnsupportedOperationException("getHTML is unsupported.");
+      }
+    }
+
+    private final String baseUrl;
+
+    ImagesFromImageBase(String baseUrl) {
+      this.baseUrl = baseUrl;
+    }
+
+    public AbstractImagePrototype treeClosed() {
+      return new Prototype("tree_closed.gif");
+    }
+    
+    public AbstractImagePrototype treeLeaf() {
+      return new Prototype("tree_white.gif");
+    }
+
+    public AbstractImagePrototype treeOpen() {
+      return new Prototype("tree_open.gif");
+    }
+
+    String getBaseUrl() {
+      return baseUrl;
+    }
+  }
+
+  /**
    * Map of TreeItem.ContentPanel --> Tree Items.
    */
   private final Set childWidgets = new HashSet();
   private TreeItem curSelection;
   private final Element focusable;
   private FocusListenerCollection focusListeners;
-  private String imageBase = GWT.getModuleBaseURL();
+  private TreeImages images;
   private KeyboardListenerCollection keyboardListeners;
   private TreeListenerCollection listeners;
   private MouseListenerCollection mouseListeners = null;
@@ -71,6 +131,16 @@
    * Constructs an empty tree.
    */
   public Tree() {
+    this((TreeImages) GWT.create(TreeImages.class));
+  }
+
+  /**
+   * Constructs a tree that uses the specified image bundle for images.
+   * 
+   * @param images a bundle that provides tree specific images
+   */
+  public Tree(TreeImages images) {
+    this.images = images;
     setElement(DOM.createDiv());
     DOM.setStyleAttribute(getElement(), "position", "relative");
     focusable = FocusPanel.impl.createFocusable();
@@ -217,9 +287,13 @@
    * 
    * @return the tree's image package
    * @see #setImageBase
+   * @deprecated Use {@link #Tree(TreeImages)} as it provides a more efficent
+   *             and manageable way to supply a set of images to be used within
+   *             a tree.
    */
   public String getImageBase() {
-    return imageBase;
+    return (images instanceof ImagesFromImageBase)
+        ? ((ImagesFromImageBase) images).getBaseUrl() : GWT.getModuleBaseURL();
   }
 
   /**
@@ -375,14 +449,14 @@
               if (!curSelection.getState()) {
                 curSelection.setState(true);
               } else if (curSelection.getChildCount() > 0) {
-                  setSelectedItem(curSelection.getChild(0));
+                setSelectedItem(curSelection.getChild(0));
               }
               DOM.eventPreventDefault(event);
               break;
             }
           }
         }
-        
+
         // Intentional fallthrough.
       case Event.ONKEYUP:
         if (eventType == Event.ONKEYUP) {
@@ -470,9 +544,14 @@
    * Sets the base URL under which this tree will find its default images. These
    * images must be named "tree_white.gif", "tree_open.gif", and
    * "tree_closed.gif".
+   * 
+   * @param baseUrl
+   * @deprecated Use {@link #Tree(TreeImages)} as it provides a more efficent
+   *             and manageable way to supply a set of images to be used within
+   *             a tree.
    */
   public void setImageBase(String baseUrl) {
-    imageBase = baseUrl;
+    images = new ImagesFromImageBase(baseUrl);
     root.updateStateRecursive();
   }
 
@@ -521,17 +600,17 @@
 
   /**
    * Indicates if keyboard navigation is enabled for the Tree and for a given
-   * TreeItem.  Subclasses of Tree can override this function to selectively
+   * TreeItem. Subclasses of Tree can override this function to selectively
    * enable or disable keyboard navigation.
    * 
    * @param currentItem the currently selected TreeItem
    * @return <code>true</code> if the Tree will response to arrow keys by
-   *           changing the currently selected item
+   *         changing the currently selected item
    */
   protected boolean isKeyboardNavigationEnabled(TreeItem currentItem) {
     return true;
   }
-  
+
   protected void onAttach() {
     super.onAttach();
 
@@ -584,6 +663,10 @@
     return childWidgets;
   }
 
+  TreeImages getImages() {
+    return images;
+  }
+
   /**
    * Collects parents going up the element tree, terminated at the tree root.
    */
@@ -602,7 +685,7 @@
 
     TreeItem item = findItemByChain(chain, 0, root);
     if (item != null) {
-      if (DOM.compare(item.getImageElement(), hElem)) {
+      if (DOM.isOrHasChild(item.getImageElement(), hElem)) {
         item.setState(!item.getState(), true);
         return true;
       } else if (DOM.isOrHasChild(item.getElement(), hElem)) {
diff --git a/user/src/com/google/gwt/user/client/ui/TreeImages.java b/user/src/com/google/gwt/user/client/ui/TreeImages.java
new file mode 100644
index 0000000..d34f0fd
--- /dev/null
+++ b/user/src/com/google/gwt/user/client/ui/TreeImages.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ImageBundle;
+
+/**
+ * An {@link ImageBundle} that provides images for
+ * {@link com.google.gwt.user.client.ui.Tree}.
+ * 
+ * <p>
+ * <h3>Example</h3> {@example com.google.gwt.examples.TreeImagesExample}
+ * </p>
+ */
+public interface TreeImages extends ImageBundle {
+
+  /**
+   * An image indicating an open branch.
+   * 
+   * @return a prototye of this image
+   */
+  AbstractImagePrototype treeOpen();
+
+  /**
+   * An image indicating a closed branch.
+   * 
+   * @return a prototype of this image
+   */
+  AbstractImagePrototype treeClosed();
+
+  /**
+   * An image indicating a leaf.
+   * 
+   * @return a prototype of this image
+   */
+  AbstractImagePrototype treeLeaf();
+}
diff --git a/user/src/com/google/gwt/user/client/ui/TreeItem.java b/user/src/com/google/gwt/user/client/ui/TreeItem.java
index 31bd74e..be27ae3 100644
--- a/user/src/com/google/gwt/user/client/ui/TreeItem.java
+++ b/user/src/com/google/gwt/user/client/ui/TreeItem.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * 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
@@ -18,8 +18,8 @@
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
 
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Vector;
 
 /**
  * An item that can be contained within a
@@ -65,9 +65,10 @@
     }
   }
 
-  private Vector children = new Vector();
+  private ArrayList children = new ArrayList();
   private ContentPanel contentPanel;
-  private Element itemTable, contentElem, imgElem, childSpanElem;
+  private Element itemTable, contentElem, childSpanElem;
+  private final Image statusImage = new Image();
   private boolean open;
   private TreeItem parent;
   private boolean selected;
@@ -82,7 +83,6 @@
     itemTable = DOM.createTable();
     contentElem = DOM.createSpan();
     childSpanElem = DOM.createSpan();
-    imgElem = DOM.createImg();
 
     // Uses the following Element hierarchy:
     // <div (handle)>
@@ -106,7 +106,7 @@
 
     DOM.appendChild(getElement(), itemTable);
     DOM.appendChild(getElement(), childSpanElem);
-    DOM.appendChild(tdImg, imgElem);
+    DOM.appendChild(tdImg, statusImage.getElement());
     DOM.appendChild(tdContent, contentElem);
 
     DOM.setStyleAttribute(contentElem, "display", "inline");
@@ -421,7 +421,7 @@
     }
   }
 
-  Vector getChildren() {
+  ArrayList getChildren() {
     return children;
   }
 
@@ -434,7 +434,7 @@
   }
 
   Element getImageElement() {
-    return imgElem;
+    return statusImage.getElement();
   }
 
   int getTreeTop() {
@@ -449,14 +449,6 @@
     return ret;
   }
 
-  String imgSrc(String img) {
-    if (tree == null) {
-      return img;
-    }
-    String src = tree.getImageBase() + img;
-    return src;
-  }
-
   void setParentItem(TreeItem parent) {
     this.parent = parent;
   }
@@ -489,9 +481,16 @@
   }
 
   void updateState() {
+    // If the tree hasn't been set, there is no visual state to update.
+    if (tree == null) {
+      return;
+    }
+    
+    TreeImages images = tree.getImages();
+    
     if (children.size() == 0) {
       UIObject.setVisible(childSpanElem, false);
-      DOM.setElementProperty(imgElem, "src", imgSrc("tree_white.gif"));
+      images.treeLeaf().applyTo(statusImage);
       return;
     }
 
@@ -499,10 +498,10 @@
     // or the children will always take up space.
     if (open) {
       UIObject.setVisible(childSpanElem, true);
-      DOM.setElementProperty(imgElem, "src", imgSrc("tree_open.gif"));
+      images.treeOpen().applyTo(statusImage);
     } else {
       UIObject.setVisible(childSpanElem, false);
-      DOM.setElementProperty(imgElem, "src", imgSrc("tree_closed.gif"));
+      images.treeClosed().applyTo(statusImage);
     }
   }
 
diff --git a/user/src/com/google/gwt/user/public/tree_closed.gif b/user/src/com/google/gwt/user/client/ui/treeClosed.gif
similarity index 100%
rename from user/src/com/google/gwt/user/public/tree_closed.gif
rename to user/src/com/google/gwt/user/client/ui/treeClosed.gif
Binary files differ
diff --git a/user/src/com/google/gwt/user/public/tree_white.gif b/user/src/com/google/gwt/user/client/ui/treeLeaf.gif
similarity index 100%
rename from user/src/com/google/gwt/user/public/tree_white.gif
rename to user/src/com/google/gwt/user/client/ui/treeLeaf.gif
Binary files differ
diff --git a/user/src/com/google/gwt/user/public/tree_open.gif b/user/src/com/google/gwt/user/client/ui/treeOpen.gif
similarity index 100%
rename from user/src/com/google/gwt/user/public/tree_open.gif
rename to user/src/com/google/gwt/user/client/ui/treeOpen.gif
Binary files differ