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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /*
  2. * Copyright 2000-2016 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 java.util.Collections;
  18. import java.util.HashSet;
  19. import java.util.Iterator;
  20. import java.util.Set;
  21. import com.google.gwt.core.client.Scheduler;
  22. import com.google.gwt.dom.client.Element;
  23. import com.google.gwt.event.dom.client.ClickEvent;
  24. import com.google.gwt.event.dom.client.ClickHandler;
  25. import com.google.gwt.event.dom.client.KeyCodes;
  26. import com.google.gwt.event.dom.client.KeyDownEvent;
  27. import com.google.gwt.event.dom.client.KeyDownHandler;
  28. import com.google.gwt.event.logical.shared.CloseEvent;
  29. import com.google.gwt.event.logical.shared.CloseHandler;
  30. import com.google.gwt.event.shared.HandlerRegistration;
  31. import com.google.gwt.user.client.DOM;
  32. import com.google.gwt.user.client.Event;
  33. import com.google.gwt.user.client.ui.Focusable;
  34. import com.google.gwt.user.client.ui.HTML;
  35. import com.google.gwt.user.client.ui.HasEnabled;
  36. import com.google.gwt.user.client.ui.Label;
  37. import com.google.gwt.user.client.ui.PopupPanel;
  38. import com.google.gwt.user.client.ui.RootPanel;
  39. import com.google.gwt.user.client.ui.Widget;
  40. import com.vaadin.client.ApplicationConnection;
  41. import com.vaadin.client.ComponentConnector;
  42. import com.vaadin.client.DeferredWorker;
  43. import com.vaadin.client.VCaptionWrapper;
  44. import com.vaadin.client.VConsole;
  45. import com.vaadin.client.communication.StateChangeEvent;
  46. import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
  47. import com.vaadin.client.ui.popupview.VisibilityChangeEvent;
  48. import com.vaadin.client.ui.popupview.VisibilityChangeHandler;
  49. public class VPopupView extends HTML
  50. implements HasEnabled, Iterable<Widget>, DeferredWorker {
  51. public static final String CLASSNAME = "v-popupview";
  52. /**
  53. * For server-client communication.
  54. * <p>
  55. * For internal use only. May be removed or replaced in the future.
  56. */
  57. public String uidlId;
  58. /** For internal use only. May be removed or replaced in the future. */
  59. public ApplicationConnection client;
  60. /**
  61. * Helps to communicate popup visibility to the server.
  62. * <p>
  63. * For internal use only. May be removed or replaced in the future.
  64. */
  65. public boolean hostPopupVisible;
  66. /** For internal use only. May be removed or replaced in the future. */
  67. public final CustomPopup popup;
  68. private final Label loading = new Label();
  69. private boolean popupShowInProgress;
  70. private boolean enabled = true;
  71. /**
  72. * Loading constructor.
  73. */
  74. public VPopupView() {
  75. super();
  76. popup = new CustomPopup();
  77. setStyleName(CLASSNAME);
  78. popup.setStyleName(CLASSNAME + "-popup");
  79. loading.setStyleName(CLASSNAME + "-loading");
  80. setHTML("");
  81. popup.setWidget(loading);
  82. // When we click to open the popup...
  83. addClickHandler(new ClickHandler() {
  84. @Override
  85. public void onClick(ClickEvent event) {
  86. if (isEnabled()) {
  87. preparePopup(popup);
  88. showPopup(popup);
  89. center();
  90. fireEvent(new VisibilityChangeEvent(true));
  91. }
  92. }
  93. });
  94. // ..and when we close it
  95. popup.addCloseHandler(new CloseHandler<PopupPanel>() {
  96. @Override
  97. public void onClose(CloseEvent<PopupPanel> event) {
  98. fireEvent(new VisibilityChangeEvent(false));
  99. }
  100. });
  101. // TODO: Enable animations once GWT fix has been merged
  102. popup.setAnimationEnabled(false);
  103. popup.setAutoHideOnHistoryEventsEnabled(false);
  104. }
  105. /** For internal use only. May be removed or replaced in the future. */
  106. public void preparePopup(final CustomPopup popup) {
  107. popup.setVisible(true);
  108. popup.setWidget(loading);
  109. popup.show();
  110. }
  111. /**
  112. * Determines the correct position for a popup and displays the popup at
  113. * that position.
  114. *
  115. * By default, the popup is shown centered relative to its host component,
  116. * ensuring it is visible on the screen if possible.
  117. *
  118. * Can be overridden to customize the popup position.
  119. *
  120. * @param popup
  121. */
  122. public void showPopup(final CustomPopup popup) {
  123. popup.setPopupPosition(0, 0);
  124. }
  125. /** For internal use only. May be removed or replaced in the future. */
  126. public void center() {
  127. int windowTop = RootPanel.get().getAbsoluteTop();
  128. int windowLeft = RootPanel.get().getAbsoluteLeft();
  129. int windowRight = windowLeft + RootPanel.get().getOffsetWidth();
  130. int windowBottom = windowTop + RootPanel.get().getOffsetHeight();
  131. int offsetWidth = popup.getOffsetWidth();
  132. int offsetHeight = popup.getOffsetHeight();
  133. int hostHorizontalCenter = VPopupView.this.getAbsoluteLeft()
  134. + VPopupView.this.getOffsetWidth() / 2;
  135. int hostVerticalCenter = VPopupView.this.getAbsoluteTop()
  136. + VPopupView.this.getOffsetHeight() / 2;
  137. int left = hostHorizontalCenter - offsetWidth / 2;
  138. int top = hostVerticalCenter - offsetHeight / 2;
  139. // Don't show the popup outside the screen.
  140. if ((left + offsetWidth) > windowRight) {
  141. left -= (left + offsetWidth) - windowRight;
  142. }
  143. if ((top + offsetHeight) > windowBottom) {
  144. top -= (top + offsetHeight) - windowBottom;
  145. }
  146. if (left < 0) {
  147. left = 0;
  148. }
  149. if (top < 0) {
  150. top = 0;
  151. }
  152. popup.setPopupPosition(left, top);
  153. }
  154. /**
  155. * Make sure that we remove the popup when the main widget is removed.
  156. *
  157. * @see com.google.gwt.user.client.ui.Widget#onUnload()
  158. */
  159. @Override
  160. protected void onDetach() {
  161. popup.hide();
  162. super.onDetach();
  163. }
  164. private static native void nativeBlur(Element e)
  165. /*-{
  166. if (e && e.blur) {
  167. e.blur();
  168. }
  169. }-*/;
  170. /**
  171. * Returns true if the popup is enabled, false if not.
  172. *
  173. * @since 7.3.4
  174. */
  175. @Override
  176. public boolean isEnabled() {
  177. return enabled;
  178. }
  179. /**
  180. * Sets whether this popup is enabled.
  181. *
  182. * @param enabled
  183. * <code>true</code> to enable the popup, <code>false</code> to
  184. * disable it
  185. * @since 7.3.4
  186. */
  187. @Override
  188. public void setEnabled(boolean enabled) {
  189. this.enabled = enabled;
  190. }
  191. /**
  192. * This class is only public to enable overriding showPopup, and is
  193. * currently not intended to be extended or otherwise used directly. Its API
  194. * (other than it being a VOverlay) is to be considered private and
  195. * potentially subject to change.
  196. */
  197. public class CustomPopup extends VOverlay
  198. implements StateChangeEvent.StateChangeHandler {
  199. private ComponentConnector popupComponentConnector = null;
  200. /** For internal use only. May be removed or replaced in the future. */
  201. public Widget popupComponentWidget = null;
  202. /** For internal use only. May be removed or replaced in the future. */
  203. public VCaptionWrapper captionWrapper = null;
  204. private boolean hasHadMouseOver = false;
  205. private boolean hideOnMouseOut = true;
  206. private final Set<Element> activeChildren = new HashSet<>();
  207. private ShortcutActionHandler shortcutActionHandler;
  208. public CustomPopup() {
  209. super(true, false); // autoHide, not modal
  210. setOwner(VPopupView.this);
  211. // Delegate popup keyboard events to the relevant handler. The
  212. // events do not propagate automatically because the popup is
  213. // directly attached to the RootPanel.
  214. addDomHandler(new KeyDownHandler() {
  215. @Override
  216. public void onKeyDown(KeyDownEvent event) {
  217. if (shortcutActionHandler != null) {
  218. shortcutActionHandler.handleKeyboardEvent(
  219. Event.as(event.getNativeEvent()));
  220. }
  221. }
  222. }, KeyDownEvent.getType());
  223. }
  224. // For some reason ONMOUSEOUT events are not always received, so we have
  225. // to use ONMOUSEMOVE that doesn't target the popup
  226. @Override
  227. public boolean onEventPreview(Event event) {
  228. Element target = DOM.eventGetTarget(event);
  229. boolean eventTargetsPopup = DOM.isOrHasChild(getElement(), target);
  230. int type = DOM.eventGetType(event);
  231. // Catch children that use keyboard, so we can unfocus them when
  232. // hiding
  233. if (eventTargetsPopup && type == Event.ONKEYPRESS) {
  234. activeChildren.add(target);
  235. }
  236. if (eventTargetsPopup && type == Event.ONMOUSEMOVE) {
  237. hasHadMouseOver = true;
  238. }
  239. if (!eventTargetsPopup && type == Event.ONMOUSEMOVE) {
  240. if (hasHadMouseOver && hideOnMouseOut) {
  241. hide();
  242. return true;
  243. }
  244. }
  245. // Was the TAB key released outside of our popup?
  246. if (!eventTargetsPopup && type == Event.ONKEYUP
  247. && event.getKeyCode() == KeyCodes.KEY_TAB) {
  248. // Should we hide on focus out (mouse out)?
  249. if (hideOnMouseOut) {
  250. hide();
  251. return true;
  252. }
  253. }
  254. return super.onEventPreview(event);
  255. }
  256. @Override
  257. public void hide(boolean autoClosed) {
  258. VConsole.log("Hiding popupview");
  259. syncChildren();
  260. clearPopupComponentConnector();
  261. hasHadMouseOver = false;
  262. shortcutActionHandler = null;
  263. super.hide(autoClosed);
  264. }
  265. @Override
  266. public void show() {
  267. popupShowInProgress = true;
  268. // Find the shortcut action handler that should handle keyboard
  269. // events from the popup. The events do not propagate automatically
  270. // because the popup is directly attached to the RootPanel.
  271. super.show();
  272. /*
  273. * Shortcut actions could be set (and currently in 7.2 they ARE SET
  274. * via old style "updateFromUIDL" method, see f.e. UIConnector)
  275. * AFTER method show() has been invoked (which is called as a
  276. * reaction on change in component hierarchy). As a result there
  277. * could be no shortcutActionHandler set yet. So let's postpone
  278. * search of shortcutActionHandler.
  279. */
  280. Scheduler.get().scheduleDeferred(() -> {
  281. try {
  282. if (shortcutActionHandler == null) {
  283. shortcutActionHandler = findShortcutActionHandler();
  284. }
  285. } finally {
  286. popupShowInProgress = false;
  287. }
  288. });
  289. }
  290. /**
  291. * Try to sync all known active child widgets to server.
  292. */
  293. public void syncChildren() {
  294. // Notify children with focus
  295. if ((popupComponentWidget instanceof Focusable)) {
  296. ((Focusable) popupComponentWidget).setFocus(false);
  297. }
  298. // Notify children that have used the keyboard
  299. for (Element e : activeChildren) {
  300. try {
  301. nativeBlur(e);
  302. } catch (Exception ignored) {
  303. }
  304. }
  305. activeChildren.clear();
  306. }
  307. private void clearPopupComponentConnector() {
  308. if (popupComponentConnector != null) {
  309. popupComponentConnector.removeStateChangeHandler(this);
  310. }
  311. popupComponentConnector = null;
  312. popupComponentWidget = null;
  313. captionWrapper = null;
  314. }
  315. @Override
  316. public boolean remove(Widget w) {
  317. clearPopupComponentConnector();
  318. return super.remove(w);
  319. }
  320. public void setPopupConnector(ComponentConnector newPopupComponent) {
  321. if (newPopupComponent != popupComponentConnector) {
  322. if (popupComponentConnector != null) {
  323. popupComponentConnector.removeStateChangeHandler(this);
  324. }
  325. Widget newWidget = newPopupComponent.getWidget();
  326. setWidget(newWidget);
  327. popupComponentWidget = newWidget;
  328. popupComponentConnector = newPopupComponent;
  329. popupComponentConnector.addStateChangeHandler("height", this);
  330. popupComponentConnector.addStateChangeHandler("width", this);
  331. }
  332. }
  333. public void setHideOnMouseOut(boolean hideOnMouseOut) {
  334. this.hideOnMouseOut = hideOnMouseOut;
  335. }
  336. @Override
  337. public com.google.gwt.user.client.Element getContainerElement() {
  338. return super.getContainerElement();
  339. }
  340. @Override
  341. public void onStateChanged(StateChangeEvent stateChangeEvent) {
  342. positionOrSizeUpdated();
  343. }
  344. private ShortcutActionHandler findShortcutActionHandler() {
  345. Widget widget = VPopupView.this;
  346. ShortcutActionHandler handler = null;
  347. while (handler == null && widget != null) {
  348. if (widget instanceof ShortcutActionHandlerOwner) {
  349. handler = ((ShortcutActionHandlerOwner) widget)
  350. .getShortcutActionHandler();
  351. }
  352. widget = widget.getParent();
  353. }
  354. return handler;
  355. }
  356. }
  357. public HandlerRegistration addVisibilityChangeHandler(
  358. final VisibilityChangeHandler visibilityChangeHandler) {
  359. return addHandler(visibilityChangeHandler,
  360. VisibilityChangeEvent.getType());
  361. }
  362. @Override
  363. public Iterator<Widget> iterator() {
  364. return Collections.singleton((Widget) popup).iterator();
  365. }
  366. /**
  367. * Checks whether there are operations pending for this widget that must be
  368. * executed before reaching a steady state.
  369. *
  370. * @returns <code>true</code> if there are operations pending which must be
  371. * executed before reaching a steady state
  372. * @since 7.3.4
  373. */
  374. @Override
  375. public boolean isWorkPending() {
  376. return popupShowInProgress;
  377. }
  378. }