/* * Copyright 2000-2014 Vaadin Ltd. * * 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.vaadin.client.ui; import java.util.logging.Level; import java.util.logging.Logger; import com.google.gwt.animation.client.Animation; import com.google.gwt.aria.client.Roles; import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.IFrameElement; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.Style; import com.google.gwt.dom.client.Style.BorderStyle; 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.user.client.DOM; import com.google.gwt.user.client.Window; 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.vaadin.client.AnimationUtil; import com.vaadin.client.AnimationUtil.AnimationEndListener; import com.vaadin.client.ApplicationConnection; import com.vaadin.client.BrowserInfo; import com.vaadin.client.ComponentConnector; import com.vaadin.client.ComputedStyle; import com.vaadin.client.Util; /** *
* In Vaadin UI this Overlay should always be used for all elements that * temporary float over other components like context menus etc. This is to deal * stacking order correctly with VWindow objects. *
* *
* The separate shadow element underneath the main overlay element is
* deprecated, and should not be used for new overlay
* components. CSS box-shadow should be used instead of a separate shadow
* element. Remember to include any vendor-prefixed versions to support all
* browsers that you need to. To cover all possible browsers that Vaadin 7
* supports, add -webkit-box-shadow
and the standard
* box-shadow
properties.
*
* For IE8, which doesn't support CSS box-shadow, you can use the proprietary * DropShadow filter. It doesn't provide the exact same features as box-shadow, * but it is suitable for graceful degradation. Other options are to use a * border or a pseudo-element underneath the overlay which mimics a shadow, or * any combination of these. *
* ** Read more about the DropShadow filter from Microsoft Developer Network *
*/ public class VOverlay extends PopupPanel implements CloseHandler* .v-shadow[-stylename] * ---------------------------------------------- * | .top-left | .top | .top-right | * |---------------|-----------|----------------| * | | | | * | .left | .center | .right | * | | | | * |---------------|-----------|----------------| * | .bottom-left | .bottom | .bottom-right | * ---------------------------------------------- ** * See default theme 'shadow.css' for implementation example. * * @deprecated See main JavaDoc for VOverlay */ @Deprecated private static final String SHADOW_HTML = ""; /** * Matches {@link PopupPanel}.ANIMATION_DURATION */ private static final int POPUP_PANEL_ANIMATION_DURATION = 200; /** * @deprecated See main JavaDoc for VOverlay */ @Deprecated private boolean sinkShadowEvents = false; public VOverlay() { super(); adjustZIndex(); } public VOverlay(boolean autoHide) { super(autoHide); adjustZIndex(); } public VOverlay(boolean autoHide, boolean modal) { super(autoHide, modal); adjustZIndex(); } /** * @deprecated See main JavaDoc for VOverlay. Use the other constructors * without the
showShadow
parameter.
*/
@Deprecated
public VOverlay(boolean autoHide, boolean modal, boolean showShadow) {
super(autoHide, modal);
setShadowEnabled(showShadow);
adjustZIndex();
}
/**
* Method to controle whether DOM elements for shadow are added. With this
* method subclasses can control displaying of shadow also after the
* constructor.
*
* @param enabled
* true if shadow should be displayed
*
* @deprecated See main JavaDoc for VOverlay
*/
@Deprecated
protected void setShadowEnabled(boolean enabled) {
if (enabled != isShadowEnabled()) {
if (enabled) {
shadow = DOM.createDiv();
shadow.setClassName(CLASSNAME_SHADOW);
shadow.setInnerHTML(SHADOW_HTML);
shadow.getStyle().setPosition(Position.ABSOLUTE);
addCloseHandler(this);
} else {
removeShadowIfPresent();
shadow = null;
}
}
}
/**
* @deprecated See main JavaDoc for VOverlay
*/
@Deprecated
protected boolean isShadowEnabled() {
return shadow != null;
}
protected boolean isShimElementEnabled() {
return shimElement != null;
}
private void removeShimElement() {
if (shimElement != null) {
shimElement.removeFromParent();
}
}
/**
* @deprecated See main JavaDoc for VOverlay
*/
@Deprecated
private void removeShadowIfPresent() {
if (isShadowAttached()) {
// Remove event listener from the shadow
unsinkShadowEvents();
shadow.removeFromParent();
}
}
/**
* @deprecated See main JavaDoc for VOverlay
*/
@Deprecated
private boolean isShadowAttached() {
return isShadowEnabled() && shadow.getParentElement() != null;
}
private void adjustZIndex() {
setZIndex(Z_INDEX);
}
/**
* Set the z-index (visual stack position) for this overlay.
*
* @param zIndex
* The new z-index
*/
protected void setZIndex(int zIndex) {
getElement().getStyle().setZIndex(zIndex);
if (isShadowEnabled()) {
shadow.getStyle().setZIndex(zIndex);
}
}
@Override
public void setPopupPosition(int left, int top) {
// TODO, this should in fact be part of
// Document.get().getBodyOffsetLeft/Top(). Would require overriding DOM
// for all permutations. Now adding fix as margin instead of fixing
// left/top because parent class saves the position.
Style style = getElement().getStyle();
style.setMarginLeft(-adjustByRelativeLeftBodyMargin(), Unit.PX);
style.setMarginTop(-adjustByRelativeTopBodyMargin(), Unit.PX);
super.setPopupPosition(left, top);
positionOrSizeUpdated(isAnimationEnabled() ? 0 : 1);
}
private IFrameElement getShimElement() {
if (shimElement == null && needsShimElement()) {
shimElement = Document.get().createIFrameElement();
// Insert shim iframe before the main overlay element. It does not
// matter if it is in front or behind the shadow as we cannot put a
// shim behind the shadow due to its transparency.
shimElement.getStyle().setPosition(Position.ABSOLUTE);
shimElement.getStyle().setBorderStyle(BorderStyle.NONE);
shimElement.setTabIndex(-1);
shimElement.setFrameBorder(0);
shimElement.setMarginHeight(0);
}
return shimElement;
}
private int getActualTop() {
int y = getAbsoluteTop();
/* This is needed for IE7 at least */
// Account for the difference between absolute position and the
// body's positioning context.
y -= Document.get().getBodyOffsetTop();
y -= adjustByRelativeTopBodyMargin();
return y;
}
private int getActualLeft() {
int x = getAbsoluteLeft();
/* This is needed for IE7 at least */
// Account for the difference between absolute position and the
// body's positioning context.
x -= Document.get().getBodyOffsetLeft();
x -= adjustByRelativeLeftBodyMargin();
return x;
}
private static int adjustByRelativeTopBodyMargin() {
if (topFix == -1) {
topFix = detectRelativeBodyFixes("top");
}
return topFix;
}
private native static int detectRelativeBodyFixes(String axis)
/*-{
try {
var b = $wnd.document.body;
var cstyle = b.currentStyle ? b.currentStyle : getComputedStyle(b);
if(cstyle && cstyle.position == 'relative') {
return b.getBoundingClientRect()[axis];
}
} catch(e){}
return 0;
}-*/;
private static int adjustByRelativeLeftBodyMargin() {
if (leftFix == -1) {
leftFix = detectRelativeBodyFixes("left");
}
return leftFix;
}
/*
* A "thread local" of sorts, set temporarily so that VOverlayImpl knows
* which VOverlay is using it, so that it can be attached to the correct
* overlay container.
*
* TODO this is a strange pattern that we should get rid of when possible.
*/
protected static VOverlay current;
@Override
public void show() {
current = this;
boolean hasAnimationIn = maybeShowWithAnimation();
if (isAnimationEnabled() && !hasAnimationIn) {
new ResizeAnimation().run(POPUP_PANEL_ANIMATION_DURATION);
} else {
positionOrSizeUpdated(1.0);
}
current = null;
}
private JavaScriptObject animateInListener;
private boolean maybeShowWithAnimation() {
boolean isAttached = isAttached() && isShowing();
super.show();
// Don't animate if already visible or browser is IE8 or IE9 (no CSS
// animation support)
if (isAttached || BrowserInfo.get().isIE8()
|| BrowserInfo.get().isIE9()) {
return false;
} else {
// Check if animations are used
addStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN);
if (isShadowEnabled()) {
shadow.addClassName(CLASSNAME_SHADOW + "-"
+ ADDITIONAL_CLASSNAME_ANIMATE_IN);
}
ComputedStyle cs = new ComputedStyle(getElement());
String animationName = AnimationUtil.getAnimationName(cs);
if (animationName == null) {
animationName = "";
}
if (animationName.contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
animateInListener = AnimationUtil.addAnimationEndListener(
getElement(), new AnimationEndListener() {
@Override
public void onAnimationEnd(NativeEvent event) {
String animationName = AnimationUtil
.getAnimationName(event);
if (animationName
.contains(ADDITIONAL_CLASSNAME_ANIMATE_IN)) {
AnimationUtil.removeAnimationEndListener(
getElement(), animateInListener);
removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN);
if (isShadowEnabled()) {
shadow.removeClassName(CLASSNAME_SHADOW
+ "-"
+ ADDITIONAL_CLASSNAME_ANIMATE_IN);
}
}
}
});
return true;
} else {
removeStyleDependentName(ADDITIONAL_CLASSNAME_ANIMATE_IN);
if (isShadowEnabled()) {
shadow.removeClassName(CLASSNAME_SHADOW + "-"
+ ADDITIONAL_CLASSNAME_ANIMATE_IN);
}
return false;
}
}
}
@Override
protected void onDetach() {
super.onDetach();
// Always ensure shadow is removed when the overlay is removed.
removeShadowIfPresent();
removeShimElement();
}
@Override
public void setVisible(boolean visible) {
super.setVisible(visible);
if (isShadowEnabled()) {
shadow.getStyle().setProperty("visibility",
visible ? "visible" : "hidden");
}
if (isShimElementEnabled()) {
shimElement.getStyle().setProperty("visibility",
visible ? "visible" : "hidden");
}
}
@Override
public void setWidth(String width) {
super.setWidth(width);
positionOrSizeUpdated(1.0);
}
@Override
public void setHeight(String height) {
super.setHeight(height);
positionOrSizeUpdated(1.0);
}
/**
* Sets the shadow style for this overlay. Will override any previous style
* for the shadow. The default style name is defined by CLASSNAME_SHADOW.
* The given style will be prefixed with CLASSNAME_SHADOW.
*
* @param style
* The new style name for the shadow element. Will be prefixed by
* CLASSNAME_SHADOW, e.g. style=='foobar' -> actual style
* name=='v-shadow-foobar'.
*
* @deprecated See main JavaDoc for VOverlay
*/
@Deprecated
protected void setShadowStyle(String style) {
if (isShadowEnabled()) {
shadow.setClassName(CLASSNAME_SHADOW + "-" + style);
}
}
/**
* Extending classes should always call this method after they change the
* size of overlay without using normal 'setWidth(String)' and
* 'setHeight(String)' methods (if not calling super.setWidth/Height).
*
*/
public void positionOrSizeUpdated() {
positionOrSizeUpdated(1.0);
}
/**
* @deprecated Call {@link #positionOrSizeUpdated()} instead.
*/
@Deprecated
protected void updateShadowSizeAndPosition() {
positionOrSizeUpdated();
}
/**
* Recalculates proper position and dimensions for the shadow and shim
* elements. Can be used to animate the related elements, using the
* 'progress' parameter (used to animate the shadow in sync with GWT
* PopupPanel's default animation 'PopupPanel.AnimationType.CENTER').
*
* @param progress
* A value between 0.0 and 1.0, indicating the progress of the
* animation (0=start, 1=end).
*/
private void positionOrSizeUpdated(final double progress) {
// Don't do anything if overlay element is not attached
if (!isAttached()) {
return;
}
// Calculate proper z-index
int zIndex = -1;
try {
// Odd behaviour with Windows Hosted Mode forces us to use
// this redundant try/catch block (See dev.vaadin.com #2011)
zIndex = Integer.parseInt(getElement().getStyle().getZIndex());
} catch (Exception ignore) {
// Ignored, will cause no harm
zIndex = 1000;
}
if (zIndex == -1) {
zIndex = Z_INDEX;
}
// Calculate position and size
if (BrowserInfo.get().isIE()) {
// Shake IE
getOffsetHeight();
getOffsetWidth();
}
if (isShadowEnabled() || needsShimElement()) {
PositionAndSize positionAndSize = new PositionAndSize(
getActualLeft(), getActualTop(), getOffsetWidth(),
getOffsetHeight());
// Animate the size
positionAndSize.setAnimationFromCenterProgress(progress);
Element container = getElement().getParentElement();
if (isShadowEnabled()) {
updateShadowPosition(progress, zIndex, positionAndSize);
if (shadow.getParentElement() == null) {
container.insertBefore(shadow, getElement());
sinkShadowEvents();
}
}
if (needsShimElement()) {
updateShimPosition(positionAndSize);
if (shimElement.getParentElement() == null) {
container.insertBefore(shimElement, getElement());
}
}
}
}
/**
* @deprecated See main JavaDoc for VOverlay
*/
@Deprecated
private void updateShadowPosition(final double progress, int zIndex,
PositionAndSize positionAndSize) {
// Opera needs some shaking to get parts of the shadow showing
// properly (ticket #2704)
if (BrowserInfo.get().isOpera()) {
// Clear the height of all middle elements
DOM.getChild(shadow, 3).getStyle().setProperty("height", "auto");
DOM.getChild(shadow, 4).getStyle().setProperty("height", "auto");
DOM.getChild(shadow, 5).getStyle().setProperty("height", "auto");
}
updatePositionAndSize(shadow, positionAndSize);
shadow.getStyle().setZIndex(zIndex);
shadow.getStyle().setProperty("display", progress < 0.9 ? "none" : "");
// Opera fix, part 2 (ticket #2704)
if (BrowserInfo.get().isOpera()) {
// We'll fix the height of all the middle elements
DOM.getChild(shadow, 3)
.getStyle()
.setPropertyPx("height",
DOM.getChild(shadow, 3).getOffsetHeight());
DOM.getChild(shadow, 4)
.getStyle()
.setPropertyPx("height",
DOM.getChild(shadow, 4).getOffsetHeight());
DOM.getChild(shadow, 5)
.getStyle()
.setPropertyPx("height",
DOM.getChild(shadow, 5).getOffsetHeight());
}
}
private void updateShimPosition(PositionAndSize positionAndSize) {
updatePositionAndSize(getShimElement(), positionAndSize);
}
/**
* Returns true if we should add a shim iframe below the overlay to deal
* with zindex issues with PDFs and applets. Can be overriden to disable
* shim iframes if they are not needed.
*
* @return true if a shim iframe should be added, false otherwise
*/
protected boolean needsShimElement() {
BrowserInfo info = BrowserInfo.get();
return info.isIE() && info.isBrowserVersionNewerOrEqual(8, 0);
}
private void updatePositionAndSize(Element e,
PositionAndSize positionAndSize) {
e.getStyle().setLeft(positionAndSize.getLeft(), Unit.PX);
e.getStyle().setTop(positionAndSize.getTop(), Unit.PX);
e.getStyle().setWidth(positionAndSize.getWidth(), Unit.PX);
e.getStyle().setHeight(positionAndSize.getHeight(), Unit.PX);
}
protected class ResizeAnimation extends Animation {
@Override
protected void onUpdate(double progress) {
positionOrSizeUpdated(progress);
}
}
@Override
public void onClose(CloseEvent