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

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