PopupPanel positioning changes. Currently, PopupPanel.center() requires that
the popup be showing before this call is made. The center() method has been
changed so that the popup will be centered first, and then shown if the popup
is not already showing. Now, developers no longer have to call show() before
center(), which prevents the effect of the popup jumping from its original
position to its centered position. 

PopupPanel positioning changes. A new method,
PopupPanel.setPopupPositionAndShow(PopupPanel.PositionCallback) has been added
to allow developers to position a popup before it is shown.
PopupPanel.PositionCallback is an interface with one method: setPosition(int
offsetWidth, int offsetHeight). Since the width and height of the popup are
made available to the developer, positioning based on the width and height of
the popup can be done even before the popup is shown. Previously, developers
would have to show the popup, and then set its position. This would cause the
"jump" effect described above. PopupPanelExample has been updated to provide an
example of this method's usage. 

Fix issue #1231. PopupPanel's show() method has been changed to set the
positioning of the popup to absolute BEFORE the popup is attached to the DOM.
This prevents the popup from starting out in its static position, and jumping
to its absolute position when shown. 

When a PopupPanel is constructed, its left and top positions are initialized to
(0,0). This is done to handle the case where the PopupPanel is shown before its
position is set. If left and top are not defined, then the PopupPanel will
appear at an undefined location on the screen, which may cause the annoying
effect of the scrollbars quickly appearing and disappearing. 

When a PopupPanel is hidden, it is detached from the DOM. Since PopupPanel
inherits from AbsolutePanel, detaching from the DOM means that the positioning
of the PopupPanel will be changed to "static", and the left and top attributes
will be cleared out. Unfortunately, when the PopupPanel is shown again, its
left and top attributes will be undefined. AbsolutePanel's
changeToStaticPositioning() method has been changed so that the the left and
top attributes are no longer cleared out; only the positioning is changed to
static.

Issue: 1231
Patch by: rdayal
Review by: jgw



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1243 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/kitchensink/src/com/google/gwt/sample/kitchensink/client/Popups.java b/samples/kitchensink/src/com/google/gwt/sample/kitchensink/client/Popups.java
index 070fa08..1e24708 100644
--- a/samples/kitchensink/src/com/google/gwt/sample/kitchensink/client/Popups.java
+++ b/samples/kitchensink/src/com/google/gwt/sample/kitchensink/client/Popups.java
@@ -127,10 +127,7 @@
       p.show();
     } else if (sender == dialogButton) {
       DialogBox dlg = new MyDialog();
-      dlg.show();
-      dlg.setVisible(false);
       dlg.center();
-      dlg.setVisible(true);
     }
   }
 
diff --git a/user/javadoc/com/google/gwt/examples/PopupPanelExample.java b/user/javadoc/com/google/gwt/examples/PopupPanelExample.java
index b1662df..c87945e 100644
--- a/user/javadoc/com/google/gwt/examples/PopupPanelExample.java
+++ b/user/javadoc/com/google/gwt/examples/PopupPanelExample.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
@@ -22,8 +22,9 @@
 import com.google.gwt.user.client.ui.PopupPanel;
 import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.Widget;
+import com.google.gwt.user.client.Window;
 
-public class PopupPanelExample implements EntryPoint, ClickListener {
+public class PopupPanelExample implements EntryPoint {
 
   private static class MyPopup extends PopupPanel {
 
@@ -40,14 +41,40 @@
   }
 
   public void onModuleLoad() {
-    Button b = new Button("Click me");
-    b.addClickListener(this);
+    Button b1 = new Button("Click me to show popup");
+    b1.addClickListener(new ClickListener() {
+      public void onClick(Widget sender) {
+        // Instantiate the popup and show it.
+        new MyPopup().show();
+      }
+    });
 
-    RootPanel.get().add(b);
-  }
+    RootPanel.get().add(b1);
 
-  public void onClick(Widget sender) {
-    // Instantiate the popup and show it.
-    new MyPopup().show();
+    Button b2 = new Button("Click me to show popup partway across the screen");
+
+    b2.addClickListener(new ClickListener() {
+      public void onClick(Widget sender) {
+        // Create the new popup.
+        final MyPopup popup = new MyPopup();
+        // Position the popup 1/3rd of the way down and across the screen, and
+        // show the popup. Since the position calculation is based on the
+        // offsetWidth and offsetHeight of the popup, you have to use the
+        // setPopupPositionAndShow(callback) method. The alternative would
+        // be to call show(), calculate the left and top positions, and
+        // call setPopupPosition(left, top). This would have the ugly side
+        // effect of the popup jumping from its original position to its
+        // new position.
+        popup.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
+          public void setPosition(int offsetWidth, int offsetHeight) {
+            int left = (Window.getClientWidth() - offsetWidth) / 3;
+            int top = (Window.getClientHeight() - offsetHeight) / 3;
+            popup.setPopupPosition(left, top);
+          }
+        });
+      }
+    });
+
+    RootPanel.get().add(b2);
   }
 }
