Adds PopupPanel.setGlassEnabled(), along with related tests and sample
changes.
Review: http://gwt-code-reviews.appspot.com/93809

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@6709 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwDialogBox.java b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwDialogBox.java
index 1d04d8f..b13b6f2 100644
--- a/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwDialogBox.java
+++ b/samples/showcase/src/com/google/gwt/sample/showcase/client/content/popups/CwDialogBox.java
@@ -102,6 +102,7 @@
   public Widget onInitialize() {
     // Create the dialog box
     final DialogBox dialogBox = createDialogBox();
+    dialogBox.setGlassEnabled(true);
     dialogBox.setAnimationEnabled(true);
 
     // Create a button to show the dialog Box
diff --git a/user/javadoc/com/google/gwt/examples/DialogBoxExample.java b/user/javadoc/com/google/gwt/examples/DialogBoxExample.java
index e1adac3..d39c350 100644
--- a/user/javadoc/com/google/gwt/examples/DialogBoxExample.java
+++ b/user/javadoc/com/google/gwt/examples/DialogBoxExample.java
@@ -30,6 +30,12 @@
       // Set the dialog box's caption.
       setText("My First Dialog");
 
+      // Enable animation.
+      setAnimationEnabled(true);
+
+      // Enable glass background.
+      setGlassEnabled(true);
+
       // DialogBox is a SimplePanel, so you have to set its widget property to
       // whatever you want its contents to be.
       Button ok = new Button("OK");
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 64eff3a..231ceee 100644
--- a/user/src/com/google/gwt/user/client/ui/PopupPanel.java
+++ b/user/src/com/google/gwt/user/client/ui/PopupPanel.java
@@ -21,9 +21,15 @@
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.EventTarget;
 import com.google.gwt.dom.client.NativeEvent;
+import com.google.gwt.dom.client.Style;
+import com.google.gwt.dom.client.Style.Display;
+import com.google.gwt.dom.client.Style.Position;
+import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.event.logical.shared.CloseEvent;
 import com.google.gwt.event.logical.shared.CloseHandler;
 import com.google.gwt.event.logical.shared.HasCloseHandlers;
+import com.google.gwt.event.logical.shared.ResizeEvent;
+import com.google.gwt.event.logical.shared.ResizeHandler;
 import com.google.gwt.event.shared.HandlerRegistration;
 import com.google.gwt.i18n.client.LocaleInfo;
 import com.google.gwt.user.client.Command;
@@ -67,6 +73,8 @@
  * <dd>the outside of the popup</dd>
  * <dt>.gwt-PopupPanel .popupContent</dt>
  * <dd>the wrapper around the content</dd>
+ * <dt>.gwt-PopupGlass</dt>
+ * <dd>the glass background behind the popup</dd>
  * </dl>
  */
 @SuppressWarnings("deprecation")
@@ -97,8 +105,8 @@
    * 
    * <ul>
    * <li>CENTER - Expand from the center of the popup</li>
-   * <li>ONE_WAY_CORNER - Expand from the top left corner, do not animate
-   * hiding </li>
+   * <li>ONE_WAY_CORNER - Expand from the top left corner, do not animate hiding
+   * </li>
    * </ul>
    */
   static enum AnimationType {
@@ -122,7 +130,12 @@
     /**
      * A boolean indicating whether we are showing or hiding the popup.
      */
-    private boolean showing = false;
+    private boolean showing;
+
+    /**
+     * A boolean indicating whether the glass element is currently attached.
+     */
+    private boolean glassShowing;
 
     /**
      * Create a new {@link ResizeAnimation}.
@@ -157,6 +170,8 @@
         // the animation. If we move this to onStart, the animation will look
         // choppy or not run at all.
         if (showing) {
+          maybeShowGlass();
+
           // 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).
@@ -184,6 +199,7 @@
     @Override
     protected void onComplete() {
       if (!showing) {
+        maybeShowGlass();
         RootPanel.get().remove(curPanel);
         impl.onHide(curPanel.getElement());
       }
@@ -244,7 +260,26 @@
           + "px)";
     }
 
+    /**
+     * Show or hide the glass.
+     */
+    private void maybeShowGlass() {
+      if (showing) {
+        if (curPanel.isGlassEnabled) {
+          Document.get().getBody().appendChild(curPanel.glass);
+          glassShowing = true;
+
+          Window.addResizeHandler(curPanel.glassResizer);
+          curPanel.glassResizer.onResize(null);
+        }
+      } else if (glassShowing) {
+        Document.get().getBody().removeChild(curPanel.glass);
+        glassShowing = false;
+      }
+    }
+
     private void onInstantaneousRun() {
+      maybeShowGlass();
       if (showing) {
         // Set the position attribute, and then attach to the DOM. Otherwise,
         // the PopupPanel will appear to 'jump' from its static/relative
@@ -276,6 +311,35 @@
   private static final PopupImpl impl = GWT.create(PopupImpl.class);
 
   /**
+   * Window resize handler used to keep the glass the proper size.
+   */
+  private ResizeHandler glassResizer = new ResizeHandler() {
+    public void onResize(ResizeEvent event) {
+      Style style = glass.getStyle();
+
+      int winWidth = Window.getClientWidth();
+      int winHeight = Window.getClientHeight();
+
+      // Hide the glass while checking the document size. Otherwise it would
+      // interfere with the measurement.
+      style.setDisplay(Display.NONE);
+      style.setWidth(0, Unit.PX);
+      style.setHeight(0, Unit.PX);
+
+      int width = Document.get().getScrollWidth();
+      int height = Document.get().getScrollHeight();
+
+      // Set the glass size to the larger of the window's client size or the
+      // document's scroll size.
+      style.setWidth(Math.max(width, winWidth), Unit.PX);
+      style.setHeight(Math.max(height, winHeight), Unit.PX);
+
+      // The size is set. Show the glass again.
+      style.setDisplay(Display.BLOCK);
+    }
+  };
+
+  /**
    * If true, animate the opening of this popup from the center. If false,
    * animate it open from top to bottom, and do not animate closing. Use false
    * to animate menus.
@@ -291,6 +355,16 @@
 
   private String desiredWidth;
 
+  /**
+   * The glass element.
+   */
+  private Element glass;
+
+  /**
+   * A boolean indicating that a glass element should be used.
+   */
+  private boolean isGlassEnabled;
+
   private boolean isAnimationEnabled = false;
 
   // the left style attribute in pixels
@@ -479,8 +553,8 @@
   }
 
   /**
-   * Returns <code>true</code> if the popup should be automatically hidden
-   * when the user clicks outside of it.
+   * Returns <code>true</code> if the popup should be automatically hidden when
+   * the user clicks outside of it.
    * 
    * @return true if autoHide is enabled, false if disabled
    */
@@ -489,6 +563,16 @@
   }
 
   /**
+   * Returns <code>true</code> if a glass element will be displayed under
+   * the {@link PopupPanel}.
+   * 
+   * @return true if enabled
+   */
+  public boolean isGlassEnabled() {
+    return isGlassEnabled;
+  }
+
+  /**
    * Returns <code>true</code> if keyboard or mouse events that do not target
    * the PopupPanel or its children should be ignored.
    * 
@@ -599,8 +683,8 @@
   }
 
   /**
-   * @deprecated Use the {@link HandlerRegistration#removeHandler}
-   * method on the object returned by {@link #addCloseHandler} instead
+   * @deprecated Use the {@link HandlerRegistration#removeHandler} method on the
+   *             object returned by {@link #addCloseHandler} instead
    */
   @Deprecated
   public void removePopupListener(PopupListener listener) {
@@ -622,6 +706,25 @@
   }
 
   /**
+   * When enabled, the background will be blocked with a semi-transparent pane
+   * the next time it is shown. If the PopupPanel is already visible, the
+   * glass will not be displayed until it is hidden and shown again.
+   * 
+   * @param enabled true to enable, false to disable
+   */
+  public void setGlassEnabled(boolean enabled) {
+    this.isGlassEnabled = enabled;
+    if (enabled && glass == null) {
+      glass = Document.get().createDivElement();
+      glass.setClassName("gwt-PopupGlass");
+
+      glass.getStyle().setPosition(Position.ABSOLUTE);
+      glass.getStyle().setLeft(0, Unit.PX);
+      glass.getStyle().setTop(0, Unit.PX);
+    }
+  }
+
+  /**
    * Sets the height of the panel's child widget. If the panel's child widget
    * has not been set, the height passed in will be cached and used to set the
    * height immediately after the child widget is set.
@@ -730,8 +833,8 @@
    * <code>visibility</code> style attribute. You need to call {@link #show()}
    * to actually attached/detach the {@link PopupPanel} to the page.
    * 
-   * @param visible <code>true</code> to show the object, <code>false</code>
-   *          to hide it
+   * @param visible <code>true</code> to show the object, <code>false</code> to
+   *          hide it
    * @see #show()
    * @see #hide()
    */
@@ -814,6 +917,16 @@
     return impl.getContainerElement(getPopupImplElement());
   }
 
+  /**
+   * Get the glass element used by this {@link PopupPanel}. The element is not
+   * created until it is enabled via {@link #setGlassEnabled(boolean)}.
+   * 
+   * @return the glass element, or null if not created
+   */
+  protected Element getGlassElement() {
+    return glass;
+  }
+
   @Override
   protected com.google.gwt.user.client.Element getStyleElement() {
     return impl.getStyleElement(getPopupImplElement());
@@ -889,11 +1002,11 @@
    * @param elt The Element on which <code>blur()</code> will be invoked
    */
   private native void blur(Element elt) /*-{
-            // Issue 2390: blurring the body causes IE to disappear to the background
-            if (elt.blur && elt != $doc.body) {
-              elt.blur();
-            }
-          }-*/;
+    // Issue 2390: blurring the body causes IE to disappear to the background
+    if (elt.blur && elt != $doc.body) {
+      elt.blur();
+    }
+  }-*/;
 
   /**
    * Does the event target one of the partner elements?
diff --git a/user/src/com/google/gwt/user/theme/chrome/public/gwt/chrome/chrome.css b/user/src/com/google/gwt/user/theme/chrome/public/gwt/chrome/chrome.css
index 91decf2..3fd7335 100644
--- a/user/src/com/google/gwt/user/theme/chrome/public/gwt/chrome/chrome.css
+++ b/user/src/com/google/gwt/user/theme/chrome/public/gwt/chrome/chrome.css
@@ -479,6 +479,12 @@
   overflow: hidden;
 }
 
+.gwt-PopupGlass {
+  background-color: #000;
+  opacity: 0.3;
+  filter: alpha(opacity=30);
+}
+
 .gwt-PushButton-up,
 .gwt-PushButton-up-hovering,
 .gwt-PushButton-up-disabled,
diff --git a/user/src/com/google/gwt/user/theme/chrome/public/gwt/chrome/chrome_rtl.css b/user/src/com/google/gwt/user/theme/chrome/public/gwt/chrome/chrome_rtl.css
index 8fae597..21d0021 100644
--- a/user/src/com/google/gwt/user/theme/chrome/public/gwt/chrome/chrome_rtl.css
+++ b/user/src/com/google/gwt/user/theme/chrome/public/gwt/chrome/chrome_rtl.css
@@ -479,6 +479,12 @@
   overflow: hidden;
 }
 
+.gwt-PopupGlass {
+  background-color: #000;
+  opacity: 0.3;
+  filter: alpha(opacity=30);
+}
+
 .gwt-PushButton-up,
 .gwt-PushButton-up-hovering,
 .gwt-PushButton-up-disabled,
diff --git a/user/src/com/google/gwt/user/theme/dark/public/gwt/dark/dark.css b/user/src/com/google/gwt/user/theme/dark/public/gwt/dark/dark.css
index 1bde5b3..89225c0 100644
--- a/user/src/com/google/gwt/user/theme/dark/public/gwt/dark/dark.css
+++ b/user/src/com/google/gwt/user/theme/dark/public/gwt/dark/dark.css
@@ -463,6 +463,12 @@
   overflow: hidden;
 }
 
+.gwt-PopupGlass {
+  background-color: #0cf;
+  opacity: 0.3;
+  filter: alpha(opacity=30);
+}
+
 .gwt-PushButton-up,
 .gwt-PushButton-up-hovering,
 .gwt-PushButton-up-disabled,
diff --git a/user/src/com/google/gwt/user/theme/dark/public/gwt/dark/dark_rtl.css b/user/src/com/google/gwt/user/theme/dark/public/gwt/dark/dark_rtl.css
index 881bd4d..5629801 100644
--- a/user/src/com/google/gwt/user/theme/dark/public/gwt/dark/dark_rtl.css
+++ b/user/src/com/google/gwt/user/theme/dark/public/gwt/dark/dark_rtl.css
@@ -463,6 +463,12 @@
   overflow: hidden;
 }
 
+.gwt-PopupGlass {
+  background-color: #0cf;
+  opacity: 0.3;
+  filter: alpha(opacity=30);
+}
+
 .gwt-PushButton-up,
 .gwt-PushButton-up-hovering,
 .gwt-PushButton-up-disabled,
diff --git a/user/src/com/google/gwt/user/theme/standard/public/gwt/standard/standard.css b/user/src/com/google/gwt/user/theme/standard/public/gwt/standard/standard.css
index fc62adb..4875b67 100644
--- a/user/src/com/google/gwt/user/theme/standard/public/gwt/standard/standard.css
+++ b/user/src/com/google/gwt/user/theme/standard/public/gwt/standard/standard.css
@@ -479,6 +479,12 @@
   overflow: hidden;
 }
 
+.gwt-PopupGlass {
+  background-color: #000;
+  opacity: 0.3;
+  filter: alpha(opacity=30);
+}
+
 .gwt-PushButton-up,
 .gwt-PushButton-up-hovering,
 .gwt-PushButton-up-disabled,
diff --git a/user/src/com/google/gwt/user/theme/standard/public/gwt/standard/standard_rtl.css b/user/src/com/google/gwt/user/theme/standard/public/gwt/standard/standard_rtl.css
index c6b7ee4..e2d75f3 100644
--- a/user/src/com/google/gwt/user/theme/standard/public/gwt/standard/standard_rtl.css
+++ b/user/src/com/google/gwt/user/theme/standard/public/gwt/standard/standard_rtl.css
@@ -479,6 +479,12 @@
   overflow: hidden;
 }
 
+.gwt-PopupGlass {
+  background-color: #000;
+  opacity: 0.3;
+  filter: alpha(opacity=30);
+}
+
 .gwt-PushButton-up,
 .gwt-PushButton-up-hovering,
 .gwt-PushButton-up-disabled,
diff --git a/user/test/com/google/gwt/user/client/ui/PopupTest.java b/user/test/com/google/gwt/user/client/ui/PopupTest.java
index 697c7f1..bab4190 100644
--- a/user/test/com/google/gwt/user/client/ui/PopupTest.java
+++ b/user/test/com/google/gwt/user/client/ui/PopupTest.java
@@ -17,10 +17,12 @@
 
 import com.google.gwt.dom.client.DivElement;
 import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.event.logical.shared.CloseEvent;
+import com.google.gwt.event.logical.shared.CloseHandler;
 import com.google.gwt.junit.DoNotRunWith;
 import com.google.gwt.junit.Platform;
 import com.google.gwt.junit.client.GWTTestCase;
-import com.google.gwt.user.client.Element;
 import com.google.gwt.user.client.Timer;
 import com.google.gwt.user.client.Window;
 
@@ -50,7 +52,7 @@
     }
 
     @Override
-    public Element getContainerElement() {
+    public com.google.gwt.user.client.Element getContainerElement() {
       return super.getContainerElement();
     }
 
@@ -172,6 +174,81 @@
     testDependantPopupPanel(primaryPopup);
   }
 
+  public void testGlassPanelDisabled() {
+    // Verify that the glass is disabled by default
+    PopupPanel popup = createPopupPanel();
+    assertFalse(popup.isGlassEnabled());
+    assertNull(popup.getGlassElement());
+
+    // Verify the glass panel is never created
+    popup.show();
+    assertNull(popup.getGlassElement());
+    popup.hide();
+  }
+
+  public void testGlassDisabledWhileShowing() {
+    // Show the popup and glass panel
+    PopupPanel popup = createPopupPanel();
+    popup.setGlassEnabled(true);
+    Element glass = popup.getGlassElement();
+    popup.show();
+
+    // Disable the glass panel and hide the popup
+    popup.setGlassEnabled(false);
+    assertTrue(isAttached(glass));
+    popup.hide();
+    assertFalse(isAttached(glass));
+
+    // Show the popup and verify that glass is no longer used
+    popup.show();
+    assertFalse(isAttached(glass));
+    popup.hide();
+  }
+
+  public void testGlassEnabled() {
+    // Verify that the glass is disabled by default
+    PopupPanel popup = createPopupPanel();
+    assertFalse(popup.isGlassEnabled());
+    assertNull(popup.getGlassElement());
+
+    // Enable the glass panel and verify it is created
+    popup.setGlassEnabled(true);
+    Element glass = popup.getGlassElement();
+    assertNotNull(glass);
+    assertFalse(isAttached(glass));
+
+    // Show the popup and verify the glass panel is added
+    popup.show();
+    assertTrue(isAttached(glass));
+
+    // Hide the popup and verify the glass panel is removed
+    popup.hide();
+    assertFalse(isAttached(glass));
+  }
+
+  public void testGlassEnabledWhileShowing() {
+    // Verify that the glass is disabled by default
+    PopupPanel popup = createPopupPanel();
+    assertFalse(popup.isGlassEnabled());
+    assertNull(popup.getGlassElement());
+
+    // Show the popup and enable the glass panel
+    popup.show();
+    popup.setGlassEnabled(true);
+    Element glass = popup.getGlassElement();
+    assertNotNull(glass);
+    assertFalse(isAttached(glass));
+
+    // Hide the popup and verify the glass panel is removed
+    popup.hide();
+    assertFalse(isAttached(glass));
+
+    // Show the popup and verify the glas is now used
+    popup.show();
+    assertTrue(isAttached(glass));
+    popup.hide();
+  }
+
   /**
    * Test that the onLoad method is only called once when showing the popup.
    */
@@ -256,8 +333,8 @@
 
     // Ensure that hiding the popup fires the appropriate events.
     delayTestFinish(1000);
-    popup.addPopupListener(new PopupListener() {
-      public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
+    popup.addCloseHandler(new CloseHandler<PopupPanel>() {
+      public void onClose(CloseEvent<PopupPanel> event) {
         finishTest();
       }
     });
@@ -342,4 +419,8 @@
       }
     }.schedule(2000);
   }
+
+  private boolean isAttached(Element elem) {
+    return Document.get().getBody().isOrHasChild(elem);
+  }
 }