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.

Util.java 40KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client;
  5. import java.util.ArrayList;
  6. import java.util.HashMap;
  7. import java.util.HashSet;
  8. import java.util.Iterator;
  9. import java.util.Map;
  10. import java.util.Set;
  11. import com.google.gwt.core.client.Scheduler;
  12. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  13. import com.google.gwt.dom.client.Document;
  14. import com.google.gwt.dom.client.NativeEvent;
  15. import com.google.gwt.dom.client.Node;
  16. import com.google.gwt.dom.client.NodeList;
  17. import com.google.gwt.dom.client.Style;
  18. import com.google.gwt.dom.client.Touch;
  19. import com.google.gwt.user.client.Command;
  20. import com.google.gwt.user.client.DOM;
  21. import com.google.gwt.user.client.Element;
  22. import com.google.gwt.user.client.Event;
  23. import com.google.gwt.user.client.EventListener;
  24. import com.google.gwt.user.client.Timer;
  25. import com.google.gwt.user.client.Window;
  26. import com.google.gwt.user.client.ui.HasWidgets;
  27. import com.google.gwt.user.client.ui.RootPanel;
  28. import com.google.gwt.user.client.ui.Widget;
  29. import com.vaadin.terminal.gwt.client.RenderInformation.FloatSize;
  30. public class Util {
  31. /**
  32. * Helper method for debugging purposes.
  33. *
  34. * Stops execution on firefox browsers on a breakpoint.
  35. *
  36. */
  37. public static native void browserDebugger()
  38. /*-{
  39. if($wnd.console)
  40. debugger;
  41. }-*/;
  42. /**
  43. *
  44. * Returns the topmost element of from given coordinates.
  45. *
  46. * TODO fix crossplat issues clientX vs pageX. See quircksmode. Not critical
  47. * for vaadin as we scroll div istead of page.
  48. *
  49. * @param x
  50. * @param y
  51. * @return the element at given coordinates
  52. */
  53. public static native Element getElementFromPoint(int clientX, int clientY)
  54. /*-{
  55. var el = $wnd.document.elementFromPoint(clientX, clientY);
  56. if(el != null && el.nodeType == 3) {
  57. el = el.parentNode;
  58. }
  59. return el;
  60. }-*/;
  61. private static final int LAZY_SIZE_CHANGE_TIMEOUT = 400;
  62. private static Set<Widget> latelyChangedWidgets = new HashSet<Widget>();
  63. private static Timer lazySizeChangeTimer = new Timer() {
  64. private boolean lazySizeChangeTimerScheduled = false;
  65. @Override
  66. public void run() {
  67. componentSizeUpdated(latelyChangedWidgets);
  68. latelyChangedWidgets.clear();
  69. lazySizeChangeTimerScheduled = false;
  70. }
  71. @Override
  72. public void schedule(int delayMillis) {
  73. if (lazySizeChangeTimerScheduled) {
  74. cancel();
  75. } else {
  76. lazySizeChangeTimerScheduled = true;
  77. }
  78. super.schedule(delayMillis);
  79. }
  80. };
  81. /**
  82. * This helper method can be called if components size have been changed
  83. * outside rendering phase. It notifies components parent about the size
  84. * change so it can react.
  85. *
  86. * When using this method, developer should consider if size changes could
  87. * be notified lazily. If lazy flag is true, method will save widget and
  88. * wait for a moment until it notifies parents in chunks. This may vastly
  89. * optimize layout in various situation. Example: if component have a lot of
  90. * images their onload events may fire "layout phase" many times in a short
  91. * period.
  92. *
  93. * @param widget
  94. * @param lazy
  95. * run componentSizeUpdated lazyly
  96. */
  97. public static void notifyParentOfSizeChange(Widget widget, boolean lazy) {
  98. if (lazy) {
  99. latelyChangedWidgets.add(widget);
  100. lazySizeChangeTimer.schedule(LAZY_SIZE_CHANGE_TIMEOUT);
  101. } else {
  102. Set<Widget> widgets = new HashSet<Widget>();
  103. widgets.add(widget);
  104. Util.componentSizeUpdated(widgets);
  105. }
  106. }
  107. /**
  108. * Called when the size of one or more widgets have changed during
  109. * rendering. Finds parent container and notifies them of the size change.
  110. *
  111. * @param paintables
  112. */
  113. public static void componentSizeUpdated(Set<Widget> widgets) {
  114. if (widgets.isEmpty()) {
  115. return;
  116. }
  117. Map<Container, Set<Widget>> childWidgets = new HashMap<Container, Set<Widget>>();
  118. for (Widget widget : widgets) {
  119. if (!widget.isAttached()) {
  120. continue;
  121. }
  122. // ApplicationConnection.getConsole().log(
  123. // "Widget " + Util.getSimpleName(widget) + " size updated");
  124. Widget parent = widget.getParent();
  125. while (parent != null && !(parent instanceof Container)) {
  126. parent = parent.getParent();
  127. }
  128. if (parent != null) {
  129. Set<Widget> set = childWidgets.get(parent);
  130. if (set == null) {
  131. set = new HashSet<Widget>();
  132. childWidgets.put((Container) parent, set);
  133. }
  134. set.add(widget);
  135. }
  136. }
  137. Set<Widget> parentChanges = new HashSet<Widget>();
  138. for (Container parent : childWidgets.keySet()) {
  139. if (!parent.requestLayout(childWidgets.get(parent))) {
  140. parentChanges.add(parent.getWidgetForPaintable());
  141. }
  142. }
  143. componentSizeUpdated(parentChanges);
  144. }
  145. public static float parseRelativeSize(String size) {
  146. if (size == null || !size.endsWith("%")) {
  147. return -1;
  148. }
  149. try {
  150. return Float.parseFloat(size.substring(0, size.length() - 1));
  151. } catch (Exception e) {
  152. VConsole.log("Unable to parse relative size");
  153. return -1;
  154. }
  155. }
  156. /**
  157. * Returns closest parent Widget in hierarchy that implements Container
  158. * interface
  159. *
  160. * @param component
  161. * @return closest parent Container
  162. */
  163. public static Container getLayout(Widget component) {
  164. Widget parent = component.getParent();
  165. while (parent != null && !(parent instanceof Container)) {
  166. parent = parent.getParent();
  167. }
  168. if (parent != null) {
  169. assert ((Container) parent).hasChildComponent(component);
  170. return (Container) parent;
  171. }
  172. return null;
  173. }
  174. private static final Element escapeHtmlHelper = DOM.createDiv();
  175. /**
  176. * Converts html entities to text.
  177. *
  178. * @param html
  179. * @return escaped string presentation of given html
  180. */
  181. public static String escapeHTML(String html) {
  182. DOM.setInnerText(escapeHtmlHelper, html);
  183. String escapedText = DOM.getInnerHTML(escapeHtmlHelper);
  184. if (BrowserInfo.get().isIE8()) {
  185. // #7478 IE8 "incorrectly" returns "<br>" for newlines set using
  186. // setInnerText. The same for " " which is converted to "&nbsp;"
  187. escapedText = escapedText.replaceAll("<(BR|br)>", "\n");
  188. escapedText = escapedText.replaceAll("&nbsp;", " ");
  189. }
  190. return escapedText;
  191. }
  192. /**
  193. * Escapes the string so it is safe to write inside an HTML attribute.
  194. *
  195. * @param attribute
  196. * The string to escape
  197. * @return An escaped version of <literal>attribute</literal>.
  198. */
  199. public static String escapeAttribute(String attribute) {
  200. attribute = attribute.replace("\"", "&quot;");
  201. attribute = attribute.replace("'", "&#39;");
  202. attribute = attribute.replace(">", "&gt;");
  203. attribute = attribute.replace("<", "&lt;");
  204. attribute = attribute.replace("&", "&amp;");
  205. return attribute;
  206. }
  207. /**
  208. * Clones given element as in JavaScript.
  209. *
  210. * Deprecate this if there appears similar method into GWT someday.
  211. *
  212. * @param element
  213. * @param deep
  214. * clone child tree also
  215. * @return
  216. */
  217. public static native Element cloneNode(Element element, boolean deep)
  218. /*-{
  219. return element.cloneNode(deep);
  220. }-*/;
  221. public static int measureHorizontalPaddingAndBorder(Element element,
  222. int paddingGuess) {
  223. String originalWidth = DOM.getStyleAttribute(element, "width");
  224. int originalOffsetWidth = element.getOffsetWidth();
  225. int widthGuess = (originalOffsetWidth - paddingGuess);
  226. if (widthGuess < 1) {
  227. widthGuess = 1;
  228. }
  229. DOM.setStyleAttribute(element, "width", widthGuess + "px");
  230. int padding = element.getOffsetWidth() - widthGuess;
  231. DOM.setStyleAttribute(element, "width", originalWidth);
  232. return padding;
  233. }
  234. public static int measureVerticalPaddingAndBorder(Element element,
  235. int paddingGuess) {
  236. String originalHeight = DOM.getStyleAttribute(element, "height");
  237. int originalOffsetHeight = element.getOffsetHeight();
  238. int widthGuess = (originalOffsetHeight - paddingGuess);
  239. if (widthGuess < 1) {
  240. widthGuess = 1;
  241. }
  242. DOM.setStyleAttribute(element, "height", widthGuess + "px");
  243. int padding = element.getOffsetHeight() - widthGuess;
  244. DOM.setStyleAttribute(element, "height", originalHeight);
  245. return padding;
  246. }
  247. public static int measureHorizontalBorder(Element element) {
  248. int borders;
  249. if (BrowserInfo.get().isIE()) {
  250. String width = element.getStyle().getProperty("width");
  251. String height = element.getStyle().getProperty("height");
  252. int offsetWidth = element.getOffsetWidth();
  253. int offsetHeight = element.getOffsetHeight();
  254. if (offsetHeight < 1) {
  255. offsetHeight = 1;
  256. }
  257. if (offsetWidth < 1) {
  258. offsetWidth = 10;
  259. }
  260. element.getStyle().setPropertyPx("height", offsetHeight);
  261. element.getStyle().setPropertyPx("width", offsetWidth);
  262. borders = element.getOffsetWidth() - element.getClientWidth();
  263. element.getStyle().setProperty("width", width);
  264. element.getStyle().setProperty("height", height);
  265. } else {
  266. borders = element.getOffsetWidth()
  267. - element.getPropertyInt("clientWidth");
  268. }
  269. assert borders >= 0;
  270. return borders;
  271. }
  272. public static int measureVerticalBorder(Element element) {
  273. int borders;
  274. if (BrowserInfo.get().isIE()) {
  275. String width = element.getStyle().getProperty("width");
  276. String height = element.getStyle().getProperty("height");
  277. int offsetWidth = element.getOffsetWidth();
  278. int offsetHeight = element.getOffsetHeight();
  279. if (offsetHeight < 1) {
  280. offsetHeight = 1;
  281. }
  282. if (offsetWidth < 1) {
  283. offsetWidth = 10;
  284. }
  285. element.getStyle().setPropertyPx("width", offsetWidth);
  286. element.getStyle().setPropertyPx("height", offsetHeight);
  287. borders = element.getOffsetHeight()
  288. - element.getPropertyInt("clientHeight");
  289. element.getStyle().setProperty("height", height);
  290. element.getStyle().setProperty("width", width);
  291. } else {
  292. borders = element.getOffsetHeight()
  293. - element.getPropertyInt("clientHeight");
  294. }
  295. assert borders >= 0;
  296. return borders;
  297. }
  298. public static int measureMarginLeft(Element element) {
  299. return element.getAbsoluteLeft()
  300. - element.getParentElement().getAbsoluteLeft();
  301. }
  302. public static int setHeightExcludingPaddingAndBorder(Widget widget,
  303. String height, int paddingBorderGuess) {
  304. if (height.equals("")) {
  305. setHeight(widget, "");
  306. return paddingBorderGuess;
  307. } else if (height.endsWith("px")) {
  308. int pixelHeight = Integer.parseInt(height.substring(0,
  309. height.length() - 2));
  310. return setHeightExcludingPaddingAndBorder(widget.getElement(),
  311. pixelHeight, paddingBorderGuess, false);
  312. } else {
  313. // Set the height in unknown units
  314. setHeight(widget, height);
  315. // Use the offsetWidth
  316. return setHeightExcludingPaddingAndBorder(widget.getElement(),
  317. widget.getOffsetHeight(), paddingBorderGuess, true);
  318. }
  319. }
  320. private static void setWidth(Widget widget, String width) {
  321. DOM.setStyleAttribute(widget.getElement(), "width", width);
  322. }
  323. private static void setHeight(Widget widget, String height) {
  324. DOM.setStyleAttribute(widget.getElement(), "height", height);
  325. }
  326. public static int setWidthExcludingPaddingAndBorder(Widget widget,
  327. String width, int paddingBorderGuess) {
  328. if (width.equals("")) {
  329. setWidth(widget, "");
  330. return paddingBorderGuess;
  331. } else if (width.endsWith("px")) {
  332. int pixelWidth = Integer.parseInt(width.substring(0,
  333. width.length() - 2));
  334. return setWidthExcludingPaddingAndBorder(widget.getElement(),
  335. pixelWidth, paddingBorderGuess, false);
  336. } else {
  337. setWidth(widget, width);
  338. return setWidthExcludingPaddingAndBorder(widget.getElement(),
  339. widget.getOffsetWidth(), paddingBorderGuess, true);
  340. }
  341. }
  342. public static int setWidthExcludingPaddingAndBorder(Element element,
  343. int requestedWidth, int horizontalPaddingBorderGuess,
  344. boolean requestedWidthIncludesPaddingBorder) {
  345. int widthGuess = requestedWidth - horizontalPaddingBorderGuess;
  346. if (widthGuess < 0) {
  347. widthGuess = 0;
  348. }
  349. DOM.setStyleAttribute(element, "width", widthGuess + "px");
  350. int captionOffsetWidth = DOM.getElementPropertyInt(element,
  351. "offsetWidth");
  352. int actualPadding = captionOffsetWidth - widthGuess;
  353. if (requestedWidthIncludesPaddingBorder) {
  354. actualPadding += actualPadding;
  355. }
  356. if (actualPadding != horizontalPaddingBorderGuess) {
  357. int w = requestedWidth - actualPadding;
  358. if (w < 0) {
  359. // Cannot set negative width even if we would want to
  360. w = 0;
  361. }
  362. DOM.setStyleAttribute(element, "width", w + "px");
  363. }
  364. return actualPadding;
  365. }
  366. public static int setHeightExcludingPaddingAndBorder(Element element,
  367. int requestedHeight, int verticalPaddingBorderGuess,
  368. boolean requestedHeightIncludesPaddingBorder) {
  369. int heightGuess = requestedHeight - verticalPaddingBorderGuess;
  370. if (heightGuess < 0) {
  371. heightGuess = 0;
  372. }
  373. DOM.setStyleAttribute(element, "height", heightGuess + "px");
  374. int captionOffsetHeight = DOM.getElementPropertyInt(element,
  375. "offsetHeight");
  376. int actualPadding = captionOffsetHeight - heightGuess;
  377. if (requestedHeightIncludesPaddingBorder) {
  378. actualPadding += actualPadding;
  379. }
  380. if (actualPadding != verticalPaddingBorderGuess) {
  381. int h = requestedHeight - actualPadding;
  382. if (h < 0) {
  383. // Cannot set negative height even if we would want to
  384. h = 0;
  385. }
  386. DOM.setStyleAttribute(element, "height", h + "px");
  387. }
  388. return actualPadding;
  389. }
  390. public static String getSimpleName(Object widget) {
  391. if (widget == null) {
  392. return "(null)";
  393. }
  394. String name = widget.getClass().getName();
  395. return name.substring(name.lastIndexOf('.') + 1);
  396. }
  397. public static void setFloat(Element element, String value) {
  398. if (BrowserInfo.get().isIE()) {
  399. DOM.setStyleAttribute(element, "styleFloat", value);
  400. } else {
  401. DOM.setStyleAttribute(element, "cssFloat", value);
  402. }
  403. }
  404. private static int detectedScrollbarSize = -1;
  405. public static int getNativeScrollbarSize() {
  406. if (detectedScrollbarSize < 0) {
  407. Element scroller = DOM.createDiv();
  408. scroller.getStyle().setProperty("width", "50px");
  409. scroller.getStyle().setProperty("height", "50px");
  410. scroller.getStyle().setProperty("overflow", "scroll");
  411. scroller.getStyle().setProperty("position", "absolute");
  412. scroller.getStyle().setProperty("marginLeft", "-5000px");
  413. RootPanel.getBodyElement().appendChild(scroller);
  414. detectedScrollbarSize = scroller.getOffsetWidth()
  415. - scroller.getPropertyInt("clientWidth");
  416. RootPanel.getBodyElement().removeChild(scroller);
  417. }
  418. return detectedScrollbarSize;
  419. }
  420. /**
  421. * Run workaround for webkits overflow auto issue.
  422. *
  423. * See: our bug #2138 and https://bugs.webkit.org/show_bug.cgi?id=21462
  424. *
  425. * @param elem
  426. * with overflow auto
  427. */
  428. public static void runWebkitOverflowAutoFix(final Element elem) {
  429. // Add max version if fix lands sometime to Webkit
  430. // Starting from Opera 11.00, also a problem in Opera
  431. if ((BrowserInfo.get().getWebkitVersion() > 0 || BrowserInfo.get()
  432. .getOperaVersion() >= 11) && getNativeScrollbarSize() > 0) {
  433. final String originalOverflow = elem.getStyle().getProperty(
  434. "overflow");
  435. if ("hidden".equals(originalOverflow)) {
  436. return;
  437. }
  438. // check the scrolltop value before hiding the element
  439. final int scrolltop = elem.getScrollTop();
  440. final int scrollleft = elem.getScrollLeft();
  441. elem.getStyle().setProperty("overflow", "hidden");
  442. Scheduler.get().scheduleDeferred(new Command() {
  443. public void execute() {
  444. // Dough, Safari scroll auto means actually just a moped
  445. elem.getStyle().setProperty("overflow", originalOverflow);
  446. if (scrolltop > 0 || elem.getScrollTop() > 0) {
  447. int scrollvalue = scrolltop;
  448. if (scrollvalue == 0) {
  449. // mysterious are the ways of webkits scrollbar
  450. // handling. In some cases webkit reports bad (0)
  451. // scrolltop before hiding the element temporary,
  452. // sometimes after.
  453. scrollvalue = elem.getScrollTop();
  454. }
  455. // fix another bug where scrollbar remains in wrong
  456. // position
  457. elem.setScrollTop(scrollvalue - 1);
  458. elem.setScrollTop(scrollvalue);
  459. }
  460. // fix for #6940 : Table horizontal scroll sometimes not
  461. // updated when collapsing/expanding columns
  462. // Also appeared in Safari 5.1 with webkit 534 (#7667)
  463. if ((BrowserInfo.get().isChrome() || (BrowserInfo.get()
  464. .isSafari() && BrowserInfo.get().getWebkitVersion() >= 534))
  465. && (scrollleft > 0 || elem.getScrollLeft() > 0)) {
  466. int scrollvalue = scrollleft;
  467. if (scrollvalue == 0) {
  468. // mysterious are the ways of webkits scrollbar
  469. // handling. In some cases webkit may report a bad
  470. // (0) scrollleft before hiding the element
  471. // temporary, sometimes after.
  472. scrollvalue = elem.getScrollLeft();
  473. }
  474. // fix another bug where scrollbar remains in wrong
  475. // position
  476. elem.setScrollLeft(scrollvalue - 1);
  477. elem.setScrollLeft(scrollvalue);
  478. }
  479. }
  480. });
  481. }
  482. }
  483. /**
  484. * Parses the UIDL parameter and fetches the relative size of the component.
  485. * If a dimension is not specified as relative it will return -1. If the
  486. * UIDL does not contain width or height specifications this will return
  487. * null.
  488. *
  489. * @param uidl
  490. * @return
  491. */
  492. public static FloatSize parseRelativeSize(UIDL uidl) {
  493. boolean hasAttribute = false;
  494. String w = "";
  495. String h = "";
  496. if (uidl.hasAttribute("width")) {
  497. hasAttribute = true;
  498. w = uidl.getStringAttribute("width");
  499. }
  500. if (uidl.hasAttribute("height")) {
  501. hasAttribute = true;
  502. h = uidl.getStringAttribute("height");
  503. }
  504. if (!hasAttribute) {
  505. return null;
  506. }
  507. float relativeWidth = Util.parseRelativeSize(w);
  508. float relativeHeight = Util.parseRelativeSize(h);
  509. FloatSize relativeSize = new FloatSize(relativeWidth, relativeHeight);
  510. return relativeSize;
  511. }
  512. public static boolean isCached(UIDL uidl) {
  513. return uidl.getBooleanAttribute("cached");
  514. }
  515. public static void alert(String string) {
  516. if (true) {
  517. Window.alert(string);
  518. }
  519. }
  520. public static boolean equals(Object a, Object b) {
  521. if (a == null) {
  522. return b == null;
  523. }
  524. return a.equals(b);
  525. }
  526. public static void updateRelativeChildrenAndSendSizeUpdateEvent(
  527. ApplicationConnection client, HasWidgets container, Widget widget) {
  528. /*
  529. * Relative sized children must be updated first so the component has
  530. * the correct outer dimensions when signaling a size change to the
  531. * parent.
  532. */
  533. Iterator<Widget> childIterator = container.iterator();
  534. while (childIterator.hasNext()) {
  535. Widget w = childIterator.next();
  536. client.handleComponentRelativeSize(w);
  537. }
  538. HashSet<Widget> widgets = new HashSet<Widget>();
  539. widgets.add(widget);
  540. Util.componentSizeUpdated(widgets);
  541. }
  542. public static native int getRequiredWidth(
  543. com.google.gwt.dom.client.Element element)
  544. /*-{
  545. if (element.getBoundingClientRect) {
  546. var rect = element.getBoundingClientRect();
  547. return Math.ceil(rect.right - rect.left);
  548. } else {
  549. return element.offsetWidth;
  550. }
  551. }-*/;
  552. public static native int getRequiredHeight(
  553. com.google.gwt.dom.client.Element element)
  554. /*-{
  555. var height;
  556. if (element.getBoundingClientRect != null) {
  557. var rect = element.getBoundingClientRect();
  558. height = Math.ceil(rect.bottom - rect.top);
  559. } else {
  560. height = element.offsetHeight;
  561. }
  562. return height;
  563. }-*/;
  564. public static int getRequiredWidth(Widget widget) {
  565. return getRequiredWidth(widget.getElement());
  566. }
  567. public static int getRequiredHeight(Widget widget) {
  568. return getRequiredHeight(widget.getElement());
  569. }
  570. /**
  571. * Detects what is currently the overflow style attribute in given element.
  572. *
  573. * @param pe
  574. * the element to detect
  575. * @return true if auto or scroll
  576. */
  577. public static boolean mayHaveScrollBars(com.google.gwt.dom.client.Element pe) {
  578. String overflow = getComputedStyle(pe, "overflow");
  579. if (overflow != null) {
  580. if (overflow.equals("auto") || overflow.equals("scroll")) {
  581. return true;
  582. } else {
  583. return false;
  584. }
  585. } else {
  586. return false;
  587. }
  588. }
  589. /**
  590. * A simple helper method to detect "computed style" (aka style sheets +
  591. * element styles). Values returned differ a lot depending on browsers.
  592. * Always be very careful when using this.
  593. *
  594. * @param el
  595. * the element from which the style property is detected
  596. * @param p
  597. * the property to detect
  598. * @return String value of style property
  599. */
  600. private static native String getComputedStyle(
  601. com.google.gwt.dom.client.Element el, String p)
  602. /*-{
  603. try {
  604. if (el.currentStyle) {
  605. // IE
  606. return el.currentStyle[p];
  607. } else if (window.getComputedStyle) {
  608. // Sa, FF, Opera
  609. var view = el.ownerDocument.defaultView;
  610. return view.getComputedStyle(el,null).getPropertyValue(p);
  611. } else {
  612. // fall back for non IE, Sa, FF, Opera
  613. return "";
  614. }
  615. } catch (e) {
  616. return "";
  617. }
  618. }-*/;
  619. /**
  620. * Locates the child component of <literal>parent</literal> which contains
  621. * the element <literal>element</literal>. The child component is also
  622. * returned if "element" is part of its caption. If
  623. * <literal>element</literal> is not part of any child component, null is
  624. * returned.
  625. *
  626. * This method returns the immediate child of the parent that contains the
  627. * element. See
  628. * {@link #getPaintableForElement(ApplicationConnection, Container, Element)}
  629. * for the deepest nested paintable of parent that contains the element.
  630. *
  631. * @param client
  632. * A reference to ApplicationConnection
  633. * @param parent
  634. * The widget that contains <literal>element</literal>.
  635. * @param element
  636. * An element that is a sub element of the parent
  637. * @return The VPaintableWidget which the element is a part of. Null if the
  638. * element does not belong to a child.
  639. */
  640. public static VPaintableWidget getChildPaintableForElement(
  641. ApplicationConnection client, Container parent, Element element) {
  642. Element rootElement = parent.getWidgetForPaintable().getElement();
  643. while (element != null && element != rootElement) {
  644. VPaintableWidget paintable = VPaintableMap.get(client)
  645. .getPaintable(element);
  646. if (paintable == null) {
  647. String ownerPid = VCaption.getCaptionOwnerPid(element);
  648. if (ownerPid != null) {
  649. paintable = (VPaintableWidget) VPaintableMap.get(client)
  650. .getPaintable(ownerPid);
  651. }
  652. }
  653. if (paintable != null
  654. && parent.hasChildComponent(paintable
  655. .getWidgetForPaintable())) {
  656. return paintable;
  657. }
  658. element = (Element) element.getParentElement();
  659. }
  660. return null;
  661. }
  662. /**
  663. * Locates the nested child component of <literal>parent</literal> which
  664. * contains the element <literal>element</literal>. The child component is
  665. * also returned if "element" is part of its caption. If
  666. * <literal>element</literal> is not part of any child component, null is
  667. * returned.
  668. *
  669. * This method returns the deepest nested VPaintableWidget. See
  670. * {@link #getChildPaintableForElement(ApplicationConnection, Container, Element)}
  671. * for the immediate child component of parent that contains the element.
  672. *
  673. * @param client
  674. * A reference to ApplicationConnection
  675. * @param parent
  676. * The widget that contains <literal>element</literal>.
  677. * @param element
  678. * An element that is a sub element of the parent
  679. * @return The VPaintableWidget which the element is a part of. Null if the
  680. * element does not belong to a child.
  681. */
  682. public static VPaintableWidget getPaintableForElement(
  683. ApplicationConnection client, Widget parent, Element element) {
  684. Element rootElement = parent.getElement();
  685. while (element != null && element != rootElement) {
  686. VPaintableWidget paintable = VPaintableMap.get(client)
  687. .getPaintable(element);
  688. if (paintable == null) {
  689. String ownerPid = VCaption.getCaptionOwnerPid(element);
  690. if (ownerPid != null) {
  691. paintable = (VPaintableWidget) VPaintableMap.get(client)
  692. .getPaintable(ownerPid);
  693. }
  694. }
  695. if (paintable != null) {
  696. // check that inside the rootElement
  697. while (element != null && element != rootElement) {
  698. element = (Element) element.getParentElement();
  699. }
  700. if (element != rootElement) {
  701. return null;
  702. } else {
  703. return paintable;
  704. }
  705. }
  706. element = (Element) element.getParentElement();
  707. }
  708. return null;
  709. }
  710. /**
  711. * Will (attempt) to focus the given DOM Element.
  712. *
  713. * @param el
  714. * the element to focus
  715. */
  716. public static native void focus(Element el)
  717. /*-{
  718. try {
  719. el.focus();
  720. } catch (e) {
  721. }
  722. }-*/;
  723. /**
  724. * Helper method to find first instance of given Widget type found by
  725. * traversing DOM upwards from given element.
  726. *
  727. * @param element
  728. * the element where to start seeking of Widget
  729. * @param class1
  730. * the Widget type to seek for
  731. */
  732. public static <T> T findWidget(Element element,
  733. Class<? extends Widget> class1) {
  734. if (element != null) {
  735. /* First seek for the first EventListener (~Widget) from dom */
  736. EventListener eventListener = null;
  737. while (eventListener == null && element != null) {
  738. eventListener = Event.getEventListener(element);
  739. if (eventListener == null) {
  740. element = (Element) element.getParentElement();
  741. }
  742. }
  743. if (eventListener != null) {
  744. /*
  745. * Then find the first widget of type class1 from widget
  746. * hierarchy
  747. */
  748. Widget w = (Widget) eventListener;
  749. while (w != null) {
  750. if (class1 == null || w.getClass() == class1) {
  751. return (T) w;
  752. }
  753. w = w.getParent();
  754. }
  755. }
  756. }
  757. return null;
  758. }
  759. /**
  760. * Force webkit to redraw an element
  761. *
  762. * @param element
  763. * The element that should be redrawn
  764. */
  765. public static void forceWebkitRedraw(Element element) {
  766. Style style = element.getStyle();
  767. String s = style.getProperty("webkitTransform");
  768. if (s == null || s.length() == 0) {
  769. style.setProperty("webkitTransform", "scale(1)");
  770. } else {
  771. style.setProperty("webkitTransform", "");
  772. }
  773. }
  774. /**
  775. * Detaches and re-attaches the element from its parent. The element is
  776. * reattached at the same position in the DOM as it was before.
  777. *
  778. * Does nothing if the element is not attached to the DOM.
  779. *
  780. * @param element
  781. * The element to detach and re-attach
  782. */
  783. public static void detachAttach(Element element) {
  784. if (element == null) {
  785. return;
  786. }
  787. Node nextSibling = element.getNextSibling();
  788. Node parent = element.getParentNode();
  789. if (parent == null) {
  790. return;
  791. }
  792. parent.removeChild(element);
  793. if (nextSibling == null) {
  794. parent.appendChild(element);
  795. } else {
  796. parent.insertBefore(element, nextSibling);
  797. }
  798. }
  799. public static void sinkOnloadForImages(Element element) {
  800. NodeList<com.google.gwt.dom.client.Element> imgElements = element
  801. .getElementsByTagName("img");
  802. for (int i = 0; i < imgElements.getLength(); i++) {
  803. DOM.sinkEvents((Element) imgElements.getItem(i), Event.ONLOAD);
  804. }
  805. }
  806. /**
  807. * Returns the index of the childElement within its parent.
  808. *
  809. * @param subElement
  810. * @return
  811. */
  812. public static int getChildElementIndex(Element childElement) {
  813. int idx = 0;
  814. Node n = childElement;
  815. while ((n = n.getPreviousSibling()) != null) {
  816. idx++;
  817. }
  818. return idx;
  819. }
  820. private static void printPaintablesVariables(ArrayList<String[]> vars,
  821. String id, ApplicationConnection c) {
  822. VPaintableWidget paintable = (VPaintableWidget) VPaintableMap.get(c)
  823. .getPaintable(id);
  824. if (paintable != null) {
  825. VConsole.log("\t" + id + " (" + paintable.getClass() + ") :");
  826. for (String[] var : vars) {
  827. VConsole.log("\t\t" + var[1] + " (" + var[2] + ")" + " : "
  828. + var[0]);
  829. }
  830. }
  831. }
  832. static void logVariableBurst(ApplicationConnection c,
  833. ArrayList<String> loggedBurst) {
  834. try {
  835. VConsole.log("Variable burst to be sent to server:");
  836. String curId = null;
  837. ArrayList<String[]> vars = new ArrayList<String[]>();
  838. for (int i = 0; i < loggedBurst.size(); i++) {
  839. String value = loggedBurst.get(i++);
  840. String[] split = loggedBurst
  841. .get(i)
  842. .split(String
  843. .valueOf(ApplicationConnection.VAR_FIELD_SEPARATOR));
  844. String id = split[0];
  845. if (curId == null) {
  846. curId = id;
  847. } else if (!curId.equals(id)) {
  848. printPaintablesVariables(vars, curId, c);
  849. vars.clear();
  850. curId = id;
  851. }
  852. split[0] = value;
  853. vars.add(split);
  854. }
  855. if (!vars.isEmpty()) {
  856. printPaintablesVariables(vars, curId, c);
  857. }
  858. } catch (Exception e) {
  859. VConsole.error(e);
  860. }
  861. }
  862. /**
  863. * Temporarily sets the {@code styleProperty} to {@code tempValue} and then
  864. * resets it to its current value. Used mainly to work around rendering
  865. * issues in IE (and possibly in other browsers)
  866. *
  867. * @param element
  868. * The target element
  869. * @param styleProperty
  870. * The name of the property to set
  871. * @param tempValue
  872. * The temporary value
  873. */
  874. public static void setStyleTemporarily(Element element,
  875. final String styleProperty, String tempValue) {
  876. final Style style = element.getStyle();
  877. final String currentValue = style.getProperty(styleProperty);
  878. style.setProperty(styleProperty, tempValue);
  879. element.getOffsetWidth();
  880. style.setProperty(styleProperty, currentValue);
  881. }
  882. /**
  883. * A helper method to return the client position from an event. Returns
  884. * position from either first changed touch (if touch event) or from the
  885. * event itself.
  886. *
  887. * @param event
  888. * @return
  889. */
  890. public static int getTouchOrMouseClientX(Event event) {
  891. if (isTouchEvent(event)) {
  892. return event.getChangedTouches().get(0).getClientX();
  893. } else {
  894. return event.getClientX();
  895. }
  896. }
  897. /**
  898. * A helper method to return the client position from an event. Returns
  899. * position from either first changed touch (if touch event) or from the
  900. * event itself.
  901. *
  902. * @param event
  903. * @return
  904. */
  905. public static int getTouchOrMouseClientY(Event event) {
  906. if (isTouchEvent(event)) {
  907. return event.getChangedTouches().get(0).getClientY();
  908. } else {
  909. return event.getClientY();
  910. }
  911. }
  912. /**
  913. *
  914. * @see #getTouchOrMouseClientY(Event)
  915. * @param currentGwtEvent
  916. * @return
  917. */
  918. public static int getTouchOrMouseClientY(NativeEvent currentGwtEvent) {
  919. return getTouchOrMouseClientY(Event.as(currentGwtEvent));
  920. }
  921. /**
  922. * @see #getTouchOrMouseClientX(Event)
  923. *
  924. * @param event
  925. * @return
  926. */
  927. public static int getTouchOrMouseClientX(NativeEvent event) {
  928. return getTouchOrMouseClientX(Event.as(event));
  929. }
  930. public static boolean isTouchEvent(Event event) {
  931. return event.getType().contains("touch");
  932. }
  933. public static boolean isTouchEvent(NativeEvent event) {
  934. return isTouchEvent(Event.as(event));
  935. }
  936. public static void simulateClickFromTouchEvent(Event touchevent,
  937. Widget widget) {
  938. Touch touch = touchevent.getChangedTouches().get(0);
  939. final NativeEvent createMouseUpEvent = Document.get()
  940. .createMouseUpEvent(0, touch.getScreenX(), touch.getScreenY(),
  941. touch.getClientX(), touch.getClientY(), false, false,
  942. false, false, NativeEvent.BUTTON_LEFT);
  943. final NativeEvent createMouseDownEvent = Document.get()
  944. .createMouseDownEvent(0, touch.getScreenX(),
  945. touch.getScreenY(), touch.getClientX(),
  946. touch.getClientY(), false, false, false, false,
  947. NativeEvent.BUTTON_LEFT);
  948. final NativeEvent createMouseClickEvent = Document.get()
  949. .createClickEvent(0, touch.getScreenX(), touch.getScreenY(),
  950. touch.getClientX(), touch.getClientY(), false, false,
  951. false, false);
  952. /*
  953. * Get target with element from point as we want the actual element, not
  954. * the one that sunk the event.
  955. */
  956. final Element target = getElementFromPoint(touch.getClientX(),
  957. touch.getClientY());
  958. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  959. public void execute() {
  960. try {
  961. target.dispatchEvent(createMouseDownEvent);
  962. target.dispatchEvent(createMouseUpEvent);
  963. target.dispatchEvent(createMouseClickEvent);
  964. } catch (Exception e) {
  965. }
  966. }
  967. });
  968. }
  969. /**
  970. * Gets the currently focused element for Internet Explorer.
  971. *
  972. * @return The currently focused element
  973. */
  974. public native static Element getIEFocusedElement()
  975. /*-{
  976. if ($wnd.document.activeElement) {
  977. return $wnd.document.activeElement;
  978. }
  979. return null;
  980. }-*/
  981. ;
  982. /**
  983. * Kind of stronger version of isAttached(). In addition to std isAttached,
  984. * this method checks that this widget nor any of its parents is hidden. Can
  985. * be e.g used to check whether component should react to some events or
  986. * not.
  987. *
  988. * @param widget
  989. * @return true if attached and displayed
  990. */
  991. public static boolean isAttachedAndDisplayed(Widget widget) {
  992. if (widget.isAttached()) {
  993. /*
  994. * Failfast using offset size, then by iterating the widget tree
  995. */
  996. boolean notZeroSized = widget.getOffsetHeight() > 0
  997. || widget.getOffsetWidth() > 0;
  998. return notZeroSized || checkVisibilityRecursively(widget);
  999. } else {
  1000. return false;
  1001. }
  1002. }
  1003. private static boolean checkVisibilityRecursively(Widget widget) {
  1004. if (widget.isVisible()) {
  1005. Widget parent = widget.getParent();
  1006. if (parent == null) {
  1007. return true; // root panel
  1008. } else {
  1009. return checkVisibilityRecursively(parent);
  1010. }
  1011. } else {
  1012. return false;
  1013. }
  1014. }
  1015. /**
  1016. * Scrolls an element into view vertically only. Modified version of
  1017. * Element.scrollIntoView.
  1018. *
  1019. * @param elem
  1020. * The element to scroll into view
  1021. */
  1022. public static native void scrollIntoViewVertically(Element elem)
  1023. /*-{
  1024. var top = elem.offsetTop;
  1025. var height = elem.offsetHeight;
  1026. if (elem.parentNode != elem.offsetParent) {
  1027. top -= elem.parentNode.offsetTop;
  1028. }
  1029. var cur = elem.parentNode;
  1030. while (cur && (cur.nodeType == 1)) {
  1031. if (top < cur.scrollTop) {
  1032. cur.scrollTop = top;
  1033. }
  1034. if (top + height > cur.scrollTop + cur.clientHeight) {
  1035. cur.scrollTop = (top + height) - cur.clientHeight;
  1036. }
  1037. var offsetTop = cur.offsetTop;
  1038. if (cur.parentNode != cur.offsetParent) {
  1039. offsetTop -= cur.parentNode.offsetTop;
  1040. }
  1041. top += offsetTop - cur.scrollTop;
  1042. cur = cur.parentNode;
  1043. }
  1044. }-*/;
  1045. }