You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VOverlay.java 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import com.google.gwt.animation.client.Animation;
  6. import com.google.gwt.dom.client.Document;
  7. import com.google.gwt.dom.client.IFrameElement;
  8. import com.google.gwt.dom.client.Style;
  9. import com.google.gwt.dom.client.Style.BorderStyle;
  10. import com.google.gwt.dom.client.Style.Position;
  11. import com.google.gwt.dom.client.Style.Unit;
  12. import com.google.gwt.event.logical.shared.CloseEvent;
  13. import com.google.gwt.event.logical.shared.CloseHandler;
  14. import com.google.gwt.user.client.DOM;
  15. import com.google.gwt.user.client.Element;
  16. import com.google.gwt.user.client.ui.PopupPanel;
  17. import com.google.gwt.user.client.ui.RootPanel;
  18. import com.google.gwt.user.client.ui.Widget;
  19. import com.vaadin.terminal.gwt.client.BrowserInfo;
  20. /**
  21. * In Vaadin UI this Overlay should always be used for all elements that
  22. * temporary float over other components like context menus etc. This is to deal
  23. * stacking order correctly with VWindow objects.
  24. */
  25. public class VOverlay extends PopupPanel implements CloseHandler<PopupPanel> {
  26. public static class PositionAndSize {
  27. private int left, top, width, height;
  28. public int getLeft() {
  29. return left;
  30. }
  31. public void setLeft(int left) {
  32. this.left = left;
  33. }
  34. public int getTop() {
  35. return top;
  36. }
  37. public void setTop(int top) {
  38. this.top = top;
  39. }
  40. public int getWidth() {
  41. return width;
  42. }
  43. public void setWidth(int width) {
  44. this.width = width;
  45. }
  46. public int getHeight() {
  47. return height;
  48. }
  49. public void setHeight(int height) {
  50. this.height = height;
  51. }
  52. public void setAnimationFromCenterProgress(double progress) {
  53. left += (int) (width * (1.0 - progress) / 2.0);
  54. top += (int) (height * (1.0 - progress) / 2.0);
  55. width = (int) (width * progress);
  56. height = (int) (height * progress);
  57. }
  58. }
  59. /*
  60. * The z-index value from where all overlays live. This can be overridden in
  61. * any extending class.
  62. */
  63. public static int Z_INDEX = 20000;
  64. private static int leftFix = -1;
  65. private static int topFix = -1;
  66. /*
  67. * Shadow element style. If an extending class wishes to use a different
  68. * style of shadow, it can use setShadowStyle(String) to give the shadow
  69. * element a new style name.
  70. */
  71. public static final String CLASSNAME_SHADOW = "v-shadow";
  72. /*
  73. * The shadow element for this overlay.
  74. */
  75. private Element shadow;
  76. /*
  77. * Creator of VOverlow (widget that made the instance, not the layout
  78. * parent)
  79. */
  80. private Widget owner;
  81. /**
  82. * The shim iframe behind the overlay, allowing PDFs and applets to be
  83. * covered by overlays.
  84. */
  85. private IFrameElement shimElement;
  86. /**
  87. * The HTML snippet that is used to render the actual shadow. In consists of
  88. * nine different DIV-elements with the following class names:
  89. *
  90. * <pre>
  91. * .v-shadow[-stylename]
  92. * ----------------------------------------------
  93. * | .top-left | .top | .top-right |
  94. * |---------------|-----------|----------------|
  95. * | | | |
  96. * | .left | .center | .right |
  97. * | | | |
  98. * |---------------|-----------|----------------|
  99. * | .bottom-left | .bottom | .bottom-right |
  100. * ----------------------------------------------
  101. * </pre>
  102. *
  103. * See default theme 'shadow.css' for implementation example.
  104. */
  105. private static final String SHADOW_HTML = "<div class=\"top-left\"></div><div class=\"top\"></div><div class=\"top-right\"></div><div class=\"left\"></div><div class=\"center\"></div><div class=\"right\"></div><div class=\"bottom-left\"></div><div class=\"bottom\"></div><div class=\"bottom-right\"></div>";
  106. /**
  107. * Matches {@link PopupPanel}.ANIMATION_DURATION
  108. */
  109. private static final int POPUP_PANEL_ANIMATION_DURATION = 200;
  110. private boolean sinkShadowEvents = false;
  111. public VOverlay() {
  112. super();
  113. adjustZIndex();
  114. }
  115. public VOverlay(boolean autoHide) {
  116. super(autoHide);
  117. adjustZIndex();
  118. }
  119. public VOverlay(boolean autoHide, boolean modal) {
  120. super(autoHide, modal);
  121. adjustZIndex();
  122. }
  123. public VOverlay(boolean autoHide, boolean modal, boolean showShadow) {
  124. super(autoHide, modal);
  125. setShadowEnabled(showShadow);
  126. adjustZIndex();
  127. }
  128. /**
  129. * Method to controle whether DOM elements for shadow are added. With this
  130. * method subclasses can control displaying of shadow also after the
  131. * constructor.
  132. *
  133. * @param enabled
  134. * true if shadow should be displayed
  135. */
  136. protected void setShadowEnabled(boolean enabled) {
  137. if (enabled != isShadowEnabled()) {
  138. if (enabled) {
  139. shadow = DOM.createDiv();
  140. shadow.setClassName(CLASSNAME_SHADOW);
  141. shadow.setInnerHTML(SHADOW_HTML);
  142. DOM.setStyleAttribute(shadow, "position", "absolute");
  143. addCloseHandler(this);
  144. } else {
  145. removeShadowIfPresent();
  146. shadow = null;
  147. }
  148. }
  149. }
  150. protected boolean isShadowEnabled() {
  151. return shadow != null;
  152. }
  153. private void removeShim() {
  154. if (shimElement != null) {
  155. shimElement.removeFromParent();
  156. }
  157. }
  158. private void removeShadowIfPresent() {
  159. if (isShadowAttached()) {
  160. shadow.removeFromParent();
  161. // Remove event listener from the shadow
  162. unsinkShadowEvents();
  163. }
  164. }
  165. private boolean isShadowAttached() {
  166. return isShadowEnabled() && shadow.getParentElement() != null;
  167. }
  168. private boolean isShimAttached() {
  169. return shimElement != null && shimElement.hasParentElement();
  170. }
  171. private void adjustZIndex() {
  172. setZIndex(Z_INDEX);
  173. }
  174. /**
  175. * Set the z-index (visual stack position) for this overlay.
  176. *
  177. * @param zIndex
  178. * The new z-index
  179. */
  180. protected void setZIndex(int zIndex) {
  181. DOM.setStyleAttribute(getElement(), "zIndex", "" + zIndex);
  182. if (isShadowEnabled()) {
  183. DOM.setStyleAttribute(shadow, "zIndex", "" + zIndex);
  184. }
  185. }
  186. @Override
  187. public void setPopupPosition(int left, int top) {
  188. // TODO, this should in fact be part of
  189. // Document.get().getBodyOffsetLeft/Top(). Would require overriding DOM
  190. // for all permutations. Now adding fix as margin instead of fixing
  191. // left/top because parent class saves the position.
  192. Style style = getElement().getStyle();
  193. style.setMarginLeft(-adjustByRelativeLeftBodyMargin(), Unit.PX);
  194. style.setMarginTop(-adjustByRelativeTopBodyMargin(), Unit.PX);
  195. super.setPopupPosition(left, top);
  196. sizeOrPositionUpdated(isAnimationEnabled() ? 0 : 1);
  197. }
  198. private IFrameElement getShimElement() {
  199. if (shimElement == null) {
  200. shimElement = Document.get().createIFrameElement();
  201. // Insert shim iframe before the main overlay element. It does not
  202. // matter if it is in front or behind the shadow as we cannot put a
  203. // shim behind the shadow due to its transparency.
  204. shimElement.getStyle().setPosition(Position.ABSOLUTE);
  205. shimElement.getStyle().setBorderStyle(BorderStyle.NONE);
  206. shimElement.setFrameBorder(0);
  207. shimElement.setMarginHeight(0);
  208. }
  209. return shimElement;
  210. }
  211. private int getActualTop() {
  212. int y = getAbsoluteTop();
  213. /* This is needed for IE7 at least */
  214. // Account for the difference between absolute position and the
  215. // body's positioning context.
  216. y -= Document.get().getBodyOffsetTop();
  217. y -= adjustByRelativeTopBodyMargin();
  218. return y;
  219. }
  220. private int getActualLeft() {
  221. int x = getAbsoluteLeft();
  222. /* This is needed for IE7 at least */
  223. // Account for the difference between absolute position and the
  224. // body's positioning context.
  225. x -= Document.get().getBodyOffsetLeft();
  226. x -= adjustByRelativeLeftBodyMargin();
  227. return x;
  228. }
  229. private static int adjustByRelativeTopBodyMargin() {
  230. if (topFix == -1) {
  231. topFix = detectRelativeBodyFixes("top");
  232. }
  233. return topFix;
  234. }
  235. private native static int detectRelativeBodyFixes(String axis)
  236. /*-{
  237. try {
  238. var b = $wnd.document.body;
  239. var cstyle = b.currentStyle ? b.currentStyle : getComputedStyle(b);
  240. if(cstyle && cstyle.position == 'relative') {
  241. return b.getBoundingClientRect()[axis];
  242. }
  243. } catch(e){}
  244. return 0;
  245. }-*/;
  246. private static int adjustByRelativeLeftBodyMargin() {
  247. if (leftFix == -1) {
  248. leftFix = detectRelativeBodyFixes("left");
  249. }
  250. return leftFix;
  251. }
  252. @Override
  253. public void show() {
  254. super.show();
  255. if (isAnimationEnabled()) {
  256. new ResizeAnimation().run(POPUP_PANEL_ANIMATION_DURATION);
  257. } else {
  258. sizeOrPositionUpdated(1.0);
  259. }
  260. }
  261. @Override
  262. protected void onDetach() {
  263. super.onDetach();
  264. // Always ensure shadow is removed when the overlay is removed.
  265. removeShadowIfPresent();
  266. removeShim();
  267. }
  268. @Override
  269. public void setVisible(boolean visible) {
  270. super.setVisible(visible);
  271. if (isShadowEnabled()) {
  272. shadow.getStyle().setProperty("visibility",
  273. visible ? "visible" : "hidden");
  274. }
  275. }
  276. @Override
  277. public void setWidth(String width) {
  278. super.setWidth(width);
  279. sizeOrPositionUpdated(1.0);
  280. }
  281. @Override
  282. public void setHeight(String height) {
  283. super.setHeight(height);
  284. sizeOrPositionUpdated(1.0);
  285. }
  286. /**
  287. * Sets the shadow style for this overlay. Will override any previous style
  288. * for the shadow. The default style name is defined by CLASSNAME_SHADOW.
  289. * The given style will be prefixed with CLASSNAME_SHADOW.
  290. *
  291. * @param style
  292. * The new style name for the shadow element. Will be prefixed by
  293. * CLASSNAME_SHADOW, e.g. style=='foobar' -> actual style
  294. * name=='v-shadow-foobar'.
  295. */
  296. protected void setShadowStyle(String style) {
  297. if (isShadowEnabled()) {
  298. shadow.setClassName(CLASSNAME_SHADOW + "-" + style);
  299. }
  300. }
  301. /**
  302. * Extending classes should always call this method after they change the
  303. * size of overlay without using normal 'setWidth(String)' and
  304. * 'setHeight(String)' methods (if not calling super.setWidth/Height).
  305. *
  306. */
  307. public void sizeOrPositionUpdated() {
  308. sizeOrPositionUpdated(1.0);
  309. }
  310. /**
  311. * Recalculates proper position and dimensions for the shadow and shim
  312. * elements. Can be used to animate the related elements, using the
  313. * 'progress' parameter (used to animate the shadow in sync with GWT
  314. * PopupPanel's default animation 'PopupPanel.AnimationType.CENTER').
  315. *
  316. * @param progress
  317. * A value between 0.0 and 1.0, indicating the progress of the
  318. * animation (0=start, 1=end).
  319. */
  320. private void sizeOrPositionUpdated(final double progress) {
  321. // Don't do anything if overlay element is not attached
  322. if (!isAttached()) {
  323. return;
  324. }
  325. // Calculate proper z-index
  326. String zIndex = null;
  327. try {
  328. // Odd behaviour with Windows Hosted Mode forces us to use
  329. // this redundant try/catch block (See dev.vaadin.com #2011)
  330. zIndex = DOM.getStyleAttribute(getElement(), "zIndex");
  331. } catch (Exception ignore) {
  332. // Ignored, will cause no harm
  333. zIndex = "1000";
  334. }
  335. if (zIndex == null) {
  336. zIndex = "" + Z_INDEX;
  337. }
  338. // Calculate position and size
  339. if (BrowserInfo.get().isIE()) {
  340. // Shake IE
  341. getOffsetHeight();
  342. getOffsetWidth();
  343. }
  344. PositionAndSize positionAndSize = new PositionAndSize();
  345. positionAndSize.left = getActualLeft();
  346. positionAndSize.top = getActualTop();
  347. positionAndSize.width = getOffsetWidth();
  348. positionAndSize.height = getOffsetHeight();
  349. if (positionAndSize.width < 0) {
  350. positionAndSize.width = 0;
  351. }
  352. if (positionAndSize.height < 0) {
  353. positionAndSize.height = 0;
  354. }
  355. // Animate the size
  356. positionAndSize.setAnimationFromCenterProgress(progress);
  357. // Opera needs some shaking to get parts of the shadow showing
  358. // properly
  359. // (ticket #2704)
  360. if (BrowserInfo.get().isOpera() && isShadowEnabled()) {
  361. // Clear the height of all middle elements
  362. DOM.getChild(shadow, 3).getStyle().setProperty("height", "auto");
  363. DOM.getChild(shadow, 4).getStyle().setProperty("height", "auto");
  364. DOM.getChild(shadow, 5).getStyle().setProperty("height", "auto");
  365. }
  366. // Update correct values
  367. if (isShadowEnabled()) {
  368. updateSizeAndPosition(shadow, positionAndSize);
  369. DOM.setStyleAttribute(shadow, "zIndex", zIndex);
  370. DOM.setStyleAttribute(shadow, "display", progress < 0.9 ? "none"
  371. : "");
  372. }
  373. updateSizeAndPosition((Element) Element.as(getShimElement()),
  374. positionAndSize);
  375. // Opera fix, part 2 (ticket #2704)
  376. if (BrowserInfo.get().isOpera() && isShadowEnabled()) {
  377. // We'll fix the height of all the middle elements
  378. DOM.getChild(shadow, 3)
  379. .getStyle()
  380. .setPropertyPx("height",
  381. DOM.getChild(shadow, 3).getOffsetHeight());
  382. DOM.getChild(shadow, 4)
  383. .getStyle()
  384. .setPropertyPx("height",
  385. DOM.getChild(shadow, 4).getOffsetHeight());
  386. DOM.getChild(shadow, 5)
  387. .getStyle()
  388. .setPropertyPx("height",
  389. DOM.getChild(shadow, 5).getOffsetHeight());
  390. }
  391. // Attach to dom if not there already
  392. if (isShadowEnabled() && !isShadowAttached()) {
  393. RootPanel.get().getElement().insertBefore(shadow, getElement());
  394. sinkShadowEvents();
  395. }
  396. if (!isShimAttached()) {
  397. RootPanel.get().getElement()
  398. .insertBefore(shimElement, getElement());
  399. }
  400. }
  401. private void updateSizeAndPosition(Element e,
  402. PositionAndSize positionAndSize) {
  403. e.getStyle().setLeft(positionAndSize.left, Unit.PX);
  404. e.getStyle().setTop(positionAndSize.top, Unit.PX);
  405. e.getStyle().setWidth(positionAndSize.width, Unit.PX);
  406. e.getStyle().setHeight(positionAndSize.height, Unit.PX);
  407. }
  408. protected class ResizeAnimation extends Animation {
  409. @Override
  410. protected void onUpdate(double progress) {
  411. sizeOrPositionUpdated(progress);
  412. }
  413. }
  414. @Override
  415. public void onClose(CloseEvent<PopupPanel> event) {
  416. removeShadowIfPresent();
  417. }
  418. @Override
  419. public void sinkEvents(int eventBitsToAdd) {
  420. super.sinkEvents(eventBitsToAdd);
  421. // Also sink events on the shadow if present
  422. sinkShadowEvents();
  423. }
  424. private void sinkShadowEvents() {
  425. if (isSinkShadowEvents() && isShadowAttached()) {
  426. // Sink the same events as the actual overlay has sunk
  427. DOM.sinkEvents(shadow, DOM.getEventsSunk(getElement()));
  428. // Send events to VOverlay.onBrowserEvent
  429. DOM.setEventListener(shadow, this);
  430. }
  431. }
  432. private void unsinkShadowEvents() {
  433. if (isShadowAttached()) {
  434. DOM.setEventListener(shadow, null);
  435. DOM.sinkEvents(shadow, 0);
  436. }
  437. }
  438. /**
  439. * Enables or disables sinking the events of the shadow to the same
  440. * onBrowserEvent as events to the actual overlay goes.
  441. *
  442. * Please note, that if you enable this, you can't assume that e.g.
  443. * event.getEventTarget returns an element inside the DOM structure of the
  444. * overlay
  445. *
  446. * @param sinkShadowEvents
  447. */
  448. protected void setSinkShadowEvents(boolean sinkShadowEvents) {
  449. this.sinkShadowEvents = sinkShadowEvents;
  450. if (sinkShadowEvents) {
  451. sinkShadowEvents();
  452. } else {
  453. unsinkShadowEvents();
  454. }
  455. }
  456. protected boolean isSinkShadowEvents() {
  457. return sinkShadowEvents;
  458. }
  459. /**
  460. * Get owner (Widget that made this VOverlay, not the layout parent) of
  461. * VOverlay
  462. *
  463. * @return Owner (creator) or null if not defined
  464. */
  465. public Widget getOwner() {
  466. return owner;
  467. }
  468. /**
  469. * Set owner (Widget that made this VOverlay, not the layout parent) of
  470. * VOverlay
  471. *
  472. * @param owner
  473. * Owner (creator) of VOverlay
  474. */
  475. public void setOwner(Widget owner) {
  476. this.owner = owner;
  477. }
  478. }