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

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