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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.Arrays;
  7. import java.util.Comparator;
  8. import com.google.gwt.core.client.Scheduler;
  9. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  10. import com.google.gwt.event.dom.client.BlurEvent;
  11. import com.google.gwt.event.dom.client.BlurHandler;
  12. import com.google.gwt.event.dom.client.FocusEvent;
  13. import com.google.gwt.event.dom.client.FocusHandler;
  14. import com.google.gwt.event.dom.client.KeyDownEvent;
  15. import com.google.gwt.event.dom.client.KeyDownHandler;
  16. import com.google.gwt.event.dom.client.ScrollEvent;
  17. import com.google.gwt.event.dom.client.ScrollHandler;
  18. import com.google.gwt.user.client.Command;
  19. import com.google.gwt.user.client.DOM;
  20. import com.google.gwt.user.client.Element;
  21. import com.google.gwt.user.client.Event;
  22. import com.google.gwt.user.client.Window;
  23. import com.google.gwt.user.client.ui.HasWidgets;
  24. import com.google.gwt.user.client.ui.RootPanel;
  25. import com.google.gwt.user.client.ui.Widget;
  26. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  27. import com.vaadin.terminal.gwt.client.BrowserInfo;
  28. import com.vaadin.terminal.gwt.client.ComponentConnector;
  29. import com.vaadin.terminal.gwt.client.Console;
  30. import com.vaadin.terminal.gwt.client.EventId;
  31. import com.vaadin.terminal.gwt.client.Focusable;
  32. import com.vaadin.terminal.gwt.client.Util;
  33. import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
  34. /**
  35. * "Sub window" component.
  36. *
  37. * @author Vaadin Ltd
  38. */
  39. public class VWindow extends VOverlay implements ShortcutActionHandlerOwner,
  40. ScrollHandler, KeyDownHandler, FocusHandler, BlurHandler, Focusable {
  41. /**
  42. * Minimum allowed height of a window. This refers to the content area, not
  43. * the outer borders.
  44. */
  45. private static final int MIN_CONTENT_AREA_HEIGHT = 100;
  46. /**
  47. * Minimum allowed width of a window. This refers to the content area, not
  48. * the outer borders.
  49. */
  50. private static final int MIN_CONTENT_AREA_WIDTH = 150;
  51. private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
  52. private static boolean orderingDefered;
  53. public static final String CLASSNAME = "v-window";
  54. /**
  55. * Difference between offsetWidth and inner width for the content area.
  56. */
  57. private int contentAreaBorderPadding = -1;
  58. /**
  59. * Pixels used by inner borders and paddings horizontally (calculated only
  60. * once). This is the difference between the width of the root element and
  61. * the content area, such that if root element width is set to "XYZpx" the
  62. * inner width (width-border-padding) of the content area is
  63. * X-contentAreaRootDifference.
  64. */
  65. private int contentAreaToRootDifference = -1;
  66. private static final int STACKING_OFFSET_PIXELS = 15;
  67. public static final int Z_INDEX = 10000;
  68. ComponentConnector layout;
  69. Element contents;
  70. private Element header;
  71. private Element footer;
  72. private Element resizeBox;
  73. final FocusableScrollPanel contentPanel = new FocusableScrollPanel();
  74. private boolean dragging;
  75. private int startX;
  76. private int startY;
  77. private int origX;
  78. private int origY;
  79. private boolean resizing;
  80. private int origW;
  81. private int origH;
  82. Element closeBox;
  83. protected ApplicationConnection client;
  84. String id;
  85. ShortcutActionHandler shortcutHandler;
  86. /** Last known positionx read from UIDL or updated to application connection */
  87. private int uidlPositionX = -1;
  88. /** Last known positiony read from UIDL or updated to application connection */
  89. private int uidlPositionY = -1;
  90. boolean vaadinModality = false;
  91. boolean resizable = true;
  92. private boolean draggable = true;
  93. boolean resizeLazy = false;
  94. private Element modalityCurtain;
  95. private Element draggingCurtain;
  96. private Element resizingCurtain;
  97. private Element headerText;
  98. private boolean closable = true;
  99. boolean dynamicWidth = false;
  100. boolean dynamicHeight = false;
  101. boolean layoutRelativeWidth = false;
  102. boolean layoutRelativeHeight = false;
  103. // If centered (via UIDL), the window should stay in the centered -mode
  104. // until a position is received from the server, or the user moves or
  105. // resizes the window.
  106. boolean centered = false;
  107. private String width;
  108. private String height;
  109. boolean immediate;
  110. private Element wrapper, wrapper2;
  111. boolean visibilityChangesDisabled;
  112. int bringToFrontSequence = -1;
  113. private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200,
  114. new ScheduledCommand() {
  115. public void execute() {
  116. updateContentsSize();
  117. }
  118. });
  119. public VWindow() {
  120. super(false, false, true); // no autohide, not modal, shadow
  121. // Different style of shadow for windows
  122. setShadowStyle("window");
  123. final int order = windowOrder.size();
  124. setWindowOrder(order);
  125. windowOrder.add(this);
  126. constructDOM();
  127. setPopupPosition(order * STACKING_OFFSET_PIXELS, order
  128. * STACKING_OFFSET_PIXELS);
  129. contentPanel.addScrollHandler(this);
  130. contentPanel.addKeyDownHandler(this);
  131. contentPanel.addFocusHandler(this);
  132. contentPanel.addBlurHandler(this);
  133. }
  134. public void bringToFront() {
  135. int curIndex = windowOrder.indexOf(this);
  136. if (curIndex + 1 < windowOrder.size()) {
  137. windowOrder.remove(this);
  138. windowOrder.add(this);
  139. for (; curIndex < windowOrder.size(); curIndex++) {
  140. windowOrder.get(curIndex).setWindowOrder(curIndex);
  141. }
  142. }
  143. }
  144. /**
  145. * Returns true if this window is the topmost VWindow
  146. *
  147. * @return
  148. */
  149. private boolean isActive() {
  150. return windowOrder.get(windowOrder.size() - 1).equals(this);
  151. }
  152. private void setWindowOrder(int order) {
  153. setZIndex(order + Z_INDEX);
  154. }
  155. @Override
  156. protected void setZIndex(int zIndex) {
  157. super.setZIndex(zIndex);
  158. if (vaadinModality) {
  159. DOM.setStyleAttribute(getModalityCurtain(), "zIndex", "" + zIndex);
  160. }
  161. }
  162. protected Element getModalityCurtain() {
  163. if (modalityCurtain == null) {
  164. modalityCurtain = DOM.createDiv();
  165. modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain");
  166. }
  167. return modalityCurtain;
  168. }
  169. protected void constructDOM() {
  170. setStyleName(CLASSNAME);
  171. header = DOM.createDiv();
  172. DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
  173. headerText = DOM.createDiv();
  174. DOM.setElementProperty(headerText, "className", CLASSNAME + "-header");
  175. contents = DOM.createDiv();
  176. DOM.setElementProperty(contents, "className", CLASSNAME + "-contents");
  177. footer = DOM.createDiv();
  178. DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
  179. resizeBox = DOM.createDiv();
  180. DOM.setElementProperty(resizeBox, "className", CLASSNAME + "-resizebox");
  181. closeBox = DOM.createDiv();
  182. DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
  183. DOM.appendChild(footer, resizeBox);
  184. wrapper = DOM.createDiv();
  185. DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
  186. wrapper2 = DOM.createDiv();
  187. DOM.setElementProperty(wrapper2, "className", CLASSNAME + "-wrap2");
  188. DOM.appendChild(wrapper2, closeBox);
  189. DOM.appendChild(wrapper2, header);
  190. DOM.appendChild(header, headerText);
  191. DOM.appendChild(wrapper2, contents);
  192. DOM.appendChild(wrapper2, footer);
  193. DOM.appendChild(wrapper, wrapper2);
  194. DOM.appendChild(super.getContainerElement(), wrapper);
  195. sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK
  196. | Event.ONLOSECAPTURE);
  197. setWidget(contentPanel);
  198. }
  199. /**
  200. * Calling this method will defer ordering algorithm, to order windows based
  201. * on servers bringToFront and modality instructions. Non changed windows
  202. * will be left intact.
  203. */
  204. static void deferOrdering() {
  205. if (!orderingDefered) {
  206. orderingDefered = true;
  207. Scheduler.get().scheduleFinally(new Command() {
  208. public void execute() {
  209. doServerSideOrdering();
  210. }
  211. });
  212. }
  213. }
  214. private static void doServerSideOrdering() {
  215. orderingDefered = false;
  216. VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]);
  217. Arrays.sort(array, new Comparator<VWindow>() {
  218. public int compare(VWindow o1, VWindow o2) {
  219. /*
  220. * Order by modality, then by bringtofront sequence.
  221. */
  222. if (o1.vaadinModality && !o2.vaadinModality) {
  223. return 1;
  224. } else if (!o1.vaadinModality && o2.vaadinModality) {
  225. return -1;
  226. } else if (o1.bringToFrontSequence > o2.bringToFrontSequence) {
  227. return 1;
  228. } else if (o1.bringToFrontSequence < o2.bringToFrontSequence) {
  229. return -1;
  230. } else {
  231. return 0;
  232. }
  233. }
  234. });
  235. for (int i = 0; i < array.length; i++) {
  236. VWindow w = array[i];
  237. if (w.bringToFrontSequence != -1 || w.vaadinModality) {
  238. w.bringToFront();
  239. w.bringToFrontSequence = -1;
  240. }
  241. }
  242. }
  243. @Override
  244. public void setVisible(boolean visible) {
  245. /*
  246. * Visibility with VWindow works differently than with other Paintables
  247. * in Vaadin. Invisible VWindows are not attached to DOM at all. Flag is
  248. * used to avoid visibility call from
  249. * ApplicationConnection.updateComponent();
  250. */
  251. if (!visibilityChangesDisabled) {
  252. super.setVisible(visible);
  253. }
  254. }
  255. void setDraggable(boolean draggable) {
  256. if (this.draggable == draggable) {
  257. return;
  258. }
  259. this.draggable = draggable;
  260. setCursorProperties();
  261. }
  262. private void setCursorProperties() {
  263. if (!draggable) {
  264. header.getStyle().setProperty("cursor", "default");
  265. footer.getStyle().setProperty("cursor", "default");
  266. } else {
  267. header.getStyle().setProperty("cursor", "");
  268. footer.getStyle().setProperty("cursor", "");
  269. }
  270. }
  271. void setNaturalWidth() {
  272. /*
  273. * Use max(layout width, window width) i.e layout content width or
  274. * caption width. We remove the previous set width so the width is
  275. * allowed to shrink. All widths are measured as outer sizes, i.e. the
  276. * borderWidth is added to the content.
  277. */
  278. DOM.setStyleAttribute(getElement(), "width", "");
  279. // Content
  280. int contentWidth = contentPanel.getElement().getScrollWidth();
  281. contentWidth += getContentAreaToRootDifference();
  282. // Window width (caption)
  283. int windowCaptionWidth = getOffsetWidth();
  284. int naturalWidth = (contentWidth > windowCaptionWidth ? contentWidth
  285. : windowCaptionWidth);
  286. setWidth(naturalWidth + "px");
  287. }
  288. private int getContentAreaToRootDifference() {
  289. if (contentAreaToRootDifference < 0) {
  290. measure();
  291. }
  292. return contentAreaToRootDifference;
  293. }
  294. private void measure() {
  295. if (!isAttached()) {
  296. return;
  297. }
  298. contentAreaBorderPadding = Util.measureHorizontalPaddingAndBorder(
  299. contents, 4);
  300. int wrapperPaddingBorder = Util.measureHorizontalPaddingAndBorder(
  301. wrapper, 0)
  302. + Util.measureHorizontalPaddingAndBorder(wrapper2, 0);
  303. contentAreaToRootDifference = wrapperPaddingBorder
  304. + contentAreaBorderPadding;
  305. }
  306. /**
  307. * Sets the closable state of the window. Additionally hides/shows the close
  308. * button according to the new state.
  309. *
  310. * @param closable
  311. * true if the window can be closed by the user
  312. */
  313. protected void setClosable(boolean closable) {
  314. if (this.closable == closable) {
  315. return;
  316. }
  317. this.closable = closable;
  318. if (closable) {
  319. DOM.setStyleAttribute(closeBox, "display", "");
  320. } else {
  321. DOM.setStyleAttribute(closeBox, "display", "none");
  322. }
  323. }
  324. /**
  325. * Returns the closable state of the sub window. If the sub window is
  326. * closable a decoration (typically an X) is shown to the user. By clicking
  327. * on the X the user can close the window.
  328. *
  329. * @return true if the sub window is closable
  330. */
  331. protected boolean isClosable() {
  332. return closable;
  333. }
  334. @Override
  335. public void show() {
  336. if (vaadinModality) {
  337. showModalityCurtain();
  338. }
  339. super.show();
  340. }
  341. @Override
  342. public void hide() {
  343. if (vaadinModality) {
  344. hideModalityCurtain();
  345. }
  346. super.hide();
  347. }
  348. void setVaadinModality(boolean modality) {
  349. vaadinModality = modality;
  350. if (vaadinModality) {
  351. if (isAttached()) {
  352. showModalityCurtain();
  353. }
  354. deferOrdering();
  355. } else {
  356. if (modalityCurtain != null) {
  357. if (isAttached()) {
  358. hideModalityCurtain();
  359. }
  360. modalityCurtain = null;
  361. }
  362. }
  363. }
  364. private void showModalityCurtain() {
  365. DOM.setStyleAttribute(getModalityCurtain(), "zIndex",
  366. "" + (windowOrder.indexOf(this) + Z_INDEX));
  367. if (isShowing()) {
  368. RootPanel.getBodyElement().insertBefore(getModalityCurtain(),
  369. getElement());
  370. } else {
  371. DOM.appendChild(RootPanel.getBodyElement(), getModalityCurtain());
  372. }
  373. }
  374. private void hideModalityCurtain() {
  375. DOM.removeChild(RootPanel.getBodyElement(), modalityCurtain);
  376. }
  377. /*
  378. * Shows an empty div on top of all other content; used when moving, so that
  379. * iframes (etc) do not steal event.
  380. */
  381. private void showDraggingCurtain() {
  382. DOM.appendChild(RootPanel.getBodyElement(), getDraggingCurtain());
  383. }
  384. private void hideDraggingCurtain() {
  385. if (draggingCurtain != null) {
  386. DOM.removeChild(RootPanel.getBodyElement(), draggingCurtain);
  387. }
  388. }
  389. /*
  390. * Shows an empty div on top of all other content; used when resizing, so
  391. * that iframes (etc) do not steal event.
  392. */
  393. private void showResizingCurtain() {
  394. DOM.appendChild(RootPanel.getBodyElement(), getResizingCurtain());
  395. }
  396. private void hideResizingCurtain() {
  397. if (resizingCurtain != null) {
  398. DOM.removeChild(RootPanel.getBodyElement(), resizingCurtain);
  399. }
  400. }
  401. private Element getDraggingCurtain() {
  402. if (draggingCurtain == null) {
  403. draggingCurtain = createCurtain();
  404. draggingCurtain.setClassName(CLASSNAME + "-draggingCurtain");
  405. }
  406. return draggingCurtain;
  407. }
  408. private Element getResizingCurtain() {
  409. if (resizingCurtain == null) {
  410. resizingCurtain = createCurtain();
  411. resizingCurtain.setClassName(CLASSNAME + "-resizingCurtain");
  412. }
  413. return resizingCurtain;
  414. }
  415. private Element createCurtain() {
  416. Element curtain = DOM.createDiv();
  417. DOM.setStyleAttribute(curtain, "position", "absolute");
  418. DOM.setStyleAttribute(curtain, "top", "0px");
  419. DOM.setStyleAttribute(curtain, "left", "0px");
  420. DOM.setStyleAttribute(curtain, "width", "100%");
  421. DOM.setStyleAttribute(curtain, "height", "100%");
  422. DOM.setStyleAttribute(curtain, "zIndex", "" + VOverlay.Z_INDEX);
  423. return curtain;
  424. }
  425. void setResizable(boolean resizability) {
  426. resizable = resizability;
  427. if (resizability) {
  428. DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
  429. DOM.setElementProperty(resizeBox, "className", CLASSNAME
  430. + "-resizebox");
  431. } else {
  432. DOM.setElementProperty(footer, "className", CLASSNAME + "-footer "
  433. + CLASSNAME + "-footer-noresize");
  434. DOM.setElementProperty(resizeBox, "className", CLASSNAME
  435. + "-resizebox " + CLASSNAME + "-resizebox-disabled");
  436. }
  437. }
  438. @Override
  439. public void setPopupPosition(int left, int top) {
  440. if (top < 0) {
  441. // ensure window is not moved out of browser window from top of the
  442. // screen
  443. top = 0;
  444. }
  445. super.setPopupPosition(left, top);
  446. if (left != uidlPositionX && client != null) {
  447. client.updateVariable(id, "positionx", left, false);
  448. uidlPositionX = left;
  449. }
  450. if (top != uidlPositionY && client != null) {
  451. client.updateVariable(id, "positiony", top, false);
  452. uidlPositionY = top;
  453. }
  454. }
  455. public void setCaption(String c) {
  456. setCaption(c, null);
  457. }
  458. public void setCaption(String c, String icon) {
  459. String html = Util.escapeHTML(c);
  460. if (icon != null) {
  461. icon = client.translateVaadinUri(icon);
  462. html = "<img src=\"" + Util.escapeAttribute(icon)
  463. + "\" class=\"v-icon\" />" + html;
  464. }
  465. DOM.setInnerHTML(headerText, html);
  466. }
  467. @Override
  468. protected Element getContainerElement() {
  469. // in GWT 1.5 this method is used in PopupPanel constructor
  470. if (contents == null) {
  471. return super.getContainerElement();
  472. }
  473. return contents;
  474. }
  475. @Override
  476. public void onBrowserEvent(final Event event) {
  477. boolean bubble = true;
  478. final int type = event.getTypeInt();
  479. final Element target = DOM.eventGetTarget(event);
  480. if (client != null && header.isOrHasChild(target)) {
  481. // Handle window caption tooltips
  482. client.handleTooltipEvent(event, this);
  483. }
  484. if (resizing || resizeBox == target) {
  485. onResizeEvent(event);
  486. bubble = false;
  487. } else if (isClosable() && target == closeBox) {
  488. if (type == Event.ONCLICK) {
  489. onCloseClick();
  490. }
  491. bubble = false;
  492. } else if (dragging || !contents.isOrHasChild(target)) {
  493. onDragEvent(event);
  494. bubble = false;
  495. } else if (type == Event.ONCLICK) {
  496. // clicked inside window, ensure to be on top
  497. if (!isActive()) {
  498. bringToFront();
  499. }
  500. }
  501. /*
  502. * If clicking on other than the content, move focus to the window.
  503. * After that this windows e.g. gets all keyboard shortcuts.
  504. */
  505. if (type == Event.ONMOUSEDOWN
  506. && !contentPanel.getElement().isOrHasChild(target)
  507. && target != closeBox) {
  508. contentPanel.focus();
  509. }
  510. if (!bubble) {
  511. event.stopPropagation();
  512. } else {
  513. // Super.onBrowserEvent takes care of Handlers added by the
  514. // ClickEventHandler
  515. super.onBrowserEvent(event);
  516. }
  517. }
  518. private void onCloseClick() {
  519. client.updateVariable(id, "close", true, true);
  520. }
  521. private void onResizeEvent(Event event) {
  522. if (resizable && Util.isTouchEventOrLeftMouseButton(event)) {
  523. switch (event.getTypeInt()) {
  524. case Event.ONMOUSEDOWN:
  525. case Event.ONTOUCHSTART:
  526. if (!isActive()) {
  527. bringToFront();
  528. }
  529. showResizingCurtain();
  530. if (BrowserInfo.get().isIE()) {
  531. DOM.setStyleAttribute(resizeBox, "visibility", "hidden");
  532. }
  533. resizing = true;
  534. startX = Util.getTouchOrMouseClientX(event);
  535. startY = Util.getTouchOrMouseClientY(event);
  536. origW = getElement().getOffsetWidth();
  537. origH = getElement().getOffsetHeight();
  538. DOM.setCapture(getElement());
  539. event.preventDefault();
  540. break;
  541. case Event.ONMOUSEUP:
  542. case Event.ONTOUCHEND:
  543. setSize(event, true);
  544. case Event.ONTOUCHCANCEL:
  545. DOM.releaseCapture(getElement());
  546. case Event.ONLOSECAPTURE:
  547. hideResizingCurtain();
  548. if (BrowserInfo.get().isIE()) {
  549. DOM.setStyleAttribute(resizeBox, "visibility", "");
  550. }
  551. resizing = false;
  552. break;
  553. case Event.ONMOUSEMOVE:
  554. case Event.ONTOUCHMOVE:
  555. if (resizing) {
  556. centered = false;
  557. setSize(event, false);
  558. event.preventDefault();
  559. }
  560. break;
  561. default:
  562. event.preventDefault();
  563. break;
  564. }
  565. }
  566. }
  567. /**
  568. * TODO check if we need to support this with touch based devices.
  569. *
  570. * Checks if the cursor was inside the browser content area when the event
  571. * happened.
  572. *
  573. * @param event
  574. * The event to be checked
  575. * @return true, if the cursor is inside the browser content area
  576. *
  577. * false, otherwise
  578. */
  579. private boolean cursorInsideBrowserContentArea(Event event) {
  580. if (event.getClientX() < 0 || event.getClientY() < 0) {
  581. // Outside to the left or above
  582. return false;
  583. }
  584. if (event.getClientX() > Window.getClientWidth()
  585. || event.getClientY() > Window.getClientHeight()) {
  586. // Outside to the right or below
  587. return false;
  588. }
  589. return true;
  590. }
  591. private void setSize(Event event, boolean updateVariables) {
  592. if (!cursorInsideBrowserContentArea(event)) {
  593. // Only drag while cursor is inside the browser client area
  594. return;
  595. }
  596. int w = Util.getTouchOrMouseClientX(event) - startX + origW;
  597. if (w < MIN_CONTENT_AREA_WIDTH + getContentAreaToRootDifference()) {
  598. w = MIN_CONTENT_AREA_WIDTH + getContentAreaToRootDifference();
  599. }
  600. int h = Util.getTouchOrMouseClientY(event) - startY + origH;
  601. if (h < MIN_CONTENT_AREA_HEIGHT + getExtraHeight()) {
  602. h = MIN_CONTENT_AREA_HEIGHT + getExtraHeight();
  603. }
  604. setWidth(w + "px");
  605. setHeight(h + "px");
  606. if (updateVariables) {
  607. // sending width back always as pixels, no need for unit
  608. client.updateVariable(id, "width", w, false);
  609. client.updateVariable(id, "height", h, immediate);
  610. }
  611. if (updateVariables || !resizeLazy) {
  612. // Resize has finished or is not lazy
  613. updateContentsSize();
  614. } else {
  615. // Lazy resize - wait for a while before re-rendering contents
  616. delayedContentsSizeUpdater.trigger();
  617. }
  618. }
  619. private void updateContentsSize() {
  620. // Update child widget dimensions
  621. if (client != null) {
  622. client.handleComponentRelativeSize(layout.getWidget());
  623. client.runDescendentsLayout((HasWidgets) layout.getWidget());
  624. }
  625. Util.runWebkitOverflowAutoFix(contentPanel.getElement());
  626. client.doLayout(false);
  627. }
  628. @Override
  629. /**
  630. * Width is set to the out-most element (v-window).
  631. *
  632. * This function should never be called with percentage values (it will
  633. * throw an exception)
  634. */
  635. public void setWidth(String width) {
  636. this.width = width;
  637. if (!isAttached()) {
  638. return;
  639. }
  640. if (width != null && !"".equals(width)) {
  641. int rootPixelWidth = -1;
  642. if (width.indexOf("px") < 0) {
  643. /*
  644. * Convert non-pixel values to pixels by setting the width and
  645. * then measuring it. Updates the "width" variable with the
  646. * pixel width.
  647. */
  648. DOM.setStyleAttribute(getElement(), "width", width);
  649. rootPixelWidth = getElement().getOffsetWidth();
  650. width = rootPixelWidth + "px";
  651. } else {
  652. rootPixelWidth = Integer.parseInt(width.substring(0,
  653. width.indexOf("px")));
  654. }
  655. // "width" now contains the new width in pixels
  656. // Apply the new pixel width
  657. getElement().getStyle().setProperty("width", width);
  658. // Caculate the inner width of the content area
  659. int contentAreaInnerWidth = rootPixelWidth
  660. - getContentAreaToRootDifference();
  661. if (contentAreaInnerWidth < MIN_CONTENT_AREA_WIDTH) {
  662. contentAreaInnerWidth = MIN_CONTENT_AREA_WIDTH;
  663. int rootWidth = contentAreaInnerWidth
  664. + getContentAreaToRootDifference();
  665. DOM.setStyleAttribute(getElement(), "width", rootWidth + "px");
  666. }
  667. updateShadowSizeAndPosition();
  668. }
  669. }
  670. @Override
  671. /**
  672. * Height is set to the out-most element (v-window).
  673. *
  674. * This function should never be called with percentage values (it will
  675. * throw an exception)
  676. *
  677. * @param height A CSS string specifying the new height of the window.
  678. * An empty string or null clears the height and lets
  679. * the browser to compute it based on the window contents.
  680. */
  681. public void setHeight(String height) {
  682. if (!isAttached()
  683. || (height == null ? this.height == null : height
  684. .equals(this.height))) {
  685. return;
  686. }
  687. if (height == null || "".equals(height)) {
  688. getElement().getStyle().clearHeight();
  689. contentPanel.getElement().getStyle().clearHeight();
  690. } else {
  691. getElement().getStyle().setProperty("height", height);
  692. int contentHeight = getElement().getOffsetHeight()
  693. - getExtraHeight();
  694. if (contentHeight < MIN_CONTENT_AREA_HEIGHT) {
  695. contentHeight = MIN_CONTENT_AREA_HEIGHT;
  696. int rootHeight = contentHeight + getExtraHeight();
  697. getElement().getStyle()
  698. .setProperty("height", rootHeight + "px");
  699. }
  700. contentPanel.getElement().getStyle()
  701. .setProperty("height", contentHeight + "px");
  702. }
  703. this.height = height;
  704. updateShadowSizeAndPosition();
  705. }
  706. int getExtraHeight() {
  707. return header.getOffsetHeight() + footer.getOffsetHeight();
  708. }
  709. private void onDragEvent(Event event) {
  710. if (!Util.isTouchEventOrLeftMouseButton(event)) {
  711. return;
  712. }
  713. switch (DOM.eventGetType(event)) {
  714. case Event.ONTOUCHSTART:
  715. if (event.getTouches().length() > 1) {
  716. return;
  717. }
  718. case Event.ONMOUSEDOWN:
  719. if (!isActive()) {
  720. bringToFront();
  721. }
  722. beginMovingWindow(event);
  723. break;
  724. case Event.ONMOUSEUP:
  725. case Event.ONTOUCHEND:
  726. case Event.ONTOUCHCANCEL:
  727. case Event.ONLOSECAPTURE:
  728. stopMovingWindow();
  729. break;
  730. case Event.ONMOUSEMOVE:
  731. case Event.ONTOUCHMOVE:
  732. moveWindow(event);
  733. break;
  734. default:
  735. break;
  736. }
  737. }
  738. private void moveWindow(Event event) {
  739. if (dragging) {
  740. centered = false;
  741. if (cursorInsideBrowserContentArea(event)) {
  742. // Only drag while cursor is inside the browser client area
  743. final int x = Util.getTouchOrMouseClientX(event) - startX
  744. + origX;
  745. final int y = Util.getTouchOrMouseClientY(event) - startY
  746. + origY;
  747. setPopupPosition(x, y);
  748. }
  749. DOM.eventPreventDefault(event);
  750. }
  751. }
  752. private void beginMovingWindow(Event event) {
  753. if (draggable) {
  754. showDraggingCurtain();
  755. dragging = true;
  756. startX = Util.getTouchOrMouseClientX(event);
  757. startY = Util.getTouchOrMouseClientY(event);
  758. origX = DOM.getAbsoluteLeft(getElement());
  759. origY = DOM.getAbsoluteTop(getElement());
  760. DOM.setCapture(getElement());
  761. DOM.eventPreventDefault(event);
  762. }
  763. }
  764. private void stopMovingWindow() {
  765. dragging = false;
  766. hideDraggingCurtain();
  767. DOM.releaseCapture(getElement());
  768. }
  769. @Override
  770. public boolean onEventPreview(Event event) {
  771. if (dragging) {
  772. onDragEvent(event);
  773. return false;
  774. } else if (resizing) {
  775. onResizeEvent(event);
  776. return false;
  777. } else if (vaadinModality) {
  778. // return false when modal and outside window
  779. final Element target = event.getEventTarget().cast();
  780. if (DOM.getCaptureElement() != null) {
  781. // Allow events when capture is set
  782. return true;
  783. }
  784. if (!DOM.isOrHasChild(getElement(), target)) {
  785. // not within the modal window, but let's see if it's in the
  786. // debug window
  787. Widget w = Util.findWidget(target, null);
  788. while (w != null) {
  789. if (w instanceof Console) {
  790. return true; // allow debug-window clicks
  791. } else if (w instanceof ComponentConnector) {
  792. return false;
  793. }
  794. w = w.getParent();
  795. }
  796. return false;
  797. }
  798. }
  799. return true;
  800. }
  801. @Override
  802. public void addStyleDependentName(String styleSuffix) {
  803. // VWindow's getStyleElement() does not return the same element as
  804. // getElement(), so we need to override this.
  805. setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix,
  806. true);
  807. }
  808. @Override
  809. protected void onAttach() {
  810. super.onAttach();
  811. setWidth(width);
  812. setHeight(height);
  813. }
  814. void requestLayout() {
  815. if (dynamicWidth && !layoutRelativeWidth) {
  816. setNaturalWidth();
  817. }
  818. // layout size change may affect its available space (scrollbars)
  819. client.handleComponentRelativeSize(layout.getWidget());
  820. }
  821. public ShortcutActionHandler getShortcutActionHandler() {
  822. return shortcutHandler;
  823. }
  824. public void onScroll(ScrollEvent event) {
  825. client.updateVariable(id, "scrollTop",
  826. contentPanel.getScrollPosition(), false);
  827. client.updateVariable(id, "scrollLeft",
  828. contentPanel.getHorizontalScrollPosition(), false);
  829. }
  830. public void onKeyDown(KeyDownEvent event) {
  831. if (shortcutHandler != null) {
  832. shortcutHandler
  833. .handleKeyboardEvent(Event.as(event.getNativeEvent()));
  834. return;
  835. }
  836. }
  837. public void onBlur(BlurEvent event) {
  838. if (client.hasEventListeners(this, EventId.BLUR)) {
  839. client.updateVariable(id, EventId.BLUR, "", true);
  840. }
  841. }
  842. public void onFocus(FocusEvent event) {
  843. if (client.hasEventListeners(this, EventId.FOCUS)) {
  844. client.updateVariable(id, EventId.FOCUS, "", true);
  845. }
  846. }
  847. public void focus() {
  848. contentPanel.focus();
  849. }
  850. }