diff --git a/user/src/com/google/gwt/user/client/ui/AbsolutePanel.java b/user/src/com/google/gwt/user/client/ui/AbsolutePanel.java
index 1225002..852707a 100644
--- a/user/src/com/google/gwt/user/client/ui/AbsolutePanel.java
+++ b/user/src/com/google/gwt/user/client/ui/AbsolutePanel.java
@@ -41,9 +41,7 @@
    *
    * @param elem the DOM element 
    */
-  private static void changeToStaticPositioning(Element elem) {
-    DOM.setStyleAttribute(elem, "left", "");
-    DOM.setStyleAttribute(elem, "top", "");
+  private static void changeToStaticPositioning(Element elem) {  
     DOM.setStyleAttribute(elem, "position", "static");
   }
 
diff --git a/user/src/com/google/gwt/user/client/ui/PopupPanel.java b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
index e42eada..a83c760 100644
--- a/user/src/com/google/gwt/user/client/ui/PopupPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
@@ -42,12 +42,32 @@
 public class PopupPanel extends SimplePanel implements SourcesPopupEvents,
     EventPreview {
 
+  /**
+   * A callback that is used to set the position of a {@link PopupPanel} right
+   * before it is shown.
+   */
+  public interface PositionCallback {
+
+    /**
+     * Provides the opportunity to set the position of the PopupPanel right
+     * before the PopupPanel is shown. The offsetWidth and offsetHeight values
+     * of the PopupPanel are made available to allow for positioning based on
+     * its size.
+     *
+     * @param offsetWidth the offsetWidth of the PopupPanel
+     * @param offsetHeight the offsetHeight of the PopupPanel
+     * @see PopupPanel#setPopupPositionAndShow(PositionCallback)
+     */
+    public void setPosition(int offsetWidth, int offsetHeight);
+  }
+
   private static final PopupImpl impl = (PopupImpl) GWT.create(PopupImpl.class);
 
   private boolean autoHide, modal, showing;
 
   // Used to track requested size across changing child widgets
   private String desiredHeight;
+
   private String desiredWidth;
 
   private PopupListenerCollection popupListeners;
@@ -58,6 +78,11 @@
    */
   public PopupPanel() {
     super(impl.createElement());
+
+    // Default position of popup should be in the upper-left corner of the
+    // window. By setting a default position, the popup will not appear in
+    // an undefined location if it is shown before its position is set.
+    setPopupPosition(0,0);
   }
 
   /**
@@ -92,23 +117,24 @@
   }
 
   /**
-   * Centers the popup in the browser window.
-   * 
-   * <p>
-   * Note that the popup must be shown before this method is called.
-   * </p>
+   * Centers the popup in the browser window and shows it. If the popup was
+   * already showing, then the popup is centered. 
    */
   public void center() {
-    // Centering will not work properly until the panel is shown, because it
-    // cannot be measured until it is attached to the DOM.
-    if (!showing) {
-      throw new IllegalStateException("PopupPanel must be shown before it may "
-          + "be centered.");
+    boolean initiallyShowing = showing;
+
+    if (!initiallyShowing) {
+      setVisible(false);
+      show();
     }
 
     int left = (Window.getClientWidth() - getOffsetWidth()) / 2;
     int top = (Window.getClientHeight() - getOffsetHeight()) / 2;
     setPopupPosition(Window.getScrollLeft() + left, Window.getScrollTop() + top);
+
+    if (!initiallyShowing) {
+      setVisible(true);
+    }
   }
 
   /**
@@ -303,6 +329,24 @@
     DOM.setStyleAttribute(elem, "top", top + "px");
   }
 
+  /**
+   * Sets the popup's position using a {@link PositionCallback}, and shows
+   * the popup. The callback allows positioning to be performed based on
+   * the offsetWidth and offsetHeight of the popup, which are normally
+   * not available until the popup is showing. By positioning the popup
+   * before it is shown, the the popup will not jump from its original
+   * position to the new position.
+   *
+   * @param callback the callback to set the position of the popup
+   * @see PositionCallback#setPosition(int offsetWidth, int offsetHeight)
+   */
+  public void setPopupPositionAndShow(PositionCallback callback) {
+    setVisible(false);
+    show();
+    callback.setPosition(getOffsetWidth(), getOffsetHeight());
+    setVisible(true);
+  }
+
   public void setTitle(String title) {
     Element containerElement = getContainerElement();
     if (title == null || title.length() == 0) {
@@ -362,8 +406,12 @@
     showing = true;
     DOM.addEventPreview(this);
 
-    RootPanel.get().add(this);
+    // Set the position attribute, and then attach to the DOM. Otherwise,
+    // the PopupPanel will appear to 'jump' from its static/relative position
+    // to its absolute position (issue #1231).
     DOM.setStyleAttribute(getElement(), "position", "absolute");
+    RootPanel.get().add(this);
+
     impl.onShow(getElement());
   }