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 16KB

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