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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250
  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 java.util.Set;
  9. import com.google.gwt.core.client.Scheduler;
  10. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  11. import com.google.gwt.event.dom.client.BlurEvent;
  12. import com.google.gwt.event.dom.client.BlurHandler;
  13. import com.google.gwt.event.dom.client.DomEvent.Type;
  14. import com.google.gwt.event.dom.client.FocusEvent;
  15. import com.google.gwt.event.dom.client.FocusHandler;
  16. import com.google.gwt.event.dom.client.KeyDownEvent;
  17. import com.google.gwt.event.dom.client.KeyDownHandler;
  18. import com.google.gwt.event.dom.client.ScrollEvent;
  19. import com.google.gwt.event.dom.client.ScrollHandler;
  20. import com.google.gwt.event.shared.EventHandler;
  21. import com.google.gwt.event.shared.HandlerRegistration;
  22. import com.google.gwt.user.client.Command;
  23. import com.google.gwt.user.client.DOM;
  24. import com.google.gwt.user.client.Element;
  25. import com.google.gwt.user.client.Event;
  26. import com.google.gwt.user.client.Window;
  27. import com.google.gwt.user.client.ui.Frame;
  28. import com.google.gwt.user.client.ui.HasWidgets;
  29. import com.google.gwt.user.client.ui.RootPanel;
  30. import com.google.gwt.user.client.ui.Widget;
  31. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  32. import com.vaadin.terminal.gwt.client.BrowserInfo;
  33. import com.vaadin.terminal.gwt.client.Console;
  34. import com.vaadin.terminal.gwt.client.Container;
  35. import com.vaadin.terminal.gwt.client.EventId;
  36. import com.vaadin.terminal.gwt.client.Focusable;
  37. import com.vaadin.terminal.gwt.client.RenderSpace;
  38. import com.vaadin.terminal.gwt.client.UIDL;
  39. import com.vaadin.terminal.gwt.client.Util;
  40. import com.vaadin.terminal.gwt.client.VPaintableWidget;
  41. import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.BeforeShortcutActionListener;
  42. import com.vaadin.terminal.gwt.client.ui.ShortcutActionHandler.ShortcutActionHandlerOwner;
  43. /**
  44. * "Sub window" component.
  45. *
  46. * @author Vaadin Ltd
  47. */
  48. public class VWindow extends VOverlay implements Container,
  49. ShortcutActionHandlerOwner, ScrollHandler, KeyDownHandler,
  50. FocusHandler, BlurHandler, BeforeShortcutActionListener, Focusable {
  51. /**
  52. * Minimum allowed height of a window. This refers to the content area, not
  53. * the outer borders.
  54. */
  55. private static final int MIN_CONTENT_AREA_HEIGHT = 100;
  56. /**
  57. * Minimum allowed width of a window. This refers to the content area, not
  58. * the outer borders.
  59. */
  60. private static final int MIN_CONTENT_AREA_WIDTH = 150;
  61. private static ArrayList<VWindow> windowOrder = new ArrayList<VWindow>();
  62. private static boolean orderingDefered;
  63. public static final String CLASSNAME = "v-window";
  64. /**
  65. * Difference between offsetWidth and inner width for the content area.
  66. */
  67. private int contentAreaBorderPadding = -1;
  68. /**
  69. * Pixels used by inner borders and paddings horizontally (calculated only
  70. * once). This is the difference between the width of the root element and
  71. * the content area, such that if root element width is set to "XYZpx" the
  72. * inner width (width-border-padding) of the content area is
  73. * X-contentAreaRootDifference.
  74. */
  75. private int contentAreaToRootDifference = -1;
  76. private static final int STACKING_OFFSET_PIXELS = 15;
  77. public static final int Z_INDEX = 10000;
  78. private VPaintableWidget layout;
  79. private Element contents;
  80. private Element header;
  81. private Element footer;
  82. private Element resizeBox;
  83. private final FocusableScrollPanel contentPanel = new FocusableScrollPanel();
  84. private boolean dragging;
  85. private int startX;
  86. private int startY;
  87. private int origX;
  88. private int origY;
  89. private boolean resizing;
  90. private int origW;
  91. private int origH;
  92. private Element closeBox;
  93. protected ApplicationConnection client;
  94. private String id;
  95. ShortcutActionHandler shortcutHandler;
  96. /** Last known positionx read from UIDL or updated to application connection */
  97. private int uidlPositionX = -1;
  98. /** Last known positiony read from UIDL or updated to application connection */
  99. private int uidlPositionY = -1;
  100. private boolean vaadinModality = false;
  101. private boolean resizable = true;
  102. private boolean draggable = true;
  103. private boolean resizeLazy = false;
  104. private Element modalityCurtain;
  105. private Element draggingCurtain;
  106. private Element headerText;
  107. private boolean closable = true;
  108. boolean dynamicWidth = false;
  109. boolean dynamicHeight = false;
  110. boolean layoutRelativeWidth = false;
  111. boolean layoutRelativeHeight = false;
  112. // If centered (via UIDL), the window should stay in the centered -mode
  113. // until a position is received from the server, or the user moves or
  114. // resizes the window.
  115. boolean centered = false;
  116. private RenderSpace renderSpace = new RenderSpace(MIN_CONTENT_AREA_WIDTH,
  117. MIN_CONTENT_AREA_HEIGHT, true);
  118. private String width;
  119. private String height;
  120. private boolean immediate;
  121. private Element wrapper, wrapper2;
  122. private ClickEventHandler clickEventHandler = new ClickEventHandler(this,
  123. VPanel.CLICK_EVENT_IDENTIFIER) {
  124. @Override
  125. protected <H extends EventHandler> HandlerRegistration registerHandler(
  126. H handler, Type<H> type) {
  127. return addDomHandler(handler, type);
  128. }
  129. };
  130. private boolean visibilityChangesDisabled;
  131. private int bringToFrontSequence = -1;
  132. private VLazyExecutor delayedContentsSizeUpdater = new VLazyExecutor(200,
  133. new ScheduledCommand() {
  134. public void execute() {
  135. updateContentsSize();
  136. }
  137. });
  138. public VWindow() {
  139. super(false, false, true); // no autohide, not modal, shadow
  140. // Different style of shadow for windows
  141. setShadowStyle("window");
  142. final int order = windowOrder.size();
  143. setWindowOrder(order);
  144. windowOrder.add(this);
  145. constructDOM();
  146. setPopupPosition(order * STACKING_OFFSET_PIXELS, order
  147. * STACKING_OFFSET_PIXELS);
  148. contentPanel.addScrollHandler(this);
  149. contentPanel.addKeyDownHandler(this);
  150. contentPanel.addFocusHandler(this);
  151. contentPanel.addBlurHandler(this);
  152. }
  153. public void bringToFront() {
  154. int curIndex = windowOrder.indexOf(this);
  155. if (curIndex + 1 < windowOrder.size()) {
  156. windowOrder.remove(this);
  157. windowOrder.add(this);
  158. for (; curIndex < windowOrder.size(); curIndex++) {
  159. windowOrder.get(curIndex).setWindowOrder(curIndex);
  160. }
  161. }
  162. }
  163. /**
  164. * Returns true if this window is the topmost VWindow
  165. *
  166. * @return
  167. */
  168. private boolean isActive() {
  169. return windowOrder.get(windowOrder.size() - 1).equals(this);
  170. }
  171. private void setWindowOrder(int order) {
  172. setZIndex(order + Z_INDEX);
  173. }
  174. @Override
  175. protected void setZIndex(int zIndex) {
  176. super.setZIndex(zIndex);
  177. if (vaadinModality) {
  178. DOM.setStyleAttribute(getModalityCurtain(), "zIndex", "" + zIndex);
  179. }
  180. }
  181. protected Element getModalityCurtain() {
  182. if (modalityCurtain == null) {
  183. modalityCurtain = DOM.createDiv();
  184. modalityCurtain.setClassName(CLASSNAME + "-modalitycurtain");
  185. }
  186. return modalityCurtain;
  187. }
  188. protected void constructDOM() {
  189. setStyleName(CLASSNAME);
  190. header = DOM.createDiv();
  191. DOM.setElementProperty(header, "className", CLASSNAME + "-outerheader");
  192. headerText = DOM.createDiv();
  193. DOM.setElementProperty(headerText, "className", CLASSNAME + "-header");
  194. contents = DOM.createDiv();
  195. DOM.setElementProperty(contents, "className", CLASSNAME + "-contents");
  196. footer = DOM.createDiv();
  197. DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
  198. resizeBox = DOM.createDiv();
  199. DOM.setElementProperty(resizeBox, "className", CLASSNAME + "-resizebox");
  200. closeBox = DOM.createDiv();
  201. DOM.setElementProperty(closeBox, "className", CLASSNAME + "-closebox");
  202. DOM.appendChild(footer, resizeBox);
  203. wrapper = DOM.createDiv();
  204. DOM.setElementProperty(wrapper, "className", CLASSNAME + "-wrap");
  205. wrapper2 = DOM.createDiv();
  206. DOM.setElementProperty(wrapper2, "className", CLASSNAME + "-wrap2");
  207. DOM.appendChild(wrapper2, closeBox);
  208. DOM.appendChild(wrapper2, header);
  209. DOM.appendChild(header, headerText);
  210. DOM.appendChild(wrapper2, contents);
  211. DOM.appendChild(wrapper2, footer);
  212. DOM.appendChild(wrapper, wrapper2);
  213. DOM.appendChild(super.getContainerElement(), wrapper);
  214. sinkEvents(Event.MOUSEEVENTS | Event.TOUCHEVENTS | Event.ONCLICK
  215. | Event.ONLOSECAPTURE);
  216. setWidget(contentPanel);
  217. }
  218. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  219. id = uidl.getId();
  220. this.client = client;
  221. // Workaround needed for Testing Tools (GWT generates window DOM
  222. // slightly different in different browsers).
  223. DOM.setElementProperty(closeBox, "id", id + "_window_close");
  224. if (uidl.hasAttribute("invisible")) {
  225. hide();
  226. return;
  227. }
  228. if (!uidl.hasAttribute("cached")) {
  229. if (uidl.getBooleanAttribute("modal") != vaadinModality) {
  230. setVaadinModality(!vaadinModality);
  231. }
  232. if (!isAttached()) {
  233. setVisible(false); // hide until possible centering
  234. show();
  235. }
  236. if (uidl.getBooleanAttribute("resizable") != resizable) {
  237. setResizable(!resizable);
  238. }
  239. resizeLazy = uidl.hasAttribute(VView.RESIZE_LAZY);
  240. setDraggable(!uidl.hasAttribute("fixedposition"));
  241. // Caption must be set before required header size is measured. If
  242. // the caption attribute is missing the caption should be cleared.
  243. setCaption(uidl.getStringAttribute("caption"),
  244. uidl.getStringAttribute("icon"));
  245. }
  246. visibilityChangesDisabled = true;
  247. if (client.updateComponent(this, uidl, false)) {
  248. return;
  249. }
  250. visibilityChangesDisabled = false;
  251. clickEventHandler.handleEventHandlerRegistration(client);
  252. immediate = uidl.hasAttribute("immediate");
  253. setClosable(!uidl.getBooleanAttribute("readonly"));
  254. // Initialize the position form UIDL
  255. int positionx = uidl.getIntVariable("positionx");
  256. int positiony = uidl.getIntVariable("positiony");
  257. if (positionx >= 0 || positiony >= 0) {
  258. if (positionx < 0) {
  259. positionx = 0;
  260. }
  261. if (positiony < 0) {
  262. positiony = 0;
  263. }
  264. setPopupPosition(positionx, positiony);
  265. }
  266. boolean showingUrl = false;
  267. int childIndex = 0;
  268. UIDL childUidl = uidl.getChildUIDL(childIndex++);
  269. while ("open".equals(childUidl.getTag())) {
  270. // TODO multiple opens with the same target will in practice just
  271. // open the last one - should we fix that somehow?
  272. final String parsedUri = client.translateVaadinUri(childUidl
  273. .getStringAttribute("src"));
  274. if (!childUidl.hasAttribute("name")) {
  275. final Frame frame = new Frame();
  276. DOM.setStyleAttribute(frame.getElement(), "width", "100%");
  277. DOM.setStyleAttribute(frame.getElement(), "height", "100%");
  278. DOM.setStyleAttribute(frame.getElement(), "border", "0px");
  279. frame.setUrl(parsedUri);
  280. contentPanel.setWidget(frame);
  281. showingUrl = true;
  282. } else {
  283. final String target = childUidl.getStringAttribute("name");
  284. Window.open(parsedUri, target, "");
  285. }
  286. childUidl = uidl.getChildUIDL(childIndex++);
  287. }
  288. final VPaintableWidget lo = client.getPaintable(childUidl);
  289. if (layout != null) {
  290. if (layout != lo) {
  291. // remove old
  292. client.unregisterPaintable(layout);
  293. contentPanel.remove(layout.getWidgetForPaintable());
  294. // add new
  295. if (!showingUrl) {
  296. contentPanel.setWidget(lo.getWidgetForPaintable());
  297. }
  298. layout = lo;
  299. }
  300. } else if (!showingUrl) {
  301. contentPanel.setWidget(lo.getWidgetForPaintable());
  302. layout = lo;
  303. }
  304. dynamicWidth = !uidl.hasAttribute("width");
  305. dynamicHeight = !uidl.hasAttribute("height");
  306. layoutRelativeWidth = uidl.hasAttribute("layoutRelativeWidth");
  307. layoutRelativeHeight = uidl.hasAttribute("layoutRelativeHeight");
  308. if (dynamicWidth && layoutRelativeWidth) {
  309. /*
  310. * Relative layout width, fix window width before rendering (width
  311. * according to caption)
  312. */
  313. setNaturalWidth();
  314. }
  315. layout.updateFromUIDL(childUidl, client);
  316. if (!dynamicHeight && layoutRelativeWidth) {
  317. /*
  318. * Relative layout width, and fixed height. Must update the size to
  319. * be able to take scrollbars into account (layout gets narrower
  320. * space if it is higher than the window) -> only vertical scrollbar
  321. */
  322. client.runDescendentsLayout(this);
  323. }
  324. /*
  325. * No explicit width is set and the layout does not have relative width
  326. * so fix the size according to the layout.
  327. */
  328. if (dynamicWidth && !layoutRelativeWidth) {
  329. setNaturalWidth();
  330. }
  331. if (dynamicHeight && layoutRelativeHeight) {
  332. // Prevent resizing until height has been fixed
  333. resizable = false;
  334. }
  335. // we may have actions and notifications
  336. if (uidl.getChildCount() > 1) {
  337. final int cnt = uidl.getChildCount();
  338. for (int i = 1; i < cnt; i++) {
  339. childUidl = uidl.getChildUIDL(i);
  340. if (childUidl.getTag().equals("actions")) {
  341. if (shortcutHandler == null) {
  342. shortcutHandler = new ShortcutActionHandler(id, client);
  343. }
  344. shortcutHandler.updateActionMap(childUidl);
  345. }
  346. }
  347. }
  348. // setting scrollposition must happen after children is rendered
  349. contentPanel.setScrollPosition(uidl.getIntVariable("scrollTop"));
  350. contentPanel.setHorizontalScrollPosition(uidl
  351. .getIntVariable("scrollLeft"));
  352. // Center this window on screen if requested
  353. // This has to be here because we might not know the content size before
  354. // everything is painted into the window
  355. if (uidl.getBooleanAttribute("center")) {
  356. // mark as centered - this is unset on move/resize
  357. centered = true;
  358. center();
  359. } else {
  360. // don't try to center the window anymore
  361. centered = false;
  362. }
  363. updateShadowSizeAndPosition();
  364. setVisible(true);
  365. boolean sizeReduced = false;
  366. // ensure window is not larger than browser window
  367. if (getOffsetWidth() > Window.getClientWidth()) {
  368. setWidth(Window.getClientWidth() + "px");
  369. sizeReduced = true;
  370. }
  371. if (getOffsetHeight() > Window.getClientHeight()) {
  372. setHeight(Window.getClientHeight() + "px");
  373. sizeReduced = true;
  374. }
  375. if (dynamicHeight && layoutRelativeHeight) {
  376. /*
  377. * Window height is undefined, layout is 100% high so the layout
  378. * should define the initial window height but on resize the layout
  379. * should be as high as the window. We fix the height to deal with
  380. * this.
  381. */
  382. int h = contents.getOffsetHeight() + getExtraHeight();
  383. int w = getElement().getOffsetWidth();
  384. client.updateVariable(id, "height", h, false);
  385. client.updateVariable(id, "width", w, true);
  386. }
  387. if (sizeReduced) {
  388. // If we changed the size we need to update the size of the child
  389. // component if it is relative (#3407)
  390. client.runDescendentsLayout(this);
  391. }
  392. Util.runWebkitOverflowAutoFix(contentPanel.getElement());
  393. client.getView().scrollIntoView(uidl);
  394. if (uidl.hasAttribute("bringToFront")) {
  395. /*
  396. * Focus as a side-efect. Will be overridden by
  397. * ApplicationConnection if another component was focused by the
  398. * server side.
  399. */
  400. contentPanel.focus();
  401. bringToFrontSequence = uidl.getIntAttribute("bringToFront");
  402. deferOrdering();
  403. }
  404. }
  405. /**
  406. * Calling this method will defer ordering algorithm, to order windows based
  407. * on servers bringToFront and modality instructions. Non changed windows
  408. * will be left intact.
  409. */
  410. private static void deferOrdering() {
  411. if (!orderingDefered) {
  412. orderingDefered = true;
  413. Scheduler.get().scheduleFinally(new Command() {
  414. public void execute() {
  415. doServerSideOrdering();
  416. }
  417. });
  418. }
  419. }
  420. private static void doServerSideOrdering() {
  421. orderingDefered = false;
  422. VWindow[] array = windowOrder.toArray(new VWindow[windowOrder.size()]);
  423. Arrays.sort(array, new Comparator<VWindow>() {
  424. public int compare(VWindow o1, VWindow o2) {
  425. /*
  426. * Order by modality, then by bringtofront sequence.
  427. */
  428. if (o1.vaadinModality && !o2.vaadinModality) {
  429. return 1;
  430. } else if (!o1.vaadinModality && o2.vaadinModality) {
  431. return -1;
  432. } else if (o1.bringToFrontSequence > o2.bringToFrontSequence) {
  433. return 1;
  434. } else if (o1.bringToFrontSequence < o2.bringToFrontSequence) {
  435. return -1;
  436. } else {
  437. return 0;
  438. }
  439. }
  440. });
  441. for (int i = 0; i < array.length; i++) {
  442. VWindow w = array[i];
  443. if (w.bringToFrontSequence != -1 || w.vaadinModality) {
  444. w.bringToFront();
  445. w.bringToFrontSequence = -1;
  446. }
  447. }
  448. }
  449. @Override
  450. public void setVisible(boolean visible) {
  451. /*
  452. * Visibility with VWindow works differently than with other Paintables
  453. * in Vaadin. Invisible VWindows are not attached to DOM at all. Flag is
  454. * used to avoid visibility call from
  455. * ApplicationConnection.updateComponent();
  456. */
  457. if (!visibilityChangesDisabled) {
  458. super.setVisible(visible);
  459. }
  460. }
  461. private void setDraggable(boolean draggable) {
  462. if (this.draggable == draggable) {
  463. return;
  464. }
  465. this.draggable = draggable;
  466. if (!this.draggable) {
  467. header.getStyle().setProperty("cursor", "default");
  468. } else {
  469. header.getStyle().setProperty("cursor", "");
  470. }
  471. }
  472. private void setNaturalWidth() {
  473. /*
  474. * Use max(layout width, window width) i.e layout content width or
  475. * caption width. We remove the previous set width so the width is
  476. * allowed to shrink. All widths are measured as outer sizes, i.e. the
  477. * borderWidth is added to the content.
  478. */
  479. DOM.setStyleAttribute(getElement(), "width", "");
  480. // Content
  481. int contentWidth = contentPanel.getElement().getScrollWidth();
  482. contentWidth += getContentAreaToRootDifference();
  483. // Window width (caption)
  484. int windowCaptionWidth = getOffsetWidth();
  485. int naturalWidth = (contentWidth > windowCaptionWidth ? contentWidth
  486. : windowCaptionWidth);
  487. setWidth(naturalWidth + "px");
  488. }
  489. private int getContentAreaToRootDifference() {
  490. if (contentAreaToRootDifference < 0) {
  491. measure();
  492. }
  493. return contentAreaToRootDifference;
  494. }
  495. private void measure() {
  496. if (!isAttached()) {
  497. return;
  498. }
  499. contentAreaBorderPadding = Util.measureHorizontalPaddingAndBorder(
  500. contents, 4);
  501. int wrapperPaddingBorder = Util.measureHorizontalPaddingAndBorder(
  502. wrapper, 0)
  503. + Util.measureHorizontalPaddingAndBorder(wrapper2, 0);
  504. contentAreaToRootDifference = wrapperPaddingBorder
  505. + contentAreaBorderPadding;
  506. }
  507. /**
  508. * Sets the closable state of the window. Additionally hides/shows the close
  509. * button according to the new state.
  510. *
  511. * @param closable
  512. * true if the window can be closed by the user
  513. */
  514. protected void setClosable(boolean closable) {
  515. if (this.closable == closable) {
  516. return;
  517. }
  518. this.closable = closable;
  519. if (closable) {
  520. DOM.setStyleAttribute(closeBox, "display", "");
  521. } else {
  522. DOM.setStyleAttribute(closeBox, "display", "none");
  523. }
  524. }
  525. /**
  526. * Returns the closable state of the sub window. If the sub window is
  527. * closable a decoration (typically an X) is shown to the user. By clicking
  528. * on the X the user can close the window.
  529. *
  530. * @return true if the sub window is closable
  531. */
  532. protected boolean isClosable() {
  533. return closable;
  534. }
  535. @Override
  536. public void show() {
  537. if (vaadinModality) {
  538. showModalityCurtain();
  539. }
  540. super.show();
  541. }
  542. @Override
  543. public void hide() {
  544. if (vaadinModality) {
  545. hideModalityCurtain();
  546. }
  547. super.hide();
  548. }
  549. private void setVaadinModality(boolean modality) {
  550. vaadinModality = modality;
  551. if (vaadinModality) {
  552. if (isAttached()) {
  553. showModalityCurtain();
  554. }
  555. deferOrdering();
  556. } else {
  557. if (modalityCurtain != null) {
  558. if (isAttached()) {
  559. hideModalityCurtain();
  560. }
  561. modalityCurtain = null;
  562. }
  563. }
  564. }
  565. private void showModalityCurtain() {
  566. DOM.setStyleAttribute(getModalityCurtain(), "zIndex",
  567. "" + (windowOrder.indexOf(this) + Z_INDEX));
  568. if (isShowing()) {
  569. RootPanel.getBodyElement().insertBefore(getModalityCurtain(),
  570. getElement());
  571. } else {
  572. DOM.appendChild(RootPanel.getBodyElement(), getModalityCurtain());
  573. }
  574. }
  575. private void hideModalityCurtain() {
  576. DOM.removeChild(RootPanel.getBodyElement(), modalityCurtain);
  577. }
  578. /*
  579. * Shows (or hides) an empty div on top of all other content; used when
  580. * resizing or moving, so that iframes (etc) do not steal event.
  581. */
  582. private void showDraggingCurtain(boolean show) {
  583. if (show && draggingCurtain == null) {
  584. draggingCurtain = DOM.createDiv();
  585. DOM.setStyleAttribute(draggingCurtain, "position", "absolute");
  586. DOM.setStyleAttribute(draggingCurtain, "top", "0px");
  587. DOM.setStyleAttribute(draggingCurtain, "left", "0px");
  588. DOM.setStyleAttribute(draggingCurtain, "width", "100%");
  589. DOM.setStyleAttribute(draggingCurtain, "height", "100%");
  590. DOM.setStyleAttribute(draggingCurtain, "zIndex", ""
  591. + VOverlay.Z_INDEX);
  592. DOM.appendChild(RootPanel.getBodyElement(), draggingCurtain);
  593. } else if (!show && draggingCurtain != null) {
  594. DOM.removeChild(RootPanel.getBodyElement(), draggingCurtain);
  595. draggingCurtain = null;
  596. }
  597. }
  598. private void setResizable(boolean resizability) {
  599. resizable = resizability;
  600. if (resizability) {
  601. DOM.setElementProperty(footer, "className", CLASSNAME + "-footer");
  602. DOM.setElementProperty(resizeBox, "className", CLASSNAME
  603. + "-resizebox");
  604. } else {
  605. DOM.setElementProperty(footer, "className", CLASSNAME + "-footer "
  606. + CLASSNAME + "-footer-noresize");
  607. DOM.setElementProperty(resizeBox, "className", CLASSNAME
  608. + "-resizebox " + CLASSNAME + "-resizebox-disabled");
  609. }
  610. }
  611. @Override
  612. public void setPopupPosition(int left, int top) {
  613. if (top < 0) {
  614. // ensure window is not moved out of browser window from top of the
  615. // screen
  616. top = 0;
  617. }
  618. super.setPopupPosition(left, top);
  619. if (left != uidlPositionX && client != null) {
  620. client.updateVariable(id, "positionx", left, false);
  621. uidlPositionX = left;
  622. }
  623. if (top != uidlPositionY && client != null) {
  624. client.updateVariable(id, "positiony", top, false);
  625. uidlPositionY = top;
  626. }
  627. }
  628. public void setCaption(String c) {
  629. setCaption(c, null);
  630. }
  631. public void setCaption(String c, String icon) {
  632. String html = Util.escapeHTML(c);
  633. if (icon != null) {
  634. icon = client.translateVaadinUri(icon);
  635. html = "<img src=\"" + Util.escapeAttribute(icon)
  636. + "\" class=\"v-icon\" />" + html;
  637. }
  638. DOM.setInnerHTML(headerText, html);
  639. }
  640. @Override
  641. protected Element getContainerElement() {
  642. // in GWT 1.5 this method is used in PopupPanel constructor
  643. if (contents == null) {
  644. return super.getContainerElement();
  645. }
  646. return contents;
  647. }
  648. @Override
  649. public void onBrowserEvent(final Event event) {
  650. boolean bubble = true;
  651. final int type = event.getTypeInt();
  652. final Element target = DOM.eventGetTarget(event);
  653. if (client != null && header.isOrHasChild(target)) {
  654. // Handle window caption tooltips
  655. client.handleTooltipEvent(event, this);
  656. }
  657. if (resizing || resizeBox == target) {
  658. onResizeEvent(event);
  659. bubble = false;
  660. } else if (isClosable() && target == closeBox) {
  661. if (type == Event.ONCLICK) {
  662. onCloseClick();
  663. }
  664. bubble = false;
  665. } else if (dragging || !contents.isOrHasChild(target)) {
  666. onDragEvent(event);
  667. bubble = false;
  668. } else if (type == Event.ONCLICK) {
  669. // clicked inside window, ensure to be on top
  670. if (!isActive()) {
  671. bringToFront();
  672. }
  673. }
  674. /*
  675. * If clicking on other than the content, move focus to the window.
  676. * After that this windows e.g. gets all keyboard shortcuts.
  677. */
  678. if (type == Event.ONMOUSEDOWN
  679. && !contentPanel.getElement().isOrHasChild(target)
  680. && target != closeBox) {
  681. contentPanel.focus();
  682. }
  683. if (!bubble) {
  684. event.stopPropagation();
  685. } else {
  686. // Super.onBrowserEvent takes care of Handlers added by the
  687. // ClickEventHandler
  688. super.onBrowserEvent(event);
  689. }
  690. }
  691. private void onCloseClick() {
  692. client.updateVariable(id, "close", true, true);
  693. }
  694. private void onResizeEvent(Event event) {
  695. if (resizable) {
  696. switch (event.getTypeInt()) {
  697. case Event.ONMOUSEDOWN:
  698. case Event.ONTOUCHSTART:
  699. if (!isActive()) {
  700. bringToFront();
  701. }
  702. showDraggingCurtain(true);
  703. if (BrowserInfo.get().isIE()) {
  704. DOM.setStyleAttribute(resizeBox, "visibility", "hidden");
  705. }
  706. resizing = true;
  707. startX = Util.getTouchOrMouseClientX(event);
  708. startY = Util.getTouchOrMouseClientY(event);
  709. origW = getElement().getOffsetWidth();
  710. origH = getElement().getOffsetHeight();
  711. DOM.setCapture(getElement());
  712. event.preventDefault();
  713. break;
  714. case Event.ONMOUSEUP:
  715. case Event.ONTOUCHEND:
  716. setSize(event, true);
  717. case Event.ONTOUCHCANCEL:
  718. DOM.releaseCapture(getElement());
  719. case Event.ONLOSECAPTURE:
  720. showDraggingCurtain(false);
  721. if (BrowserInfo.get().isIE()) {
  722. DOM.setStyleAttribute(resizeBox, "visibility", "");
  723. }
  724. resizing = false;
  725. break;
  726. case Event.ONMOUSEMOVE:
  727. case Event.ONTOUCHMOVE:
  728. if (resizing) {
  729. centered = false;
  730. setSize(event, false);
  731. event.preventDefault();
  732. }
  733. break;
  734. default:
  735. event.preventDefault();
  736. break;
  737. }
  738. }
  739. }
  740. /**
  741. * TODO check if we need to support this with touch based devices.
  742. *
  743. * Checks if the cursor was inside the browser content area when the event
  744. * happened.
  745. *
  746. * @param event
  747. * The event to be checked
  748. * @return true, if the cursor is inside the browser content area
  749. *
  750. * false, otherwise
  751. */
  752. private boolean cursorInsideBrowserContentArea(Event event) {
  753. if (event.getClientX() < 0 || event.getClientY() < 0) {
  754. // Outside to the left or above
  755. return false;
  756. }
  757. if (event.getClientX() > Window.getClientWidth()
  758. || event.getClientY() > Window.getClientHeight()) {
  759. // Outside to the right or below
  760. return false;
  761. }
  762. return true;
  763. }
  764. private void setSize(Event event, boolean updateVariables) {
  765. if (!cursorInsideBrowserContentArea(event)) {
  766. // Only drag while cursor is inside the browser client area
  767. return;
  768. }
  769. int w = Util.getTouchOrMouseClientX(event) - startX + origW;
  770. if (w < MIN_CONTENT_AREA_WIDTH + getContentAreaToRootDifference()) {
  771. w = MIN_CONTENT_AREA_WIDTH + getContentAreaToRootDifference();
  772. }
  773. int h = Util.getTouchOrMouseClientY(event) - startY + origH;
  774. if (h < MIN_CONTENT_AREA_HEIGHT + getExtraHeight()) {
  775. h = MIN_CONTENT_AREA_HEIGHT + getExtraHeight();
  776. }
  777. setWidth(w + "px");
  778. setHeight(h + "px");
  779. if (updateVariables) {
  780. // sending width back always as pixels, no need for unit
  781. client.updateVariable(id, "width", w, false);
  782. client.updateVariable(id, "height", h, immediate);
  783. }
  784. if (updateVariables || !resizeLazy) {
  785. // Resize has finished or is not lazy
  786. updateContentsSize();
  787. } else {
  788. // Lazy resize - wait for a while before re-rendering contents
  789. delayedContentsSizeUpdater.trigger();
  790. }
  791. }
  792. private void updateContentsSize() {
  793. // Update child widget dimensions
  794. if (client != null) {
  795. client.handleComponentRelativeSize(layout.getWidgetForPaintable());
  796. client.runDescendentsLayout((HasWidgets) layout
  797. .getWidgetForPaintable());
  798. }
  799. Util.runWebkitOverflowAutoFix(contentPanel.getElement());
  800. }
  801. @Override
  802. /**
  803. * Width is set to the out-most element (v-window).
  804. *
  805. * This function should never be called with percentage values (it will
  806. * throw an exception)
  807. */
  808. public void setWidth(String width) {
  809. this.width = width;
  810. if (!isAttached()) {
  811. return;
  812. }
  813. if (width != null && !"".equals(width)) {
  814. int rootPixelWidth = -1;
  815. if (width.indexOf("px") < 0) {
  816. /*
  817. * Convert non-pixel values to pixels by setting the width and
  818. * then measuring it. Updates the "width" variable with the
  819. * pixel width.
  820. */
  821. DOM.setStyleAttribute(getElement(), "width", width);
  822. rootPixelWidth = getElement().getOffsetWidth();
  823. width = rootPixelWidth + "px";
  824. } else {
  825. rootPixelWidth = Integer.parseInt(width.substring(0,
  826. width.indexOf("px")));
  827. }
  828. // "width" now contains the new width in pixels
  829. // Apply the new pixel width
  830. getElement().getStyle().setProperty("width", width);
  831. // Caculate the inner width of the content area
  832. int contentAreaInnerWidth = rootPixelWidth
  833. - getContentAreaToRootDifference();
  834. if (contentAreaInnerWidth < MIN_CONTENT_AREA_WIDTH) {
  835. contentAreaInnerWidth = MIN_CONTENT_AREA_WIDTH;
  836. int rootWidth = contentAreaInnerWidth
  837. + getContentAreaToRootDifference();
  838. DOM.setStyleAttribute(getElement(), "width", rootWidth + "px");
  839. }
  840. renderSpace.setWidth(contentAreaInnerWidth);
  841. updateShadowSizeAndPosition();
  842. }
  843. }
  844. @Override
  845. /**
  846. * Height is set to the out-most element (v-window).
  847. *
  848. * This function should never be called with percentage values (it will
  849. * throw an exception)
  850. *
  851. * @param height A CSS string specifying the new height of the window.
  852. * An empty string or null clears the height and lets
  853. * the browser to compute it based on the window contents.
  854. */
  855. public void setHeight(String height) {
  856. if (!isAttached()
  857. || (height == null ? this.height == null : height
  858. .equals(this.height))) {
  859. return;
  860. }
  861. if (height == null || "".equals(height)) {
  862. getElement().getStyle().clearHeight();
  863. contentPanel.getElement().getStyle().clearHeight();
  864. // Reset to default, the exact value does not actually
  865. // matter as an undefined-height parent should not have
  866. // a relative-height child anyway.
  867. renderSpace.setHeight(MIN_CONTENT_AREA_HEIGHT);
  868. } else {
  869. getElement().getStyle().setProperty("height", height);
  870. int contentHeight = getElement().getOffsetHeight()
  871. - getExtraHeight();
  872. if (contentHeight < MIN_CONTENT_AREA_HEIGHT) {
  873. contentHeight = MIN_CONTENT_AREA_HEIGHT;
  874. int rootHeight = contentHeight + getExtraHeight();
  875. getElement().getStyle()
  876. .setProperty("height", rootHeight + "px");
  877. }
  878. renderSpace.setHeight(contentHeight);
  879. contentPanel.getElement().getStyle()
  880. .setProperty("height", contentHeight + "px");
  881. }
  882. this.height = height;
  883. updateShadowSizeAndPosition();
  884. }
  885. private int extraH = 0;
  886. private int getExtraHeight() {
  887. extraH = header.getOffsetHeight() + footer.getOffsetHeight();
  888. return extraH;
  889. }
  890. private void onDragEvent(Event event) {
  891. switch (DOM.eventGetType(event)) {
  892. case Event.ONTOUCHSTART:
  893. if (event.getTouches().length() > 1) {
  894. return;
  895. }
  896. case Event.ONMOUSEDOWN:
  897. if (!isActive()) {
  898. bringToFront();
  899. }
  900. beginMovingWindow(event);
  901. break;
  902. case Event.ONMOUSEUP:
  903. case Event.ONTOUCHEND:
  904. case Event.ONTOUCHCANCEL:
  905. case Event.ONLOSECAPTURE:
  906. stopMovingWindow();
  907. break;
  908. case Event.ONMOUSEMOVE:
  909. case Event.ONTOUCHMOVE:
  910. moveWindow(event);
  911. break;
  912. default:
  913. break;
  914. }
  915. }
  916. private void moveWindow(Event event) {
  917. if (dragging) {
  918. centered = false;
  919. if (cursorInsideBrowserContentArea(event)) {
  920. // Only drag while cursor is inside the browser client area
  921. final int x = Util.getTouchOrMouseClientX(event) - startX
  922. + origX;
  923. final int y = Util.getTouchOrMouseClientY(event) - startY
  924. + origY;
  925. setPopupPosition(x, y);
  926. }
  927. DOM.eventPreventDefault(event);
  928. }
  929. }
  930. private void beginMovingWindow(Event event) {
  931. if (draggable) {
  932. showDraggingCurtain(true);
  933. dragging = true;
  934. startX = Util.getTouchOrMouseClientX(event);
  935. startY = Util.getTouchOrMouseClientY(event);
  936. origX = DOM.getAbsoluteLeft(getElement());
  937. origY = DOM.getAbsoluteTop(getElement());
  938. DOM.setCapture(getElement());
  939. DOM.eventPreventDefault(event);
  940. }
  941. }
  942. private void stopMovingWindow() {
  943. dragging = false;
  944. showDraggingCurtain(false);
  945. DOM.releaseCapture(getElement());
  946. }
  947. @Override
  948. public boolean onEventPreview(Event event) {
  949. if (dragging) {
  950. onDragEvent(event);
  951. return false;
  952. } else if (resizing) {
  953. onResizeEvent(event);
  954. return false;
  955. } else if (vaadinModality) {
  956. // return false when modal and outside window
  957. final Element target = event.getEventTarget().cast();
  958. if (DOM.getCaptureElement() != null) {
  959. // Allow events when capture is set
  960. return true;
  961. }
  962. if (!DOM.isOrHasChild(getElement(), target)) {
  963. // not within the modal window, but let's see if it's in the
  964. // debug window
  965. Widget w = Util.findWidget(target, null);
  966. while (w != null) {
  967. if (w instanceof Console) {
  968. return true; // allow debug-window clicks
  969. } else if (w instanceof VPaintableWidget) {
  970. return false;
  971. }
  972. w = w.getParent();
  973. }
  974. return false;
  975. }
  976. }
  977. return true;
  978. }
  979. @Override
  980. public void addStyleDependentName(String styleSuffix) {
  981. // VWindow's getStyleElement() does not return the same element as
  982. // getElement(), so we need to override this.
  983. setStyleName(getElement(), getStylePrimaryName() + "-" + styleSuffix,
  984. true);
  985. }
  986. @Override
  987. protected void onAttach() {
  988. super.onAttach();
  989. setWidth(width);
  990. setHeight(height);
  991. }
  992. public RenderSpace getAllocatedSpace(Widget child) {
  993. if (child == layout) {
  994. return renderSpace;
  995. } else {
  996. // Exception ??
  997. return null;
  998. }
  999. }
  1000. public boolean hasChildComponent(Widget component) {
  1001. if (component == layout) {
  1002. return true;
  1003. } else {
  1004. return false;
  1005. }
  1006. }
  1007. public void replaceChildComponent(Widget oldComponent, Widget newComponent) {
  1008. contentPanel.setWidget(newComponent);
  1009. }
  1010. public boolean requestLayout(Set<Widget> children) {
  1011. if (dynamicWidth && !layoutRelativeWidth) {
  1012. setNaturalWidth();
  1013. }
  1014. if (centered) {
  1015. center();
  1016. }
  1017. updateShadowSizeAndPosition();
  1018. // layout size change may affect its available space (scrollbars)
  1019. client.handleComponentRelativeSize(layout.getWidgetForPaintable());
  1020. return true;
  1021. }
  1022. public void updateCaption(VPaintableWidget component, UIDL uidl) {
  1023. // NOP, window has own caption, layout captio not rendered
  1024. }
  1025. public ShortcutActionHandler getShortcutActionHandler() {
  1026. return shortcutHandler;
  1027. }
  1028. public void onScroll(ScrollEvent event) {
  1029. client.updateVariable(id, "scrollTop",
  1030. contentPanel.getScrollPosition(), false);
  1031. client.updateVariable(id, "scrollLeft",
  1032. contentPanel.getHorizontalScrollPosition(), false);
  1033. }
  1034. public void onKeyDown(KeyDownEvent event) {
  1035. if (shortcutHandler != null) {
  1036. shortcutHandler
  1037. .handleKeyboardEvent(Event.as(event.getNativeEvent()));
  1038. return;
  1039. }
  1040. }
  1041. public void onBlur(BlurEvent event) {
  1042. if (client.hasEventListeners(this, EventId.BLUR)) {
  1043. client.updateVariable(id, EventId.BLUR, "", true);
  1044. }
  1045. }
  1046. public void onFocus(FocusEvent event) {
  1047. if (client.hasEventListeners(this, EventId.FOCUS)) {
  1048. client.updateVariable(id, EventId.FOCUS, "", true);
  1049. }
  1050. }
  1051. public void onBeforeShortcutAction(Event e) {
  1052. // NOP, nothing to update just avoid workaround ( causes excess
  1053. // blur/focus )
  1054. }
  1055. public void focus() {
  1056. contentPanel.focus();
  1057. }
  1058. public Widget getWidgetForPaintable() {
  1059. return this;
  1060. }
  1061. }