Introduces Widget.LoadingDelegate, to allow a widget owner to act when
a widget is added to or removed from the document without subclassing
it. E.g. to attach to an event bus, or fire RPC requests.

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8770 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/event/logical/shared/AttachEvent.java b/user/src/com/google/gwt/event/logical/shared/AttachEvent.java
new file mode 100644
index 0000000..7723ac5
--- /dev/null
+++ b/user/src/com/google/gwt/event/logical/shared/AttachEvent.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2010 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.event.logical.shared;
+
+import com.google.gwt.event.shared.EventHandler;
+import com.google.gwt.event.shared.GwtEvent;
+
+/**
+ * Fired when the event source is attached to the browser's document
+ * or detached from it.
+ */
+public class AttachEvent extends GwtEvent<AttachEvent.Handler> {
+
+  /**
+   * Implemented by objects that handle {@link AttachEvent}.
+   */
+  public interface Handler extends EventHandler {
+    void onAttach(AttachEvent event);
+    void onDetach(AttachEvent event);
+  }
+
+  /**
+   * The event type.
+   */
+  static Type<AttachEvent.Handler> TYPE;
+
+  /**
+   * Fires a {@link AttachEvent} on all registered handlers in the handler source.
+   *
+   * @param <S> The handler source type
+   * @param source the source of the handlers
+   * @param attached whether to announce an attach or detach
+   */
+  public static <S extends HasAttachHandlers> void fire(S source,
+      boolean attached) {
+    if (TYPE != null) {
+      AttachEvent event = new AttachEvent(attached);
+      source.fireEvent(event);
+    }
+  }
+
+  /**
+   * Ensures the existence of the handler hook and then returns it.
+   *
+   * @return returns a handler hook
+   */
+  public static Type<AttachEvent.Handler> getType() {
+    if (TYPE == null) {
+      TYPE = new Type<AttachEvent.Handler>();
+    }
+    return TYPE;
+  }
+
+  private final boolean attached;
+
+  /**
+   * Construct a new {@link AttachEvent}.
+   *
+   * @param width the new width
+   * @param height the new height
+   */
+  protected AttachEvent(boolean loaded) {
+    this.attached = loaded;
+  }
+
+  @Override
+  public final Type<AttachEvent.Handler> getAssociatedType() {
+    return TYPE;
+  }
+
+  /**
+   * @return true if this event announces that the source has been attached,
+   * false if it has been detached
+   */
+  public boolean isAttached() {
+    return attached;
+  }
+
+  @Override
+  public String toDebugString() {
+    assertLive();
+    return super.toDebugString() + " attached = " + attached;
+  }
+
+  @Override
+  protected void dispatch(AttachEvent.Handler handler) {
+    if (attached) {
+      handler.onAttach(this);
+    } else {
+      handler.onDetach(this);
+    }
+  }
+}
diff --git a/user/src/com/google/gwt/event/logical/shared/HasAttachHandlers.java b/user/src/com/google/gwt/event/logical/shared/HasAttachHandlers.java
new file mode 100644
index 0000000..c01efe3
--- /dev/null
+++ b/user/src/com/google/gwt/event/logical/shared/HasAttachHandlers.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2008 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.event.logical.shared;
+
+import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.event.shared.HasHandlers;
+
+/**
+ * A widget that implements this interface is a public source of
+ * {@link AttachEvent} events.
+ */
+public interface HasAttachHandlers extends HasHandlers {
+  /**
+   * Adds a {@link AttachEvent} handler.
+   *
+   * @param handler the handler
+   * @return the handler registration
+   */
+  HandlerRegistration addAttachHandler(AttachEvent.Handler handler);
+}
diff --git a/user/src/com/google/gwt/event/shared/HandlerManager.java b/user/src/com/google/gwt/event/shared/HandlerManager.java
index 82e0565..9a8776d 100644
--- a/user/src/com/google/gwt/event/shared/HandlerManager.java
+++ b/user/src/com/google/gwt/event/shared/HandlerManager.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -19,15 +19,15 @@
 
 /**
  * Manager responsible for adding handlers to event sources and firing those
- * handlers on passed in events. Primitive ancestor of {@link EventBus}, 
+ * handlers on passed in events. Primitive ancestor of {@link EventBus},
  * and used at the core of {com.google.gwt.user.client.ui.Widget}.
- * 
+ *
  * @deprecated use {@link SimpleEventBus}.
  */
 @Deprecated
 public class HandlerManager implements HasHandlers {
 
-  private SimpleEventBus eventBus;
+  private final SimpleEventBus eventBus;
 
   // source of the events
   private final Object source;
@@ -36,7 +36,7 @@
    * Creates a handler manager with a source to be set on all events fired via
    * {@link #fireEvent(GwtEvent)}. Handlers will be fired in the order that they
    * are added.
-   * 
+   *
    * @param source the default event source
    */
   public HandlerManager(Object source) {
@@ -46,10 +46,11 @@
   /**
    * Creates a handler manager with the given source, specifying the order in
    * which handlers are fired.
-   * 
+   *
    * @param source the event source
    * @param fireInReverseOrder true to fire handlers in reverse order
    */
+  @SuppressWarnings("deprecation")
   public HandlerManager(Object source, boolean fireInReverseOrder) {
     eventBus = new SimpleEventBus(fireInReverseOrder);
     this.source = source;
@@ -57,7 +58,7 @@
 
   /**
    * Adds a handler.
-   * 
+   *
    * @param <H> The type of handler
    * @param type the event type associated with this handler
    * @param handler the handler
@@ -76,11 +77,11 @@
    * {@link UmbrellaException} and then re-thrown after all handlers have
    * completed. An exception thrown by a handler will not prevent other handlers
    * from executing.
-   * <p> 
+   * <p>
    * Note, any subclass should be very careful about overriding this method, as
    * adds/removes of handlers will not be safe except within this
    * implementation.
-   * 
+   *
    * @param event the event
    */
   public void fireEvent(GwtEvent<?> event) {
@@ -108,41 +109,44 @@
 
   /**
    * Gets the handler at the given index.
-   * 
+   *
    * @param <H> the event handler type
    * @param index the index
    * @param type the handler's event type
    * @return the given handler
    */
+  @SuppressWarnings("deprecation")
   public <H extends EventHandler> H getHandler(GwtEvent.Type<H> type, int index) {
     return eventBus.getHandler(type, index);
   }
 
   /**
    * Gets the number of handlers listening to the event type.
-   * 
+   *
    * @param type the event type
    * @return the number of registered handlers
    */
+  @SuppressWarnings("deprecation")
   public int getHandlerCount(Type<?> type) {
     return eventBus.getHandlerCount(type);
   }
 
   /**
    * Does this handler manager handle the given event type?
-   * 
+   *
    * @param e the event type
    * @return whether the given event type is handled
    */
+  @SuppressWarnings("deprecation")
   public boolean isEventHandled(Type<?> e) {
     return eventBus.isEventHandled(e);
   }
 
   /**
    * Removes the given handler from the specified event type.
-   * 
+   *
    * @param <H> handler type
-   * 
+   *
    * @param type the event type
    * @param handler the handler
    */
diff --git a/user/src/com/google/gwt/user/client/ui/Widget.java b/user/src/com/google/gwt/user/client/ui/Widget.java
index 6b6df98..43191cd 100644
--- a/user/src/com/google/gwt/user/client/ui/Widget.java
+++ b/user/src/com/google/gwt/user/client/ui/Widget.java
@@ -17,11 +17,14 @@
 
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.DomEvent;
+import com.google.gwt.event.logical.shared.AttachEvent;
+import com.google.gwt.event.logical.shared.AttachEvent.Handler;
+import com.google.gwt.event.logical.shared.HasAttachHandlers;
 import com.google.gwt.event.shared.EventHandler;
 import com.google.gwt.event.shared.GwtEvent;
-import com.google.gwt.event.shared.HandlerRegistration;
-import com.google.gwt.event.shared.HasHandlers;
 import com.google.gwt.event.shared.GwtEvent.Type;
+import com.google.gwt.event.shared.HandlerManager;
+import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Event;
 import com.google.gwt.user.client.EventListener;
@@ -31,7 +34,8 @@
  * support for receiving events from the browser and being added directly to
  * {@link com.google.gwt.user.client.ui.Panel panels}.
  */
-public class Widget extends UIObject implements EventListener, HasHandlers, IsWidget {
+public class Widget extends UIObject implements EventListener, HasAttachHandlers,
+    IsWidget {
 
   /**
    * This convenience method makes a null-safe call to
@@ -52,10 +56,14 @@
   int eventsToSink;
   private boolean attached;
   @SuppressWarnings("deprecation")
-  private com.google.gwt.event.shared.HandlerManager handlerManager;
+  private HandlerManager handlerManager;
   private Object layoutData;
   private Widget parent;
 
+  public HandlerRegistration addAttachHandler(Handler handler) {
+    return addHandler(handler, AttachEvent.getType());
+  }
+
   /**
    * Adds a native event handler to the widget and sinks the corresponding
    * native event. If you do not want to sink the native event, use the generic
@@ -286,8 +294,8 @@
    * </p>
    * <p>
    * It is strongly recommended that you override {@link #onLoad()} or
-   * {@link #doAttachChildren()} instead of this method to avoid
-   * inconsistencies between logical and physical attachment states.
+   * {@link #doAttachChildren()} instead of this method to avoid inconsistencies
+   * between logical and physical attachment states.
    * </p>
    * <p>
    * Subclasses that override this method must call
@@ -330,8 +338,8 @@
    * </p>
    * <p>
    * It is strongly recommended that you override {@link #onUnload()} or
-   * {@link #doDetachChildren()} instead of this method to avoid
-   * inconsistencies between logical and physical attachment states.
+   * {@link #doDetachChildren()} instead of this method to avoid inconsistencies
+   * between logical and physical attachment states.
    * </p>
    * <p>
    * Subclasses that override this method must call
@@ -369,16 +377,20 @@
 
   /**
    * This method is called immediately after a widget becomes attached to the
-   * browser's document.
+   * browser's document. This default implementation notifies the widgets
+   * {@link AttachEvent.Handler}s.
    */
   protected void onLoad() {
+    AttachEvent.fire(this, true);
   }
 
   /**
    * This method is called immediately before a widget will be detached from the
-   * browser's document.
+   * browser's document. This default implementation notifies the widgets
+   * {@link AttachEvent.Handler}s.
    */
   protected void onUnload() {
+    AttachEvent.fire(this, false);
   }
 
   /**
diff --git a/user/test/com/google/gwt/user/client/ui/WidgetOnLoadTest.java b/user/test/com/google/gwt/user/client/ui/WidgetOnLoadTest.java
index 430deda..af02983 100644
--- a/user/test/com/google/gwt/user/client/ui/WidgetOnLoadTest.java
+++ b/user/test/com/google/gwt/user/client/ui/WidgetOnLoadTest.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -15,6 +15,7 @@
  */
 package com.google.gwt.user.client.ui;
 
+import com.google.gwt.event.logical.shared.AttachEvent;
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.Element;
@@ -24,16 +25,6 @@
  */
 public class WidgetOnLoadTest extends GWTTestCase {
 
-  public String getModuleName() {
-    return "com.google.gwt.user.User";
-  }
-
-  static int orderIndex;
-
-  static boolean isElementAttached(Element elem) {
-    return DOM.isOrHasChild(RootPanel.getBodyElement(), elem);
-  }
-
   static class TestPanel extends FlowPanel {
     int onAttachOrder;
     int onLoadOrder;
@@ -42,22 +33,26 @@
     boolean domAttachedOnLoad;
     boolean domAttachedOnUnload;
 
+    @Override
     protected void onAttach() {
       onAttachOrder = ++orderIndex;
       super.onAttach();
     }
 
+    @Override
+    protected void onDetach() {
+      onDetachOrder = ++orderIndex;
+      super.onDetach();
+    }
+
+    @Override
     protected void onLoad() {
       onLoadOrder = ++orderIndex;
       domAttachedOnLoad = isElementAttached(getElement());
       super.onLoad();
     }
 
-    protected void onDetach() {
-      onDetachOrder = ++orderIndex;
-      super.onDetach();
-    }
-
+    @Override
     protected void onUnload() {
       onUnloadOrder = ++orderIndex;
       domAttachedOnUnload = isElementAttached(getElement());
@@ -73,22 +68,26 @@
     boolean domAttachedOnLoad;
     boolean domAttachedOnUnload;
 
+    @Override
     protected void onAttach() {
       onAttachOrder = ++orderIndex;
       super.onAttach();
     }
 
+    @Override
+    protected void onDetach() {
+      onDetachOrder = ++orderIndex;
+      super.onDetach();
+    }
+
+    @Override
     protected void onLoad() {
       domAttachedOnLoad = isElementAttached(getElement());
       onLoadOrder = ++orderIndex;
       super.onLoad();
     }
 
-    protected void onDetach() {
-      onDetachOrder = ++orderIndex;
-      super.onDetach();
-    }
-
+    @Override
     protected void onUnload() {
       onUnloadOrder = ++orderIndex;
       domAttachedOnUnload = isElementAttached(getElement());
@@ -96,9 +95,34 @@
     }
   }
 
+  static int orderIndex;
+
+  static boolean isElementAttached(Element elem) {
+    return DOM.isOrHasChild(RootPanel.getBodyElement(), elem);
+  }
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.user.User";
+  }
+
   public void testOnLoadAndUnloadOrder() {
+    class TestAttachHandler implements AttachEvent.Handler {
+      int delegateAttachOrder;
+      int delegateDetachOrder;
+
+      public void onAttach(AttachEvent event) {
+        delegateAttachOrder = ++orderIndex;
+      }
+      public void onDetach(AttachEvent event) {
+        delegateDetachOrder = ++orderIndex;
+      }
+    }
+
     TestPanel tp = new TestPanel();
     TestWidget tw = new TestWidget();
+    TestAttachHandler ta = new TestAttachHandler();
+    tw.addAttachHandler(ta);
 
     tp.add(tw);
     RootPanel.get().add(tp);
@@ -109,7 +133,9 @@
     assertTrue(tp.onAttachOrder < tp.onLoadOrder);
     assertTrue(tp.onDetachOrder < tp.onUnloadOrder);
     assertTrue(tw.onAttachOrder < tw.onLoadOrder);
+    assertTrue(tw.onLoadOrder < ta.delegateAttachOrder);
     assertTrue(tw.onDetachOrder < tw.onUnloadOrder);
+    assertTrue(tw.onUnloadOrder < ta.delegateDetachOrder);
 
     // Also trivial. Ensure that the panel's onAttach/onDetach is called before
     // its child's onAttach/onDetach.