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

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