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.

VPopupView.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.HashSet;
  6. import java.util.Iterator;
  7. import java.util.NoSuchElementException;
  8. import java.util.Set;
  9. import com.google.gwt.event.dom.client.ClickEvent;
  10. import com.google.gwt.event.dom.client.ClickHandler;
  11. import com.google.gwt.event.logical.shared.CloseEvent;
  12. import com.google.gwt.event.logical.shared.CloseHandler;
  13. import com.google.gwt.user.client.DOM;
  14. import com.google.gwt.user.client.Element;
  15. import com.google.gwt.user.client.Event;
  16. import com.google.gwt.user.client.ui.Focusable;
  17. import com.google.gwt.user.client.ui.HTML;
  18. import com.google.gwt.user.client.ui.HasWidgets;
  19. import com.google.gwt.user.client.ui.Label;
  20. import com.google.gwt.user.client.ui.PopupPanel;
  21. import com.google.gwt.user.client.ui.RootPanel;
  22. import com.google.gwt.user.client.ui.Widget;
  23. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  24. import com.vaadin.terminal.gwt.client.Container;
  25. import com.vaadin.terminal.gwt.client.Paintable;
  26. import com.vaadin.terminal.gwt.client.RenderSpace;
  27. import com.vaadin.terminal.gwt.client.UIDL;
  28. import com.vaadin.terminal.gwt.client.Util;
  29. import com.vaadin.terminal.gwt.client.VCaption;
  30. import com.vaadin.terminal.gwt.client.VCaptionWrapper;
  31. import com.vaadin.terminal.gwt.client.VTooltip;
  32. import com.vaadin.terminal.gwt.client.RenderInformation.Size;
  33. import com.vaadin.terminal.gwt.client.ui.richtextarea.VRichTextArea;
  34. public class VPopupView extends HTML implements Container, Iterable<Widget> {
  35. public static final String CLASSNAME = "v-popupview";
  36. /** For server-client communication */
  37. private String uidlId;
  38. private ApplicationConnection client;
  39. /** This variable helps to communicate popup visibility to the server */
  40. private boolean hostPopupVisible;
  41. private final CustomPopup popup;
  42. private final Label loading = new Label();
  43. /**
  44. * loading constructor
  45. */
  46. public VPopupView() {
  47. super();
  48. popup = new CustomPopup();
  49. setStyleName(CLASSNAME);
  50. popup.setStylePrimaryName(CLASSNAME + "-popup");
  51. loading.setStyleName(CLASSNAME + "-loading");
  52. setHTML("");
  53. popup.setWidget(loading);
  54. // When we click to open the popup...
  55. addClickHandler(new ClickHandler() {
  56. public void onClick(ClickEvent event) {
  57. updateState(true);
  58. }
  59. });
  60. // ..and when we close it
  61. popup.addCloseHandler(new CloseHandler<PopupPanel>() {
  62. public void onClose(CloseEvent<PopupPanel> event) {
  63. updateState(false);
  64. }
  65. });
  66. popup.setAnimationEnabled(true);
  67. sinkEvents(VTooltip.TOOLTIP_EVENTS);
  68. }
  69. /**
  70. *
  71. *
  72. * @see com.vaadin.terminal.gwt.client.Paintable#updateFromUIDL(com.vaadin.terminal.gwt.client.UIDL,
  73. * com.vaadin.terminal.gwt.client.ApplicationConnection)
  74. */
  75. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  76. // This call should be made first. Ensure correct implementation,
  77. // and don't let the containing layout manage caption.
  78. if (client.updateComponent(this, uidl, false)) {
  79. return;
  80. }
  81. // These are for future server connections
  82. this.client = client;
  83. uidlId = uidl.getId();
  84. hostPopupVisible = uidl.getBooleanVariable("popupVisibility");
  85. setHTML(uidl.getStringAttribute("html"));
  86. if (uidl.hasAttribute("hideOnMouseOut")) {
  87. popup.setHideOnMouseOut(uidl.getBooleanAttribute("hideOnMouseOut"));
  88. }
  89. // Render the popup if visible and show it.
  90. if (hostPopupVisible) {
  91. UIDL popupUIDL = uidl.getChildUIDL(0);
  92. // showPopupOnTop(popup, hostReference);
  93. preparePopup(popup);
  94. popup.updateFromUIDL(popupUIDL, client);
  95. if (uidl.hasAttribute("style")) {
  96. final String[] styles = uidl.getStringAttribute("style").split(
  97. " ");
  98. final StringBuffer styleBuf = new StringBuffer();
  99. final String primaryName = popup.getStylePrimaryName();
  100. styleBuf.append(primaryName);
  101. for (int i = 0; i < styles.length; i++) {
  102. styleBuf.append(" ");
  103. styleBuf.append(primaryName);
  104. styleBuf.append("-");
  105. styleBuf.append(styles[i]);
  106. }
  107. popup.setStyleName(styleBuf.toString());
  108. } else {
  109. popup.setStyleName(popup.getStylePrimaryName());
  110. }
  111. showPopup(popup);
  112. // The popup shouldn't be visible, try to hide it.
  113. } else {
  114. popup.hide();
  115. }
  116. }// updateFromUIDL
  117. /**
  118. * Update popup visibility to server
  119. *
  120. * @param visibility
  121. */
  122. private void updateState(boolean visible) {
  123. // If we know the server connection
  124. // then update the current situation
  125. if (uidlId != null && client != null && isAttached()) {
  126. client.updateVariable(uidlId, "popupVisibility", visible, true);
  127. }
  128. }
  129. private void preparePopup(final CustomPopup popup) {
  130. popup.setVisible(false);
  131. popup.show();
  132. }
  133. /**
  134. * Determines the correct position for a popup and displays the popup at
  135. * that position.
  136. *
  137. * By default, the popup is shown centered relative to its host component,
  138. * ensuring it is visible on the screen if possible.
  139. *
  140. * Can be overridden to customize the popup position.
  141. *
  142. * @param popup
  143. */
  144. protected void showPopup(final CustomPopup popup) {
  145. int windowTop = RootPanel.get().getAbsoluteTop();
  146. int windowLeft = RootPanel.get().getAbsoluteLeft();
  147. int windowRight = windowLeft + RootPanel.get().getOffsetWidth();
  148. int windowBottom = windowTop + RootPanel.get().getOffsetHeight();
  149. int offsetWidth = popup.getOffsetWidth();
  150. int offsetHeight = popup.getOffsetHeight();
  151. int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft()
  152. + VPopupView.this.getOffsetWidth() / 2;
  153. int hostVerticalCenter = VPopupView.this.getAbsoluteTop()
  154. + VPopupView.this.getOffsetHeight() / 2;
  155. int left = hostHorizontalCenter - offsetWidth / 2;
  156. int top = hostVerticalCenter - offsetHeight / 2;
  157. // Don't show the popup outside the screen.
  158. if ((left + offsetWidth) > windowRight) {
  159. left -= (left + offsetWidth) - windowRight;
  160. }
  161. if ((top + offsetHeight) > windowBottom) {
  162. top -= (top + offsetHeight) - windowBottom;
  163. }
  164. if (left < 0) {
  165. left = 0;
  166. }
  167. if (top < 0) {
  168. top = 0;
  169. }
  170. popup.setPopupPosition(left, top);
  171. popup.setVisible(true);
  172. }
  173. /**
  174. * Make sure that we remove the popup when the main widget is removed.
  175. *
  176. * @see com.google.gwt.user.client.ui.Widget#onUnload()
  177. */
  178. @Override
  179. protected void onDetach() {
  180. popup.hide();
  181. super.onDetach();
  182. }
  183. private static native void nativeBlur(Element e)
  184. /*-{
  185. if(e && e.blur) {
  186. e.blur();
  187. }
  188. }-*/;
  189. /**
  190. * This class is only protected to enable overriding showPopup, and is
  191. * currently not intended to be extended or otherwise used directly. Its API
  192. * (other than it being a VOverlay) is to be considered private and
  193. * potentially subject to change.
  194. */
  195. protected class CustomPopup extends VOverlay {
  196. private Paintable popupComponentPaintable = null;
  197. private Widget popupComponentWidget = null;
  198. private VCaptionWrapper captionWrapper = null;
  199. private boolean hasHadMouseOver = false;
  200. private boolean hideOnMouseOut = true;
  201. private final Set<Element> activeChildren = new HashSet<Element>();
  202. private boolean hiding = false;
  203. public CustomPopup() {
  204. super(true, false, true); // autoHide, not modal, dropshadow
  205. }
  206. // For some reason ONMOUSEOUT events are not always received, so we have
  207. // to use ONMOUSEMOVE that doesn't target the popup
  208. @Override
  209. public boolean onEventPreview(Event event) {
  210. Element target = DOM.eventGetTarget(event);
  211. boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target);
  212. int type = DOM.eventGetType(event);
  213. // Catch children that use keyboard, so we can unfocus them when
  214. // hiding
  215. if (eventTargetsPopup && type == Event.ONKEYPRESS) {
  216. activeChildren.add(target);
  217. }
  218. if (eventTargetsPopup && type == Event.ONMOUSEMOVE) {
  219. hasHadMouseOver = true;
  220. }
  221. if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) {
  222. if (hasHadMouseOver && hideOnMouseOut) {
  223. hide();
  224. return true;
  225. }
  226. }
  227. return super.onEventPreview(event);
  228. }
  229. @Override
  230. public void hide(boolean autoClosed) {
  231. hiding = true;
  232. syncChildren();
  233. unregisterPaintables();
  234. if (popupComponentWidget != null && popupComponentWidget != loading) {
  235. remove(popupComponentWidget);
  236. }
  237. hasHadMouseOver = false;
  238. super.hide(autoClosed);
  239. }
  240. @Override
  241. public void show() {
  242. hiding = false;
  243. super.show();
  244. }
  245. /**
  246. * Try to sync all known active child widgets to server
  247. */
  248. public void syncChildren() {
  249. // Notify children with focus
  250. if ((popupComponentWidget instanceof Focusable)) {
  251. ((Focusable) popupComponentWidget).setFocus(false);
  252. } else {
  253. checkForRTE(popupComponentWidget);
  254. }
  255. // Notify children that have used the keyboard
  256. for (Element e : activeChildren) {
  257. try {
  258. nativeBlur(e);
  259. } catch (Exception ignored) {
  260. }
  261. }
  262. activeChildren.clear();
  263. }
  264. private void checkForRTE(Widget popupComponentWidget2) {
  265. if (popupComponentWidget2 instanceof VRichTextArea) {
  266. ((VRichTextArea) popupComponentWidget2)
  267. .synchronizeContentToServer();
  268. } else if (popupComponentWidget2 instanceof HasWidgets) {
  269. HasWidgets hw = (HasWidgets) popupComponentWidget2;
  270. Iterator<Widget> iterator = hw.iterator();
  271. while (iterator.hasNext()) {
  272. checkForRTE(iterator.next());
  273. }
  274. }
  275. }
  276. @Override
  277. public boolean remove(Widget w) {
  278. popupComponentPaintable = null;
  279. popupComponentWidget = null;
  280. captionWrapper = null;
  281. return super.remove(w);
  282. }
  283. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  284. Paintable newPopupComponent = client.getPaintable(uidl
  285. .getChildUIDL(0));
  286. if (newPopupComponent != popupComponentPaintable) {
  287. setWidget((Widget) newPopupComponent);
  288. popupComponentWidget = (Widget) newPopupComponent;
  289. popupComponentPaintable = newPopupComponent;
  290. }
  291. popupComponentPaintable
  292. .updateFromUIDL(uidl.getChildUIDL(0), client);
  293. }
  294. public void unregisterPaintables() {
  295. if (popupComponentPaintable != null) {
  296. client.unregisterPaintable(popupComponentPaintable);
  297. }
  298. }
  299. public void setHideOnMouseOut(boolean hideOnMouseOut) {
  300. this.hideOnMouseOut = hideOnMouseOut;
  301. }
  302. /*
  303. *
  304. * We need a hack make popup act as a child of VPopupView in Vaadin's
  305. * component tree, but work in default GWT manner when closing or
  306. * opening.
  307. *
  308. * (non-Javadoc)
  309. *
  310. * @see com.google.gwt.user.client.ui.Widget#getParent()
  311. */
  312. @Override
  313. public Widget getParent() {
  314. if (!isAttached() || hiding) {
  315. return super.getParent();
  316. } else {
  317. return VPopupView.this;
  318. }
  319. }
  320. @Override
  321. protected void onDetach() {
  322. super.onDetach();
  323. hiding = false;
  324. }
  325. @Override
  326. public Element getContainerElement() {
  327. return super.getContainerElement();
  328. }
  329. }// class CustomPopup
  330. // Container methods
  331. public RenderSpace getAllocatedSpace(Widget child) {
  332. Size popupExtra = calculatePopupExtra();
  333. return new RenderSpace(RootPanel.get().getOffsetWidth()
  334. - popupExtra.getWidth(), RootPanel.get().getOffsetHeight()
  335. - popupExtra.getHeight());
  336. }
  337. /**
  338. * Calculate extra space taken by the popup decorations
  339. *
  340. * @return
  341. */
  342. protected Size calculatePopupExtra() {
  343. Element pe = popup.getElement();
  344. Element ipe = popup.getContainerElement();
  345. // border + padding
  346. int width = Util.getRequiredWidth(pe) - Util.getRequiredWidth(ipe);
  347. int height = Util.getRequiredHeight(pe) - Util.getRequiredHeight(ipe);
  348. return new Size(width, height);
  349. }
  350. public boolean hasChildComponent(Widget component) {
  351. if (popup.popupComponentWidget != null) {
  352. return popup.popupComponentWidget == component;
  353. } else {
  354. return false;
  355. }
  356. }
  357. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  358. popup.setWidget(newComponent);
  359. popup.popupComponentWidget = newComponent;
  360. }
  361. public boolean requestLayout(Set<Paintable> child) {
  362. popup.updateShadowSizeAndPosition();
  363. return true;
  364. }
  365. public void updateCaption(Paintable component, UIDL uidl) {
  366. if (VCaption.isNeeded(uidl)) {
  367. if (popup.captionWrapper != null) {
  368. popup.captionWrapper.updateCaption(uidl);
  369. } else {
  370. popup.captionWrapper = new VCaptionWrapper(component, client);
  371. popup.setWidget(popup.captionWrapper);
  372. popup.captionWrapper.updateCaption(uidl);
  373. }
  374. } else {
  375. if (popup.captionWrapper != null) {
  376. popup.setWidget(popup.popupComponentWidget);
  377. }
  378. }
  379. popup.popupComponentWidget = (Widget) component;
  380. popup.popupComponentPaintable = component;
  381. }
  382. @Override
  383. public void onBrowserEvent(Event event) {
  384. super.onBrowserEvent(event);
  385. if (client != null) {
  386. client.handleTooltipEvent(event, this);
  387. }
  388. }
  389. public Iterator<Widget> iterator() {
  390. return new Iterator<Widget>() {
  391. int pos = 0;
  392. public boolean hasNext() {
  393. // There is a child widget only if next() has not been called.
  394. return (pos == 0);
  395. }
  396. public Widget next() {
  397. // Next can be called only once to return the popup.
  398. if (pos != 0) {
  399. throw new NoSuchElementException();
  400. }
  401. pos++;
  402. return popup;
  403. }
  404. public void remove() {
  405. throw new UnsupportedOperationException();
  406. }
  407. };
  408. }
  409. }// class VPopupView