package org.vaadin.touchkit.gwt.client.ui; import com.google.gwt.dom.client.DivElement; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Style.Unit; import com.google.gwt.user.client.Element; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.RootPanel; import com.google.gwt.user.client.ui.UIObject; import com.google.gwt.user.client.ui.Widget; /** * TouchKit "subwindow". Both for iPad style 'popover' windows, and iPhone style * 'fullscreen' (actionsheet) windows. * */ public class VPopover extends com.vaadin.client.ui.VWindow { private static final int SMALL_SCREEN_WIDTH_THRESHOLD = 500; private static final int MIN_EDGE_DISTANCE = 10; private static final int MIN_ARROW_EDGE_DISTANCE = 2 * MIN_EDGE_DISTANCE; private Widget relComponent; private DivElement arrowElement; protected int zIndex; public VPopover() { } public void setRelatedComponent(Widget relComponent) { this.relComponent = relComponent; } public void slideIn() { /* * Make full width. */ setWidth((Window.getClientWidth() + "px")); int top = 0; if (relComponent != null) { boolean isCloseToBottom = relComponent.getAbsoluteTop() - Window.getScrollTop() > Window.getClientHeight() / 2; if (isCloseToBottom) { top = Window.getClientHeight() - getOffsetHeight(); } } setPopupPosition(0, top); hideReferenceArrow(); } private Element getWrapperElement() { return getElement().getFirstChild().getFirstChild().cast(); } public void showNextTo(Widget paintable) { if (paintable == null || !paintable.isAttached()) { // Vaadin may call this via setWidth/setHeight when reference // paintable don't exist anymore. The window may actually also be // about to be removed too. Thus make a sanity check. return; } // attach arrow, we need to measure it during calculations attachReferenceArrow(); final int centerOfReferencComponent = paintable.getAbsoluteLeft() + paintable.getOffsetWidth() / 2 - Window.getScrollLeft(); /* * Arrow below the popup takes precedence, since that way around the * user's hand doesn't get in the way. */ boolean arrowBelow = fitsAbove(paintable); boolean arrowAbove = !arrowBelow && fitsBelow(paintable); /* * show arrow that shows related component (similar to ios on ipad), * unless we needed to draw on top of the related widget. */ if (arrowAbove || arrowBelow) { showReferenceArrow(arrowBelow, centerOfReferencComponent, paintable); } else { hideReferenceArrow(); } int left = 0; if (getOffsetWidth() < Window.getClientWidth()) { left = centerOfReferencComponent - getOffsetWidth() / 2; /* * Ensure the window is totally on screen. */ if (left - MIN_EDGE_DISTANCE < 0) { left = MIN_EDGE_DISTANCE; } else if (left + getOffsetWidth() + MIN_EDGE_DISTANCE > Window .getClientWidth()) { left = Window.getClientWidth() - getOffsetWidth() - MIN_EDGE_DISTANCE; } } int top = paintable.getAbsoluteTop() - Window.getScrollTop(); if (arrowAbove) { top += paintable.getOffsetHeight(); top += arrowElement.getOffsetHeight(); } else if (arrowBelow) { top -= getOffsetHeight(); top -= arrowElement.getOffsetHeight(); } else { top = 0; } setPopupPosition(left, top); } private void showReferenceArrow(boolean onTop, int centerOfReferencComponent, Widget ref) { UIObject.setStyleName(arrowElement, "v-touchkit-popover-pointer-bottom", !onTop); UIObject.setStyleName(arrowElement, "v-touchkit-popover-pointer", onTop); int horizontalpoint = centerOfReferencComponent - arrowElement.getOffsetWidth() / 2; if (horizontalpoint - MIN_ARROW_EDGE_DISTANCE < 0) { horizontalpoint = MIN_ARROW_EDGE_DISTANCE; } else if (horizontalpoint + arrowElement.getOffsetWidth() + MIN_ARROW_EDGE_DISTANCE > Window.getClientWidth()) { horizontalpoint = Window.getClientWidth() - arrowElement.getOffsetWidth() - MIN_ARROW_EDGE_DISTANCE; } arrowElement.getStyle().setLeft(horizontalpoint, Unit.PX); int arrowPos = ref.getAbsoluteTop(); if (onTop) { arrowPos -= arrowElement.getOffsetHeight(); } else { arrowPos += ref.getOffsetHeight(); } arrowElement.getStyle().setTop(arrowPos, Unit.PX); arrowElement.getStyle().setProperty("opacity", ""); arrowElement.getStyle().setZIndex(zIndex); } private void hideReferenceArrow() { if (arrowElement != null && arrowElement.getParentElement() != null) { arrowElement.removeFromParent(); } } private void attachReferenceArrow() { if (arrowElement == null) { arrowElement = Document.get().createDivElement(); } arrowElement.getStyle().setOpacity(0); RootPanel.getBodyElement().appendChild(arrowElement); } @Override protected void setZIndex(int zIndex) { super.setZIndex(zIndex); // Store zIndex for later use. set arrow zIndex if available this.zIndex = zIndex; if (arrowElement != null) { arrowElement.getStyle().setZIndex(zIndex); } } private boolean fitsBelow(Widget paintable) { final int spaceBelow = Window.getClientHeight() - (paintable.getAbsoluteTop() + paintable.getOffsetHeight() - Window .getScrollTop()); final int requiredHeight = getOffsetHeight() + arrowElement.getOffsetHeight(); return spaceBelow > requiredHeight; } private boolean fitsAbove(Widget paintable) { final int spaceOntop = paintable.getAbsoluteTop() - Window.getScrollTop(); final int requiredHeight = getOffsetHeight() + arrowElement.getOffsetHeight(); return spaceOntop > requiredHeight; } public static boolean isSmallScreenDevice() { return Window.getClientWidth() < SMALL_SCREEN_WIDTH_THRESHOLD; } @Override public void center() { // Ignore centering if the popover is relative to component, or we're on // a small screen device if (relComponent == null && !isSmallScreenDevice()) { super.center(); } } @Override public void hide() { if (arrowElement != null && arrowElement.getParentElement() != null) { arrowElement.removeFromParent(); } super.hide(); } /* Make the methods below visible in this package */ @Override public boolean isClosable() { return super.isClosable(); } @Override public Element getModalityCurtain() { return super.getModalityCurtain(); } }