Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

VPopupView.java 14KB

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