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.

VWindow.java 53KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587
  1. /*
  2. * Copyright 2000-2018 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 static com.vaadin.client.WidgetUtil.isFocusedElementEditable;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collections;
  21. import java.util.List;
  22. import com.google.gwt.aria.client.Id;
  23. import com.google.gwt.aria.client.RelevantValue;
  24. import com.google.gwt.aria.client.Roles;
  25. import com.google.gwt.core.client.Scheduler;
  26. import com.google.gwt.dom.client.Document;
  27. import com.google.gwt.dom.client.Element;
  28. import com.google.gwt.dom.client.NativeEvent;
  29. import com.google.gwt.dom.client.Style.Position;
  30. import com.google.gwt.dom.client.Style.Unit;
  31. import com.google.gwt.dom.client.Style.Visibility;
  32. import com.google.gwt.event.dom.client.BlurEvent;
  33. import com.google.gwt.event.dom.client.BlurHandler;
  34. import com.google.gwt.event.dom.client.FocusEvent;
  35. import com.google.gwt.event.dom.client.FocusHandler;
  36. import com.google.gwt.event.dom.client.KeyCodes;
  37. import com.google.gwt.event.dom.client.KeyDownEvent;
  38. import com.google.gwt.event.dom.client.KeyDownHandler;
  39. import com.google.gwt.event.dom.client.ScrollEvent;
  40. import com.google.gwt.event.dom.client.ScrollHandler;
  41. import com.google.gwt.event.shared.HandlerManager;
  42. import com.google.gwt.event.shared.HandlerRegistration;
  43. import com.google.gwt.user.client.DOM;
  44. import com.google.gwt.user.client.Event;
  45. import com.google.gwt.user.client.Event.NativePreviewHandler;
  46. import com.google.gwt.user.client.Window;
  47. import com.google.gwt.user.client.ui.Widget;
  48. import com.vaadin.client.ApplicationConnection;
  49. import com.vaadin.client.BrowserInfo;
  50. import com.vaadin.client.ComponentConnector;
  51. import com.vaadin.client.ConnectorMap;
  52. import com.vaadin.client.Focusable;
  53. import com.vaadin.client.HasComponentsConnector;
  54. import com.vaadin.client.LayoutManager;
  55. import com.vaadin.client.WidgetUtil;
  56. import com.vaadin.client.debug.internal.VDebugWindow;
  57. import com.vaadin.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
  58. import com.vaadin.client.ui.aria.AriaHelper;
  59. import com.vaadin.client.ui.window.WindowConnector;
  60. import com.vaadin.client.ui.window.WindowMoveEvent;
  61. import com.vaadin.client.ui.window.WindowMoveHandler;
  62. import com.vaadin.client.ui.window.WindowOrderEvent;
  63. import com.vaadin.client.ui.window.WindowOrderHandler;
  64. import com.vaadin.shared.Connector;
  65. import com.vaadin.shared.EventId;
  66. import com.vaadin.shared.ui.window.WindowMode;
  67. import com.vaadin.shared.ui.window.WindowRole;
  68. /**
  69. * "Sub window" component.
  70. *
  71. * @author Vaadin Ltd
  72. */
  73. public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
  74. ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
  75. private static List<VWindow> windowOrder = new ArrayList<>();
  76. private static final HandlerManager WINDOW_ORDER_HANDLER = new HandlerManager(
  77. VWindow.class);
  78. private static boolean orderingDefered;
  79. public static final String CLASSNAME = "v-window";
  80. private static final String MODAL_WINDOW_OPEN_CLASSNAME = "v-modal-window-open";
  81. private static final int STACKING_OFFSET_PIXELS = 15;
  82. public static final int Z_INDEX = 10000;
  83. /** For internal use only. May be removed or replaced in the future. */
  84. public Element contents;
  85. /** For internal use only. May be removed or replaced in the future. */
  86. public Element header;
  87. /** For internal use only. May be removed or replaced in the future. */
  88. public Element footer;
  89. private Element resizeBox;
  90. /** For internal use only. May be removed or replaced in the future. */
  91. public final FocusableScrollPanel contentPanel = new FocusableScrollPanel();
  92. private boolean dragging;
  93. private int startX;
  94. private int startY;
  95. private int origX;
  96. private int origY;
  97. private boolean resizing;
  98. private int origW;
  99. private int origH;
  100. /** For internal use only. May be removed or replaced in the future. */
  101. public Element closeBox;
  102. /** For internal use only. May be removed or replaced in the future. */
  103. public Element maximizeRestoreBox;
  104. /** For internal use only. May be removed or replaced in the future. */
  105. public ApplicationConnection client;
  106. /** For internal use only. May be removed or replaced in the future. */
  107. public WindowConnector connector;
  108. /** For internal use only. May be removed or replaced in the future. */
  109. public String id;
  110. /** For internal use only. May be removed or replaced in the future. */
  111. public ShortcutActionHandler shortcutHandler;
  112. /**
  113. * Last known positionx read from UIDL or updated to application connection
  114. */
  115. private int uidlPositionX = -1;
  116. /**
  117. * Last known positiony read from UIDL or updated to application connection
  118. */
  119. private int uidlPositionY = -1;
  120. /** For internal use only. May be removed or replaced in the future. */
  121. public boolean vaadinModality = false;
  122. /** For internal use only. May be removed or replaced in the future. */
  123. public boolean resizable = true;
  124. private boolean draggable = true;
  125. /** For internal use only. May be removed or replaced in the future. */
  126. public boolean resizeLazy = false;
  127. private Element modalityCurtain;
  128. private Element draggingCurtain;
  129. private Element resizingCurtain;
  130. private Element headerText;
  131. private boolean closable = true;
  132. private Connector[] assistiveConnectors = new Connector[0];
  133. private String assistivePrefix;
  134. private String assistivePostfix;
  135. private Element topTabStop;
  136. private Element bottomTabStop;
  137. private NativePreviewHandler topEventBlocker;
  138. private NativePreviewHandler bottomEventBlocker;
  139. private NativePreviewHandler modalEventBlocker;
  140. private HandlerRegistration topBlockerRegistration;
  141. private HandlerRegistration bottomBlockerRegistration;
  142. private HandlerRegistration modalBlockerRegistration;
  143. // Prevents leaving the window with the Tab key when true
  144. private boolean doTabStop;
  145. /**
  146. * If centered (via UIDL), the window should stay in the centered -mode
  147. * until a position is received from the server, or the user moves or
  148. * resizes the window.
  149. * <p>
  150. * For internal use only. May be removed or replaced in the future.
  151. */
  152. public boolean centered = false;
  153. private Element wrapper;
  154. /** For internal use only. May be removed or replaced in the future. */
  155. public boolean visibilityChangesDisabled;
  156. /** For internal use only. May be removed or replaced in the future. */
  157. public int bringToFrontSequence = -1;
  158. private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200,
  159. () -> updateContentsSize());
  160. public VWindow() {
  161. super(false, false); // no autohide, not modal
  162. Roles.getDialogRole().set(getElement());
  163. Roles.getDialogRole().setAriaRelevantProperty(getElement(),
  164. RelevantValue.ADDITIONS);
  165. constructDOM();
  166. contentPanel.addScrollHandler(this);
  167. contentPanel.addKeyDownHandler(this);
  168. contentPanel.addFocusHandler(this);
  169. contentPanel.addBlurHandler(this);
  170. addTransitionEndLayoutListener(getElement());
  171. }
  172. @Override
  173. protected void onAttach() {
  174. super.onAttach();
  175. /*
  176. * Stores the element that has focus in the application UI when the
  177. * window is opened, so it can be restored when the window closes.
  178. *
  179. * This is currently implemented for the case when one non-modal window
  180. * can be open at the same time, and the focus is not changed while the
  181. * window is open.
  182. */
  183. getApplicationConnection().getUIConnector().getWidget().storeFocus();
  184. /*
  185. * When this window gets reattached, set the tabstop to the previous
  186. * state.
  187. */
  188. setTabStopEnabled(doTabStop);
  189. }
  190. @Override
  191. protected void onDetach() {
  192. super.onDetach();
  193. /*
  194. * Restores the previously stored focused element.
  195. *
  196. * When the focus was changed outside the window while the window was
  197. * open, the originally stored element is not restored.
  198. *
  199. * IE returns null and other browsers HTMLBodyElement, if no element is
  200. * focused after window is closed.
  201. */
  202. if (WidgetUtil.getFocusedElement() == null || "body".equalsIgnoreCase(
  203. WidgetUtil.getFocusedElement().getTagName())) {
  204. getApplicationConnection().getUIConnector().getWidget()
  205. .focusStoredElement();
  206. }
  207. removeTabBlockHandlers();
  208. // If you click while the window is being closed,
  209. // a new dragging curtain might be added and will
  210. // remain after detach. Theoretically a resize curtain can also remain
  211. // if you manage to click on the resize element
  212. hideDraggingCurtain();
  213. hideResizingCurtain();
  214. }
  215. private void addTabBlockHandlers() {
  216. if (topBlockerRegistration == null) {
  217. topBlockerRegistration = Event
  218. .addNativePreviewHandler(topEventBlocker);
  219. bottomBlockerRegistration = Event
  220. .addNativePreviewHandler(bottomEventBlocker);
  221. modalBlockerRegistration = Event
  222. .addNativePreviewHandler(modalEventBlocker);
  223. }
  224. }
  225. private void removeTabBlockHandlers() {
  226. if (topBlockerRegistration != null) {
  227. topBlockerRegistration.removeHandler();
  228. topBlockerRegistration = null;
  229. bottomBlockerRegistration.removeHandler();
  230. bottomBlockerRegistration = null;
  231. modalBlockerRegistration.removeHandler();
  232. modalBlockerRegistration = null;
  233. }
  234. }
  235. public void bringToFront() {
  236. bringToFront(true);
  237. }
  238. private void bringToFront(boolean notifyListeners) {
  239. int curIndex = getWindowOrder();
  240. if (curIndex + 1 < windowOrder.size()) {
  241. windowOrder.remove(this);
  242. windowOrder.add(this);
  243. for (; curIndex < windowOrder.size(); curIndex++) {
  244. VWindow window = windowOrder.get(curIndex);
  245. window.setWindowOrder(curIndex);
  246. }
  247. }
  248. if (notifyListeners) {
  249. fireOrderEvent();
  250. }
  251. }
  252. static void fireOrderEvent() {
  253. fireOrderEvent(windowOrder);
  254. }
  255. private void doFireOrderEvent() {
  256. List<VWindow> list = new ArrayList<>();
  257. list.add(this);
  258. fireOrderEvent(list);
  259. }
  260. private static void fireOrderEvent(List<VWindow> windows) {
  261. WINDOW_ORDER_HANDLER
  262. .fireEvent(new WindowOrderEvent(new ArrayList<>(windows)));
  263. }
  264. /**
  265. * Returns true if this window is the topmost VWindow
  266. *
  267. * @return
  268. */
  269. private boolean isActive() {
  270. return equals(getTopmostWindow());
  271. }
  272. private static VWindow getTopmostWindow() {
  273. if (!windowOrder.isEmpty()) {
  274. return windowOrder.get(windowOrder.size() - 1);
  275. }
  276. return null;
  277. }
  278. /** For internal use only. May be removed or replaced in the future. */
  279. public void setWindowOrderAndPosition() {
  280. // This cannot be done in the constructor as the widgets are created in
  281. // a different order than on they should appear on screen
  282. if (windowOrder.contains(this)) {
  283. // Already set
  284. return;
  285. }
  286. final int order = windowOrder.size();
  287. setWindowOrder(order);
  288. windowOrder.add(this);
  289. setPopupPosition(order * STACKING_OFFSET_PIXELS,
  290. order * STACKING_OFFSET_PIXELS);
  291. doFireOrderEvent();
  292. }
  293. private void setWindowOrder(int order) {
  294. setZIndex(order + Z_INDEX);
  295. }
  296. /**
  297. * Returns window position in list of opened and shown windows.
  298. *
  299. * @since 8.0
  300. */
  301. public final int getWindowOrder() {
  302. return windowOrder.indexOf(this);
  303. }
  304. @Override
  305. protected void setZIndex(int zIndex) {
  306. super.setZIndex(zIndex);
  307. if (vaadinModality) {
  308. getModalityCurtain().getStyle().setZIndex(zIndex);
  309. }
  310. }
  311. protected com.google.gwt.user.client.Element getModalityCurtain() {
  312. if (modalityCurtain == null) {
  313. modalityCurtain = DOM.createDiv();
  314. modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain");
  315. }
  316. return DOM.asOld(modalityCurtain);
  317. }
  318. protected void constructDOM() {
  319. setStyleName(CLASSNAME);
  320. topTabStop = DOM.createDiv();
  321. DOM.setElementAttribute(topTabStop, "tabindex", "0");
  322. header = DOM.createDiv();
  323. DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
  324. headerText = DOM.createDiv();
  325. DOM.setElementProperty(headerText, "className", CLASSNAME + "-header");
  326. contents = DOM.createDiv();
  327. DOM.setElementProperty(contents, "className", CLASSNAME + "-contents");
  328. footer = DOM.createDiv();
  329. DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
  330. resizeBox = DOM.createDiv();
  331. DOM.setElementProperty(resizeBox, "className",
  332. CLASSNAME + "-resizebox");
  333. closeBox = DOM.createDiv();
  334. maximizeRestoreBox = DOM.createDiv();
  335. DOM.setElementProperty(maximizeRestoreBox, "className",
  336. CLASSNAME + "-maximizebox");
  337. DOM.setElementAttribute(maximizeRestoreBox, "tabindex", "0");
  338. DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
  339. DOM.setElementAttribute(closeBox, "tabindex", "0");
  340. DOM.appendChild(footer, resizeBox);
  341. bottomTabStop = DOM.createDiv();
  342. DOM.setElementAttribute(bottomTabStop, "tabindex", "0");
  343. wrapper = DOM.createDiv();
  344. DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
  345. DOM.appendChild(wrapper, topTabStop);
  346. DOM.appendChild(wrapper, header);
  347. DOM.appendChild(header, maximizeRestoreBox);
  348. DOM.appendChild(header, closeBox);
  349. DOM.appendChild(header, headerText);
  350. DOM.appendChild(wrapper, contents);
  351. DOM.appendChild(wrapper, footer);
  352. DOM.appendChild(wrapper, bottomTabStop);
  353. DOM.appendChild(super.getContainerElement(), wrapper);
  354. sinkEvents(Event.ONDBLCLICK | Event.MOUSEEVENTS | Event.TOUCHEVENTS
  355. | Event.ONCLICK | Event.ONLOSECAPTURE | Event.ONKEYUP);
  356. setWidget(contentPanel);
  357. // Make the closebox accessible for assistive devices
  358. Roles.getButtonRole().set(closeBox);
  359. Roles.getButtonRole().setAriaLabelProperty(closeBox, "close button");
  360. // Make the maximizebox accessible for assistive devices
  361. Roles.getButtonRole().set(maximizeRestoreBox);
  362. Roles.getButtonRole().setAriaLabelProperty(maximizeRestoreBox,
  363. "maximize button");
  364. // Provide the title to assistive devices
  365. AriaHelper.ensureHasId(headerText);
  366. Roles.getDialogRole().setAriaLabelledbyProperty(getElement(),
  367. Id.of(headerText));
  368. // Handlers to Prevent tab to leave the window (by circulating focus)
  369. // and backspace to cause browser navigation
  370. topEventBlocker = event -> {
  371. if (!getElement().isOrHasChild(WidgetUtil.getFocusedElement())) {
  372. return;
  373. }
  374. NativeEvent nativeEvent = event.getNativeEvent();
  375. if (nativeEvent.getEventTarget().cast() == topTabStop
  376. && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB
  377. && nativeEvent.getShiftKey()) {
  378. nativeEvent.preventDefault();
  379. FocusUtil.focusOnLastFocusableElement(getElement());
  380. }
  381. if (nativeEvent.getEventTarget().cast() == topTabStop
  382. && nativeEvent.getKeyCode() == KeyCodes.KEY_BACKSPACE) {
  383. nativeEvent.preventDefault();
  384. }
  385. };
  386. bottomEventBlocker = event -> {
  387. if (!getElement().isOrHasChild(WidgetUtil.getFocusedElement())) {
  388. return;
  389. }
  390. NativeEvent nativeEvent = event.getNativeEvent();
  391. if (nativeEvent.getEventTarget().cast() == bottomTabStop
  392. && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB
  393. && !nativeEvent.getShiftKey()) {
  394. nativeEvent.preventDefault();
  395. FocusUtil.focusOnFirstFocusableElement(getElement());
  396. }
  397. if (nativeEvent.getEventTarget().cast() == bottomTabStop
  398. && nativeEvent.getKeyCode() == KeyCodes.KEY_BACKSPACE) {
  399. nativeEvent.preventDefault();
  400. }
  401. };
  402. // Handle modal window + tabbing when the focus is not inside the
  403. // window (custom tab order or tabbing in from browser url bar)
  404. modalEventBlocker = event -> {
  405. if (!vaadinModality
  406. || getElement().isOrHasChild(WidgetUtil.getFocusedElement())
  407. || (getTopmostWindow() != VWindow.this)) {
  408. return;
  409. }
  410. NativeEvent nativeEvent = event.getNativeEvent();
  411. if (nativeEvent.getType().equals("keyup")
  412. && nativeEvent.getKeyCode() == KeyCodes.KEY_TAB) {
  413. nativeEvent.preventDefault();
  414. focus();
  415. }
  416. };
  417. }
  418. /**
  419. * Sets the message that is provided to users of assistive devices when the
  420. * user reaches the top of the window when leaving a window with the tab key
  421. * is prevented.
  422. * <p>
  423. * This message is not visible on the screen.
  424. *
  425. * @param topMessage
  426. * String provided when the user navigates with Shift-Tab keys to
  427. * the top of the window
  428. */
  429. public void setTabStopTopAssistiveText(String topMessage) {
  430. Roles.getNoteRole().setAriaLabelProperty(topTabStop, topMessage);
  431. }
  432. /**
  433. * Sets the message that is provided to users of assistive devices when the
  434. * user reaches the bottom of the window when leaving a window with the tab
  435. * key is prevented.
  436. * <p>
  437. * This message is not visible on the screen.
  438. *
  439. * @param bottomMessage
  440. * String provided when the user navigates with the Tab key to
  441. * the bottom of the window
  442. */
  443. public void setTabStopBottomAssistiveText(String bottomMessage) {
  444. Roles.getNoteRole().setAriaLabelProperty(bottomTabStop, bottomMessage);
  445. }
  446. /**
  447. * Gets the message that is provided to users of assistive devices when the
  448. * user reaches the top of the window when leaving a window with the tab key
  449. * is prevented.
  450. *
  451. * @return the top message
  452. */
  453. public String getTabStopTopAssistiveText() {
  454. return Roles.getNoteRole().getAriaLabelProperty(topTabStop);
  455. }
  456. /**
  457. * Gets the message that is provided to users of assistive devices when the
  458. * user reaches the bottom of the window when leaving a window with the tab
  459. * key is prevented.
  460. *
  461. * @return the bottom message
  462. */
  463. public String getTabStopBottomAssistiveText() {
  464. return Roles.getNoteRole().getAriaLabelProperty(bottomTabStop);
  465. }
  466. /**
  467. * Calling this method will defer ordering algorithm, to order windows based
  468. * on servers bringToFront and modality instructions. Non changed windows
  469. * will be left intact.
  470. * <p>
  471. * For internal use only. May be removed or replaced in the future.
  472. */
  473. public static void deferOrdering() {
  474. if (!orderingDefered) {
  475. orderingDefered = true;
  476. Scheduler.get().scheduleFinally(() -> {
  477. doServerSideOrdering();
  478. VNotification.bringNotificationsToFront();
  479. });
  480. }
  481. }
  482. private static void doServerSideOrdering() {
  483. orderingDefered = false;
  484. VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]);
  485. Arrays.sort(array, (o1, o2) -> {
  486. /*
  487. * Order by modality, then by bringtofront sequence.
  488. */
  489. if (o1.vaadinModality && !o2.vaadinModality) {
  490. return 1;
  491. }
  492. if (!o1.vaadinModality && o2.vaadinModality) {
  493. return -1;
  494. }
  495. if (o1.bringToFrontSequence > o2.bringToFrontSequence) {
  496. return 1;
  497. }
  498. if (o1.bringToFrontSequence < o2.bringToFrontSequence) {
  499. return -1;
  500. }
  501. return 0;
  502. });
  503. for (VWindow w : array) {
  504. if (w.bringToFrontSequence != -1 || w.vaadinModality) {
  505. w.bringToFront(false);
  506. w.bringToFrontSequence = -1;
  507. }
  508. }
  509. focusTopmostModalWindow();
  510. }
  511. private static void focusTopmostModalWindow() {
  512. VWindow topmost = getTopmostWindow();
  513. if (topmost != null && topmost.vaadinModality) {
  514. topmost.focus();
  515. }
  516. fireOrderEvent();
  517. }
  518. @Override
  519. public void setVisible(boolean visible) {
  520. /*
  521. * Visibility with VWindow works differently than with other Paintables
  522. * in Vaadin. Invisible VWindows are not attached to DOM at all. Flag is
  523. * used to avoid visibility call from
  524. * ApplicationConnection.updateComponent();
  525. */
  526. if (!visibilityChangesDisabled) {
  527. super.setVisible(visible);
  528. }
  529. if (visible && BrowserInfo.get()
  530. .requiresPositionAbsoluteOverflowAutoFix()) {
  531. /*
  532. * Shake up the DOM a bit to make the window shed unnecessary
  533. * scrollbars and resize correctly afterwards. The version fixing
  534. * ticket #11994 which was changing the size to 110% was replaced
  535. * with this due to ticket #12943
  536. */
  537. WidgetUtil
  538. .runWebkitOverflowAutoFix(contents.getFirstChildElement());
  539. Scheduler.get().scheduleFinally(() -> {
  540. List<ComponentConnector> childComponents = ((HasComponentsConnector) ConnectorMap
  541. .get(client).getConnector(this)).getChildComponents();
  542. if (!childComponents.isEmpty()) {
  543. LayoutManager layoutManager = getLayoutManager();
  544. layoutManager.setNeedsMeasure(childComponents.get(0));
  545. layoutManager.layoutNow();
  546. }
  547. });
  548. }
  549. }
  550. /** For internal use only. May be removed or replaced in the future. */
  551. public void setDraggable(boolean draggable) {
  552. if (this.draggable == draggable) {
  553. return;
  554. }
  555. this.draggable = draggable;
  556. setCursorProperties();
  557. }
  558. private void setCursorProperties() {
  559. if (!draggable) {
  560. header.getStyle().setProperty("cursor", "default");
  561. footer.getStyle().setProperty("cursor", "default");
  562. } else {
  563. header.getStyle().setProperty("cursor", "");
  564. footer.getStyle().setProperty("cursor", "");
  565. }
  566. }
  567. /**
  568. * Sets the closable state of the window. Additionally hides/shows the close
  569. * button according to the new state.
  570. *
  571. * @param closable
  572. * true if the window can be closed by the user
  573. */
  574. public void setClosable(boolean closable) {
  575. if (this.closable == closable) {
  576. return;
  577. }
  578. this.closable = closable;
  579. if (closable) {
  580. DOM.setElementProperty(closeBox, "className",
  581. CLASSNAME + "-closebox");
  582. } else {
  583. DOM.setElementProperty(closeBox, "className", CLASSNAME
  584. + "-closebox " + CLASSNAME + "-closebox-disabled");
  585. }
  586. }
  587. /**
  588. * Returns the closable state of the sub window. If the sub window is
  589. * closable a decoration (typically an X) is shown to the user. By clicking
  590. * on the X the user can close the window.
  591. *
  592. * @return true if the sub window is closable
  593. */
  594. protected boolean isClosable() {
  595. return closable;
  596. }
  597. @Override
  598. public void show() {
  599. if (!windowOrder.contains(this)) {
  600. // This is needed if the window is hidden and then shown again.
  601. // Otherwise this VWindow is added to windowOrder in the
  602. // constructor.
  603. windowOrder.add(this);
  604. }
  605. if (vaadinModality) {
  606. showModalityCurtain();
  607. }
  608. super.show();
  609. }
  610. @Override
  611. public void hide() {
  612. if (vaadinModality) {
  613. hideModalityCurtain();
  614. hideDraggingCurtain();
  615. hideResizingCurtain();
  616. }
  617. super.hide();
  618. int curIndex = getWindowOrder();
  619. // Remove window from windowOrder to avoid references being left
  620. // hanging.
  621. windowOrder.remove(curIndex);
  622. // Update the z-indices of any remaining windows
  623. List<VWindow> update = new ArrayList<>(
  624. windowOrder.size() - curIndex + 1);
  625. update.add(this);
  626. while (curIndex < windowOrder.size()) {
  627. VWindow window = windowOrder.get(curIndex);
  628. window.setWindowOrder(curIndex++);
  629. update.add(window);
  630. }
  631. focusTopmostModalWindow();
  632. fireOrderEvent(update);
  633. }
  634. /** For internal use only. May be removed or replaced in the future. */
  635. public void setVaadinModality(boolean modality) {
  636. vaadinModality = modality;
  637. if (vaadinModality) {
  638. getElement().setAttribute("aria-modal", "true");
  639. Roles.getDialogRole().set(getElement());
  640. if (isAttached()) {
  641. showModalityCurtain();
  642. }
  643. addTabBlockHandlers();
  644. deferOrdering();
  645. } else {
  646. getElement().removeAttribute("aria-modal");
  647. Roles.getDialogRole().remove(getElement());
  648. if (modalityCurtain != null) {
  649. if (isAttached()) {
  650. hideModalityCurtain();
  651. }
  652. modalityCurtain = null;
  653. }
  654. if (!doTabStop) {
  655. removeTabBlockHandlers();
  656. }
  657. }
  658. }
  659. private void showModalityCurtain() {
  660. getModalityCurtain().getStyle().setZIndex(getWindowOrder() + Z_INDEX);
  661. if (isShowing()) {
  662. getOverlayContainer().insertBefore(getModalityCurtain(),
  663. getElement());
  664. } else {
  665. getOverlayContainer().appendChild(getModalityCurtain());
  666. }
  667. Document.get().getBody().addClassName(MODAL_WINDOW_OPEN_CLASSNAME);
  668. }
  669. private void hideModalityCurtain() {
  670. Document.get().getBody().removeClassName(MODAL_WINDOW_OPEN_CLASSNAME);
  671. modalityCurtain.removeFromParent();
  672. // IE leaks memory in certain cases unless we release the reference
  673. // (#9197)
  674. modalityCurtain = null;
  675. }
  676. /*
  677. * Shows an empty div on top of all other content; used when moving, so that
  678. * iframes (etc) do not steal event.
  679. */
  680. private void showDraggingCurtain() {
  681. getElement().getParentElement().insertBefore(getDraggingCurtain(),
  682. getElement());
  683. }
  684. private void hideDraggingCurtain() {
  685. if (draggingCurtain != null) {
  686. draggingCurtain.removeFromParent();
  687. }
  688. }
  689. /*
  690. * Shows an empty div on top of all other content; used when resizing, so
  691. * that iframes (etc) do not steal event.
  692. */
  693. private void showResizingCurtain() {
  694. getElement().getParentElement().insertBefore(getResizingCurtain(),
  695. getElement());
  696. }
  697. private void hideResizingCurtain() {
  698. if (resizingCurtain != null) {
  699. resizingCurtain.removeFromParent();
  700. }
  701. }
  702. private Element getDraggingCurtain() {
  703. if (draggingCurtain == null) {
  704. draggingCurtain = createCurtain();
  705. draggingCurtain.setClassName(CLASSNAME + "-draggingCurtain");
  706. }
  707. return draggingCurtain;
  708. }
  709. private Element getResizingCurtain() {
  710. if (resizingCurtain == null) {
  711. resizingCurtain = createCurtain();
  712. resizingCurtain.setClassName(CLASSNAME + "-resizingCurtain");
  713. }
  714. return resizingCurtain;
  715. }
  716. private Element createCurtain() {
  717. Element curtain = DOM.createDiv();
  718. curtain.getStyle().setPosition(Position.ABSOLUTE);
  719. curtain.getStyle().setTop(0, Unit.PX);
  720. curtain.getStyle().setLeft(0, Unit.PX);
  721. curtain.getStyle().setWidth(100, Unit.PCT);
  722. curtain.getStyle().setHeight(100, Unit.PCT);
  723. curtain.getStyle().setZIndex(VOverlay.Z_INDEX);
  724. return curtain;
  725. }
  726. /** For internal use only. May be removed or replaced in the future. */
  727. public void setResizable(boolean resizability) {
  728. resizable = resizability;
  729. if (resizability) {
  730. DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
  731. DOM.setElementProperty(resizeBox, "className",
  732. CLASSNAME + "-resizebox");
  733. } else {
  734. DOM.setElementProperty(footer, "className",
  735. CLASSNAME + "-footer " + CLASSNAME + "-footer-noresize");
  736. DOM.setElementProperty(resizeBox, "className", CLASSNAME
  737. + "-resizebox " + CLASSNAME + "-resizebox-disabled");
  738. }
  739. }
  740. public void updateMaximizeRestoreClassName(boolean visible,
  741. WindowMode windowMode) {
  742. String className;
  743. if (windowMode == WindowMode.MAXIMIZED) {
  744. className = CLASSNAME + "-restorebox";
  745. } else {
  746. className = CLASSNAME + "-maximizebox";
  747. }
  748. if (!visible) {
  749. className = className + " " + className + "-disabled";
  750. }
  751. maximizeRestoreBox.setClassName(className);
  752. }
  753. // TODO this will eventually be removed, currently used to avoid updating to
  754. // server side.
  755. public void setPopupPositionNoUpdate(int left, int top) {
  756. if (top < 0) {
  757. // ensure window is not moved out of browser window from top of the
  758. // screen
  759. top = 0;
  760. }
  761. super.setPopupPosition(left, top);
  762. }
  763. @Override
  764. public void setPopupPosition(int left, int top) {
  765. if (top < 0) {
  766. // ensure window is not moved out of browser window from top of the
  767. // screen
  768. top = 0;
  769. }
  770. super.setPopupPosition(left, top);
  771. if (left != uidlPositionX && client != null) {
  772. client.updateVariable(id, "positionx", left, false);
  773. uidlPositionX = left;
  774. }
  775. if (top != uidlPositionY && client != null) {
  776. client.updateVariable(id, "positiony", top, false);
  777. uidlPositionY = top;
  778. }
  779. }
  780. public void setCaption(String c) {
  781. setCaption(c, null);
  782. }
  783. public void setCaption(String c, String iconURL) {
  784. setCaption(c, iconURL, false);
  785. }
  786. public void setCaption(String c, String iconURL, boolean asHtml) {
  787. String html;
  788. if (asHtml) {
  789. html = c == null ? "" : c;
  790. } else {
  791. html = WidgetUtil.escapeHTML(c);
  792. }
  793. // Provide information to assistive device users that a sub window was
  794. // opened
  795. String prefix = "<span class='" + AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE
  796. + "'>" + assistivePrefix + "</span>";
  797. String postfix = "<span class='"
  798. + AriaHelper.ASSISTIVE_DEVICE_ONLY_STYLE + "'>"
  799. + assistivePostfix + "</span>";
  800. html = prefix + html + postfix;
  801. headerText.setInnerHTML(html);
  802. if (iconURL != null) {
  803. Icon icon = client.getIcon(iconURL);
  804. DOM.insertChild(headerText, icon.getElement(), 0);
  805. }
  806. }
  807. /**
  808. * Setter for the text for assistive devices the window caption is prefixed
  809. * with.
  810. *
  811. * @param assistivePrefix
  812. * the assistivePrefix to set
  813. */
  814. public void setAssistivePrefix(String assistivePrefix) {
  815. this.assistivePrefix = assistivePrefix;
  816. }
  817. /**
  818. * Getter for the text for assistive devices the window caption is prefixed
  819. * with.
  820. *
  821. * @return the assistivePrefix
  822. */
  823. public String getAssistivePrefix() {
  824. return assistivePrefix;
  825. }
  826. /**
  827. * Setter for the text for assistive devices the window caption is postfixed
  828. * with.
  829. *
  830. * @param assistivePostfix
  831. * the assistivePostfix to set
  832. */
  833. public void setAssistivePostfix(String assistivePostfix) {
  834. this.assistivePostfix = assistivePostfix;
  835. }
  836. /**
  837. * Getter for the text for assistive devices the window caption is postfixed
  838. * with.
  839. *
  840. * @return the assistivePostfix
  841. */
  842. public String getAssistivePostfix() {
  843. return assistivePostfix;
  844. }
  845. @Override
  846. protected com.google.gwt.user.client.Element getContainerElement() {
  847. // in GWT 1.5 this method is used in PopupPanel constructor
  848. if (contents == null) {
  849. return super.getContainerElement();
  850. }
  851. return DOM.asOld(contents);
  852. }
  853. private Event headerDragPending;
  854. @Override
  855. public void onBrowserEvent(final Event event) {
  856. boolean bubble = true;
  857. final int type = event.getTypeInt();
  858. final Element target = DOM.eventGetTarget(event);
  859. if (resizing || resizeBox == target) {
  860. onResizeEvent(event);
  861. bubble = false;
  862. // if clicked or key ENTER or SPACE is pressed
  863. } else if (isClosable() && target == closeBox) {
  864. if (type == Event.ONCLICK || (type == Event.ONKEYUP
  865. && (isKeyEnterOrSpace(event.getKeyCode()))
  866. || event.getKeyCode() == KeyCodes.KEY_ESCAPE)) {
  867. closeWindow();
  868. }
  869. bubble = false;
  870. } else if (target == maximizeRestoreBox) {
  871. // if ESC is pressed, close the window
  872. if (type == Event.ONKEYUP
  873. && event.getKeyCode() == KeyCodes.KEY_ESCAPE) {
  874. closeWindow();
  875. }
  876. // handled in connector
  877. // if clicked or key ENTER or SPACE is pressed
  878. else if (type != Event.ONCLICK && !(type == Event.ONKEYUP
  879. && isKeyEnterOrSpace(event.getKeyCode()))) {
  880. bubble = false;
  881. }
  882. } else if (header.isOrHasChild(target) && !dragging) {
  883. // dblclick handled in connector
  884. if (type != Event.ONDBLCLICK && draggable) {
  885. if (type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART) {
  886. /**
  887. * Prevents accidental selection of window caption or
  888. * content. (#12726)
  889. */
  890. event.preventDefault();
  891. headerDragPending = event;
  892. bubble = false;
  893. } else if (type == Event.ONMOUSEMOVE
  894. && headerDragPending != null) {
  895. // ie won't work unless this is set here
  896. dragging = true;
  897. onDragEvent(headerDragPending);
  898. onDragEvent(event);
  899. headerDragPending = null;
  900. bubble = false;
  901. } else if (type != Event.ONMOUSEMOVE) {
  902. // The event can propagate to the parent in case it is a
  903. // mouse move event. This is needed for tooltips to work in
  904. // header and footer, see Ticket #19073
  905. headerDragPending = null;
  906. bubble = false;
  907. } else {
  908. headerDragPending = null;
  909. }
  910. }
  911. if (type == Event.ONCLICK) {
  912. activateOnClick();
  913. }
  914. } else if (footer.isOrHasChild(target) && !dragging) {
  915. onDragEvent(event);
  916. if (type != Event.ONMOUSEMOVE) {
  917. // This is needed for tooltips to work in header and footer, see
  918. // Ticket #19073
  919. bubble = false;
  920. }
  921. } else if (dragging || !contents.isOrHasChild(target)) {
  922. onDragEvent(event);
  923. bubble = false;
  924. } else if (type == Event.ONCLICK) {
  925. activateOnClick();
  926. }
  927. /*
  928. * If clicking on other than the content, move focus to the window.
  929. * After that this windows e.g. gets all keyboard shortcuts.
  930. */
  931. if ((type == Event.ONMOUSEDOWN || type == Event.ONTOUCHSTART)
  932. && !contentPanel.getElement().isOrHasChild(target)
  933. && target != closeBox && target != maximizeRestoreBox) {
  934. contentPanel.focus();
  935. }
  936. if (!bubble) {
  937. event.stopPropagation();
  938. }
  939. // Super.onBrowserEvent takes care of Handlers added by the
  940. // ClickEventHandler
  941. super.onBrowserEvent(event);
  942. }
  943. private void activateOnClick() {
  944. // clicked inside window or inside header, ensure to be on top
  945. if (!isActive()) {
  946. bringToFront();
  947. }
  948. }
  949. private void closeWindow() {
  950. // Send the close event to the server
  951. client.updateVariable(id, "close", true, true);
  952. }
  953. private void onResizeEvent(Event event) {
  954. if (resizable && WidgetUtil.isTouchEventOrLeftMouseButton(event)) {
  955. switch (event.getTypeInt()) {
  956. case Event.ONMOUSEDOWN:
  957. case Event.ONTOUCHSTART:
  958. if (!isActive()) {
  959. bringToFront();
  960. }
  961. showResizingCurtain();
  962. if (BrowserInfo.get().isIE()) {
  963. resizeBox.getStyle().setVisibility(Visibility.HIDDEN);
  964. }
  965. resizing = true;
  966. startX = WidgetUtil.getTouchOrMouseClientX(event);
  967. startY = WidgetUtil.getTouchOrMouseClientY(event);
  968. origW = getElement().getOffsetWidth();
  969. origH = getElement().getOffsetHeight();
  970. DOM.setCapture(getElement());
  971. event.preventDefault();
  972. break;
  973. case Event.ONMOUSEUP:
  974. case Event.ONTOUCHEND:
  975. setSize(event, true);
  976. case Event.ONTOUCHCANCEL:
  977. DOM.releaseCapture(getElement());
  978. case Event.ONLOSECAPTURE:
  979. hideResizingCurtain();
  980. if (BrowserInfo.get().isIE()) {
  981. resizeBox.getStyle().clearVisibility();
  982. }
  983. resizing = false;
  984. break;
  985. case Event.ONMOUSEMOVE:
  986. case Event.ONTOUCHMOVE:
  987. if (resizing) {
  988. centered = false;
  989. setSize(event, false);
  990. event.preventDefault();
  991. }
  992. break;
  993. default:
  994. event.preventDefault();
  995. break;
  996. }
  997. }
  998. }
  999. /**
  1000. * TODO check if we need to support this with touch based devices.
  1001. *
  1002. * Checks if the cursor was inside the browser content area when the event
  1003. * happened.
  1004. *
  1005. * @param event
  1006. * The event to be checked
  1007. * @return true, if the cursor is inside the browser content area
  1008. *
  1009. * false, otherwise
  1010. */
  1011. private boolean cursorInsideBrowserContentArea(Event event) {
  1012. if (event.getClientX() < 0 || event.getClientY() < 0) {
  1013. // Outside to the left or above
  1014. return false;
  1015. }
  1016. if (event.getClientX() > Window.getClientWidth()
  1017. || event.getClientY() > Window.getClientHeight()) {
  1018. // Outside to the right or below
  1019. return false;
  1020. }
  1021. return true;
  1022. }
  1023. private void setSize(Event event, boolean updateVariables) {
  1024. if (!cursorInsideBrowserContentArea(event)) {
  1025. // Only drag while cursor is inside the browser client area
  1026. return;
  1027. }
  1028. int w = WidgetUtil.getTouchOrMouseClientX(event) - startX + origW;
  1029. int h = WidgetUtil.getTouchOrMouseClientY(event) - startY + origH;
  1030. w = Math.max(w, getMinWidth());
  1031. h = Math.max(h, getMinHeight());
  1032. setWidth(w + "px");
  1033. setHeight(h + "px");
  1034. if (updateVariables) {
  1035. // sending width back always as pixels, no need for unit
  1036. client.updateVariable(id, "width", w, false);
  1037. client.updateVariable(id, "height", h, true);
  1038. }
  1039. if (updateVariables || !resizeLazy) {
  1040. // Resize has finished or is not lazy
  1041. updateContentsSize();
  1042. } else {
  1043. // Lazy resize - wait for a while before re-rendering contents
  1044. delayedContentsSizeUpdater.trigger();
  1045. }
  1046. }
  1047. private int getMinHeight() {
  1048. return getPixelValue(getElement().getStyle().getProperty("minHeight"));
  1049. }
  1050. private int getMinWidth() {
  1051. return getPixelValue(getElement().getStyle().getProperty("minWidth"));
  1052. }
  1053. private static int getPixelValue(String size) {
  1054. if (size == null || !size.endsWith("px")) {
  1055. return -1;
  1056. } else {
  1057. return Integer.parseInt(size.substring(0, size.length() - 2));
  1058. }
  1059. }
  1060. public void updateContentsSize() {
  1061. LayoutManager layoutManager = getLayoutManager();
  1062. layoutManager.setNeedsMeasureRecursively(
  1063. ConnectorMap.get(client).getConnector(this));
  1064. layoutManager.layoutNow();
  1065. }
  1066. private native void addTransitionEndLayoutListener(Element e)
  1067. /*-{
  1068. var self = this;
  1069. e.addEventListener("transitionend", function(e) {
  1070. if (e.propertyName == "width" || e.propertyName == 'height') {
  1071. $entry(function() {
  1072. self.@com.vaadin.client.ui.VWindow::updateContentsSize()();
  1073. })();
  1074. }
  1075. });
  1076. }-*/;
  1077. @Override
  1078. public void setWidth(String width) {
  1079. // Override PopupPanel which sets the width to the contents
  1080. getElement().getStyle().setProperty("width", width);
  1081. // Update v-has-width in case undefined window is resized
  1082. setStyleName("v-has-width", width != null && !width.isEmpty());
  1083. }
  1084. @Override
  1085. public void setHeight(String height) {
  1086. // Override PopupPanel which sets the height to the contents
  1087. getElement().getStyle().setProperty("height", height);
  1088. // Update v-has-height in case undefined window is resized
  1089. setStyleName("v-has-height", height != null && !height.isEmpty());
  1090. }
  1091. private void onDragEvent(Event event) {
  1092. if (!WidgetUtil.isTouchEventOrLeftMouseButton(event)) {
  1093. return;
  1094. }
  1095. switch (DOM.eventGetType(event)) {
  1096. case Event.ONTOUCHSTART:
  1097. if (event.getTouches().length() > 1) {
  1098. return;
  1099. }
  1100. case Event.ONMOUSEDOWN:
  1101. if (!isActive()) {
  1102. bringToFront();
  1103. }
  1104. beginMovingWindow(event);
  1105. break;
  1106. case Event.ONMOUSEUP:
  1107. case Event.ONTOUCHEND:
  1108. case Event.ONTOUCHCANCEL:
  1109. case Event.ONLOSECAPTURE:
  1110. stopMovingWindow();
  1111. break;
  1112. case Event.ONMOUSEMOVE:
  1113. case Event.ONTOUCHMOVE:
  1114. moveWindow(event);
  1115. break;
  1116. default:
  1117. break;
  1118. }
  1119. }
  1120. private void moveWindow(Event event) {
  1121. if (dragging) {
  1122. centered = false;
  1123. if (cursorInsideBrowserContentArea(event)) {
  1124. // Only drag while cursor is inside the browser client area
  1125. final int x = WidgetUtil.getTouchOrMouseClientX(event) - startX
  1126. + origX;
  1127. final int y = WidgetUtil.getTouchOrMouseClientY(event) - startY
  1128. + origY;
  1129. setPopupPosition(x, y);
  1130. }
  1131. DOM.eventPreventDefault(event);
  1132. }
  1133. }
  1134. private void beginMovingWindow(Event event) {
  1135. if (draggable) {
  1136. showDraggingCurtain();
  1137. dragging = true;
  1138. startX = WidgetUtil.getTouchOrMouseClientX(event);
  1139. startY = WidgetUtil.getTouchOrMouseClientY(event);
  1140. origX = DOM.getAbsoluteLeft(getElement());
  1141. origY = DOM.getAbsoluteTop(getElement());
  1142. DOM.setCapture(getElement());
  1143. DOM.eventPreventDefault(event);
  1144. }
  1145. }
  1146. private void stopMovingWindow() {
  1147. dragging = false;
  1148. hideDraggingCurtain();
  1149. DOM.releaseCapture(getElement());
  1150. // fire move event
  1151. fireEvent(new WindowMoveEvent(uidlPositionX, uidlPositionY));
  1152. }
  1153. @Override
  1154. public boolean onEventPreview(Event event) {
  1155. if (dragging) {
  1156. onDragEvent(event);
  1157. return false;
  1158. } else if (resizing) {
  1159. onResizeEvent(event);
  1160. return false;
  1161. }
  1162. // TODO This is probably completely unnecessary as the modality curtain
  1163. // prevents events from reaching other windows and any security check
  1164. // must be done on the server side and not here.
  1165. // The code here is also run many times as each VWindow has an event
  1166. // preview but we cannot check only the current VWindow here (e.g.
  1167. // if(isTopMost) {...}) because PopupPanel will cause all events that
  1168. // are not cancelled here and target this window to be consume():d
  1169. // meaning the event won't be sent to the rest of the preview handlers.
  1170. if (getTopmostWindow() != null && getTopmostWindow().vaadinModality) {
  1171. // Topmost window is modal. Cancel the event if it targets something
  1172. // outside that window (except debug console...)
  1173. if (DOM.getCaptureElement() != null) {
  1174. // Allow events when capture is set
  1175. return true;
  1176. }
  1177. final Element target = event.getEventTarget().cast();
  1178. if (!DOM.isOrHasChild(getTopmostWindow().getElement(), target)) {
  1179. // not within the modal window, but let's see if it's in the
  1180. // debug window
  1181. Widget w = WidgetUtil.findWidget(target);
  1182. while (w != null) {
  1183. if (w instanceof VDebugWindow) {
  1184. return true; // allow debug-window clicks
  1185. } else if (ConnectorMap.get(client).isConnector(w)) {
  1186. return false;
  1187. }
  1188. w = w.getParent();
  1189. }
  1190. return false;
  1191. }
  1192. }
  1193. return true;
  1194. }
  1195. @Override
  1196. public void addStyleDependentName(String styleSuffix) {
  1197. // VWindow's getStyleElement() does not return the same element as
  1198. // getElement(), so we need to override this.
  1199. setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix,
  1200. true);
  1201. }
  1202. @Override
  1203. public ShortcutActionHandler getShortcutActionHandler() {
  1204. return shortcutHandler;
  1205. }
  1206. @Override
  1207. public void onScroll(ScrollEvent event) {
  1208. client.updateVariable(id, "scrollTop", contentPanel.getScrollPosition(),
  1209. false);
  1210. client.updateVariable(id, "scrollLeft",
  1211. contentPanel.getHorizontalScrollPosition(), false);
  1212. }
  1213. @Override
  1214. public void onKeyDown(KeyDownEvent event) {
  1215. if (vaadinModality && event.getNativeKeyCode() == KeyCodes.KEY_BACKSPACE
  1216. && !isFocusedElementEditable()) {
  1217. event.preventDefault();
  1218. }
  1219. if (shortcutHandler != null) {
  1220. shortcutHandler
  1221. .handleKeyboardEvent(Event.as(event.getNativeEvent()));
  1222. return;
  1223. }
  1224. }
  1225. @Override
  1226. public void onBlur(BlurEvent event) {
  1227. if (connector.hasEventListener(EventId.BLUR)) {
  1228. client.updateVariable(id, EventId.BLUR, "", true);
  1229. }
  1230. }
  1231. @Override
  1232. public void onFocus(FocusEvent event) {
  1233. if (connector.hasEventListener(EventId.FOCUS)) {
  1234. client.updateVariable(id, EventId.FOCUS, "", true);
  1235. }
  1236. }
  1237. @Override
  1238. public void focus() {
  1239. // We don't want to use contentPanel.focus() as that will use a timer in
  1240. // Chrome/Safari and ultimately run focus events in the wrong order when
  1241. // opening a modal window and focusing some other component at the same
  1242. // time
  1243. contentPanel.getElement().focus();
  1244. }
  1245. private int getDecorationHeight() {
  1246. LayoutManager lm = getLayoutManager();
  1247. int headerHeight = lm.getOuterHeight(header);
  1248. int footerHeight = lm.getOuterHeight(footer);
  1249. return headerHeight + footerHeight;
  1250. }
  1251. private LayoutManager getLayoutManager() {
  1252. return LayoutManager.get(client);
  1253. }
  1254. private int getDecorationWidth() {
  1255. LayoutManager layoutManager = getLayoutManager();
  1256. return layoutManager.getOuterWidth(getElement())
  1257. - contentPanel.getElement().getOffsetWidth();
  1258. }
  1259. /**
  1260. * Allows to specify which connectors contain the description for the
  1261. * window. Text contained in the widgets of the connectors will be read by
  1262. * assistive devices when it is opened.
  1263. * <p>
  1264. * When the provided array is empty, an existing description is removed.
  1265. *
  1266. * @param connectors
  1267. * with the connectors of the widgets to use as description
  1268. */
  1269. public void setAssistiveDescription(Connector[] connectors) {
  1270. if (connectors != null) {
  1271. assistiveConnectors = connectors;
  1272. if (connectors.length == 0) {
  1273. Roles.getDialogRole()
  1274. .removeAriaDescribedbyProperty(getElement());
  1275. } else {
  1276. Id[] ids = new Id[connectors.length];
  1277. for (int index = 0; index < connectors.length; index++) {
  1278. if (connectors[index] == null) {
  1279. throw new IllegalArgumentException(
  1280. "All values in parameter description need to be non-null");
  1281. }
  1282. Element element = ((ComponentConnector) connectors[index])
  1283. .getWidget().getElement();
  1284. AriaHelper.ensureHasId(element);
  1285. ids[index] = Id.of(element);
  1286. }
  1287. Roles.getDialogRole().setAriaDescribedbyProperty(getElement(),
  1288. ids);
  1289. }
  1290. } else {
  1291. throw new IllegalArgumentException(
  1292. "Parameter description must be non-null");
  1293. }
  1294. }
  1295. /**
  1296. * Gets the connectors that are used as assistive description. Text
  1297. * contained in these connectors will be read by assistive devices when the
  1298. * window is opened.
  1299. *
  1300. * @return list of previously set connectors
  1301. */
  1302. public List<Connector> getAssistiveDescription() {
  1303. return Collections.unmodifiableList(Arrays.asList(assistiveConnectors));
  1304. }
  1305. /**
  1306. * Sets the WAI-ARIA role the window.
  1307. *
  1308. * This role defines how an assistive device handles a window. Available
  1309. * roles are alertdialog and dialog (@see
  1310. * <a href="http://www.w3.org/TR/2011/CR-wai-aria-20110118/roles">Roles
  1311. * Model</a>).
  1312. *
  1313. * The default role is dialog.
  1314. *
  1315. * @param role
  1316. * WAI-ARIA role to set for the window
  1317. */
  1318. public void setWaiAriaRole(WindowRole role) {
  1319. if (role == WindowRole.ALERTDIALOG) {
  1320. Roles.getAlertdialogRole().set(getElement());
  1321. } else {
  1322. Roles.getDialogRole().set(getElement());
  1323. }
  1324. }
  1325. /**
  1326. * Registers the handlers that prevent to leave the window using the
  1327. * Tab-key.
  1328. * <p>
  1329. * The value of the parameter doTabStop is stored and used for non-modal
  1330. * windows. For modal windows, the handlers are always registered, while
  1331. * preserving the stored value.
  1332. *
  1333. * @param doTabStop
  1334. * true to prevent leaving the window, false to allow leaving the
  1335. * window for non modal windows
  1336. */
  1337. public void setTabStopEnabled(boolean doTabStop) {
  1338. this.doTabStop = doTabStop;
  1339. if (doTabStop || vaadinModality) {
  1340. addTabBlockHandlers();
  1341. } else {
  1342. removeTabBlockHandlers();
  1343. }
  1344. }
  1345. /**
  1346. * Adds a Handler for when user moves the window.
  1347. *
  1348. * @since 7.1.9
  1349. *
  1350. * @return {@link HandlerRegistration} used to remove the handler
  1351. */
  1352. public HandlerRegistration addMoveHandler(WindowMoveHandler handler) {
  1353. return addHandler(handler, WindowMoveEvent.getType());
  1354. }
  1355. /**
  1356. * Adds a Handler for window order change event.
  1357. *
  1358. * @since 8.0
  1359. *
  1360. * @return registration object to deregister the handler
  1361. */
  1362. public static HandlerRegistration addWindowOrderHandler(
  1363. WindowOrderHandler handler) {
  1364. return WINDOW_ORDER_HANDLER.addHandler(WindowOrderEvent.getType(),
  1365. handler);
  1366. }
  1367. /**
  1368. * Checks if a modal window is currently open.
  1369. *
  1370. * @return <code>true</code> if a modal window is open, <code>false</code>
  1371. * otherwise.
  1372. */
  1373. public static boolean isModalWindowOpen() {
  1374. return Document.get().getBody()
  1375. .hasClassName(MODAL_WINDOW_OPEN_CLASSNAME);
  1376. }
  1377. private boolean isKeyEnterOrSpace(int keyCode) {
  1378. return keyCode == KeyCodes.KEY_ENTER || keyCode == KeyCodes.KEY_SPACE;
  1379. }
  1380. }