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 19KB

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