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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223
  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((Widget) parent);
  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. @Deprecated
  513. public static boolean isCached(UIDL uidl) {
  514. return uidl.getBooleanAttribute("cached");
  515. }
  516. public static void alert(String string) {
  517. if (true) {
  518. Window.alert(string);
  519. }
  520. }
  521. public static boolean equals(Object a, Object b) {
  522. if (a == null) {
  523. return b == null;
  524. }
  525. return a.equals(b);
  526. }
  527. public static void updateRelativeChildrenAndSendSizeUpdateEvent(
  528. ApplicationConnection client, HasWidgets container, Widget widget) {
  529. /*
  530. * Relative sized children must be updated first so the component has
  531. * the correct outer dimensions when signaling a size change to the
  532. * parent.
  533. */
  534. Iterator<Widget> childIterator = container.iterator();
  535. while (childIterator.hasNext()) {
  536. Widget w = childIterator.next();
  537. client.handleComponentRelativeSize(w);
  538. }
  539. HashSet<Widget> widgets = new HashSet<Widget>();
  540. widgets.add(widget);
  541. Util.componentSizeUpdated(widgets);
  542. }
  543. public static native int getRequiredWidth(
  544. com.google.gwt.dom.client.Element element)
  545. /*-{
  546. if (element.getBoundingClientRect) {
  547. var rect = element.getBoundingClientRect();
  548. return Math.ceil(rect.right - rect.left);
  549. } else {
  550. return element.offsetWidth;
  551. }
  552. }-*/;
  553. public static native int getRequiredHeight(
  554. com.google.gwt.dom.client.Element element)
  555. /*-{
  556. var height;
  557. if (element.getBoundingClientRect != null) {
  558. var rect = element.getBoundingClientRect();
  559. height = Math.ceil(rect.bottom - rect.top);
  560. } else {
  561. height = element.offsetHeight;
  562. }
  563. return height;
  564. }-*/;
  565. public static int getRequiredWidth(Widget widget) {
  566. return getRequiredWidth(widget.getElement());
  567. }
  568. public static int getRequiredHeight(Widget widget) {
  569. return getRequiredHeight(widget.getElement());
  570. }
  571. /**
  572. * Detects what is currently the overflow style attribute in given element.
  573. *
  574. * @param pe
  575. * the element to detect
  576. * @return true if auto or scroll
  577. */
  578. public static boolean mayHaveScrollBars(com.google.gwt.dom.client.Element pe) {
  579. String overflow = getComputedStyle(pe, "overflow");
  580. if (overflow != null) {
  581. if (overflow.equals("auto") || overflow.equals("scroll")) {
  582. return true;
  583. } else {
  584. return false;
  585. }
  586. } else {
  587. return false;
  588. }
  589. }
  590. /**
  591. * A simple helper method to detect "computed style" (aka style sheets +
  592. * element styles). Values returned differ a lot depending on browsers.
  593. * Always be very careful when using this.
  594. *
  595. * @param el
  596. * the element from which the style property is detected
  597. * @param p
  598. * the property to detect
  599. * @return String value of style property
  600. */
  601. private static native String getComputedStyle(
  602. com.google.gwt.dom.client.Element el, String p)
  603. /*-{
  604. try {
  605. if (el.currentStyle) {
  606. // IE
  607. return el.currentStyle[p];
  608. } else if (window.getComputedStyle) {
  609. // Sa, FF, Opera
  610. var view = el.ownerDocument.defaultView;
  611. return view.getComputedStyle(el,null).getPropertyValue(p);
  612. } else {
  613. // fall back for non IE, Sa, FF, Opera
  614. return "";
  615. }
  616. } catch (e) {
  617. return "";
  618. }
  619. }-*/;
  620. /**
  621. * Locates the child component of <literal>parent</literal> which contains
  622. * the element <literal>element</literal>. The child component is also
  623. * returned if "element" is part of its caption. If
  624. * <literal>element</literal> is not part of any child component, null is
  625. * returned.
  626. *
  627. * This method returns the immediate child of the parent that contains the
  628. * element. See
  629. * {@link #getPaintableForElement(ApplicationConnection, Container, Element)}
  630. * for the deepest nested paintable of parent that contains the element.
  631. *
  632. * @param client
  633. * A reference to ApplicationConnection
  634. * @param parent
  635. * The widget that contains <literal>element</literal>.
  636. * @param element
  637. * An element that is a sub element of the parent
  638. * @return The VPaintableWidget which the element is a part of. Null if the
  639. * element does not belong to a child.
  640. */
  641. public static VPaintableWidget getChildPaintableForElement(
  642. ApplicationConnection client, Container parent, Element element) {
  643. Element rootElement = ((Widget) parent).getElement();
  644. while (element != null && element != rootElement) {
  645. VPaintableWidget paintable = VPaintableMap.get(client)
  646. .getPaintable(element);
  647. if (paintable == null) {
  648. String ownerPid = VCaption.getCaptionOwnerPid(element);
  649. if (ownerPid != null) {
  650. paintable = (VPaintableWidget) VPaintableMap.get(client)
  651. .getPaintable(ownerPid);
  652. }
  653. }
  654. if (paintable != null
  655. && parent.hasChildComponent(paintable
  656. .getWidgetForPaintable())) {
  657. return paintable;
  658. }
  659. element = (Element) element.getParentElement();
  660. }
  661. return null;
  662. }
  663. /**
  664. * Locates the nested child component of <literal>parent</literal> which
  665. * contains the element <literal>element</literal>. The child component is
  666. * also returned if "element" is part of its caption. If
  667. * <literal>element</literal> is not part of any child component, null is
  668. * returned.
  669. *
  670. * This method returns the deepest nested VPaintableWidget. See
  671. * {@link #getChildPaintableForElement(ApplicationConnection, Container, Element)}
  672. * for the immediate child component of parent that contains the element.
  673. *
  674. * @param client
  675. * A reference to ApplicationConnection
  676. * @param parent
  677. * The widget that contains <literal>element</literal>.
  678. * @param element
  679. * An element that is a sub element of the parent
  680. * @return The VPaintableWidget which the element is a part of. Null if the
  681. * element does not belong to a child.
  682. */
  683. public static VPaintableWidget getPaintableForElement(
  684. ApplicationConnection client, Widget parent, Element element) {
  685. Element rootElement = parent.getElement();
  686. while (element != null && element != rootElement) {
  687. VPaintableWidget paintable = VPaintableMap.get(client)
  688. .getPaintable(element);
  689. if (paintable == null) {
  690. String ownerPid = VCaption.getCaptionOwnerPid(element);
  691. if (ownerPid != null) {
  692. paintable = (VPaintableWidget) VPaintableMap.get(client)
  693. .getPaintable(ownerPid);
  694. }
  695. }
  696. if (paintable != null) {
  697. // check that inside the rootElement
  698. while (element != null && element != rootElement) {
  699. element = (Element) element.getParentElement();
  700. }
  701. if (element != rootElement) {
  702. return null;
  703. } else {
  704. return paintable;
  705. }
  706. }
  707. element = (Element) element.getParentElement();
  708. }
  709. return null;
  710. }
  711. /**
  712. * Will (attempt) to focus the given DOM Element.
  713. *
  714. * @param el
  715. * the element to focus
  716. */
  717. public static native void focus(Element el)
  718. /*-{
  719. try {
  720. el.focus();
  721. } catch (e) {
  722. }
  723. }-*/;
  724. /**
  725. * Helper method to find the nearest parent paintable instance by traversing
  726. * the DOM upwards from given element.
  727. *
  728. * @param element
  729. * the element to start from
  730. */
  731. public static VPaintableWidget findPaintable(ApplicationConnection client,
  732. Element element) {
  733. Widget widget = Util.findWidget(element, null);
  734. VPaintableMap vPaintableMap = VPaintableMap.get(client);
  735. while (widget != null && !vPaintableMap.isPaintable(widget)) {
  736. widget = widget.getParent();
  737. }
  738. return vPaintableMap.getPaintable(widget);
  739. }
  740. /**
  741. * Helper method to find first instance of given Widget type found by
  742. * traversing DOM upwards from given element.
  743. *
  744. * @param element
  745. * the element where to start seeking of Widget
  746. * @param class1
  747. * the Widget type to seek for
  748. */
  749. public static <T> T findWidget(Element element,
  750. Class<? extends Widget> class1) {
  751. if (element != null) {
  752. /* First seek for the first EventListener (~Widget) from dom */
  753. EventListener eventListener = null;
  754. while (eventListener == null && element != null) {
  755. eventListener = Event.getEventListener(element);
  756. if (eventListener == null) {
  757. element = (Element) element.getParentElement();
  758. }
  759. }
  760. if (eventListener != null) {
  761. /*
  762. * Then find the first widget of type class1 from widget
  763. * hierarchy
  764. */
  765. Widget w = (Widget) eventListener;
  766. while (w != null) {
  767. if (class1 == null || w.getClass() == class1) {
  768. return (T) w;
  769. }
  770. w = w.getParent();
  771. }
  772. }
  773. }
  774. return null;
  775. }
  776. /**
  777. * Force webkit to redraw an element
  778. *
  779. * @param element
  780. * The element that should be redrawn
  781. */
  782. public static void forceWebkitRedraw(Element element) {
  783. Style style = element.getStyle();
  784. String s = style.getProperty("webkitTransform");
  785. if (s == null || s.length() == 0) {
  786. style.setProperty("webkitTransform", "scale(1)");
  787. } else {
  788. style.setProperty("webkitTransform", "");
  789. }
  790. }
  791. /**
  792. * Detaches and re-attaches the element from its parent. The element is
  793. * reattached at the same position in the DOM as it was before.
  794. *
  795. * Does nothing if the element is not attached to the DOM.
  796. *
  797. * @param element
  798. * The element to detach and re-attach
  799. */
  800. public static void detachAttach(Element element) {
  801. if (element == null) {
  802. return;
  803. }
  804. Node nextSibling = element.getNextSibling();
  805. Node parent = element.getParentNode();
  806. if (parent == null) {
  807. return;
  808. }
  809. parent.removeChild(element);
  810. if (nextSibling == null) {
  811. parent.appendChild(element);
  812. } else {
  813. parent.insertBefore(element, nextSibling);
  814. }
  815. }
  816. public static void sinkOnloadForImages(Element element) {
  817. NodeList<com.google.gwt.dom.client.Element> imgElements = element
  818. .getElementsByTagName("img");
  819. for (int i = 0; i < imgElements.getLength(); i++) {
  820. DOM.sinkEvents((Element) imgElements.getItem(i), Event.ONLOAD);
  821. }
  822. }
  823. /**
  824. * Returns the index of the childElement within its parent.
  825. *
  826. * @param subElement
  827. * @return
  828. */
  829. public static int getChildElementIndex(Element childElement) {
  830. int idx = 0;
  831. Node n = childElement;
  832. while ((n = n.getPreviousSibling()) != null) {
  833. idx++;
  834. }
  835. return idx;
  836. }
  837. private static void printPaintablesVariables(ArrayList<String[]> vars,
  838. String id, ApplicationConnection c) {
  839. VPaintableWidget paintable = (VPaintableWidget) VPaintableMap.get(c)
  840. .getPaintable(id);
  841. if (paintable != null) {
  842. VConsole.log("\t" + id + " (" + paintable.getClass() + ") :");
  843. for (String[] var : vars) {
  844. VConsole.log("\t\t" + var[1] + " (" + var[2] + ")" + " : "
  845. + var[0]);
  846. }
  847. } else {
  848. VConsole.log("\t" + id + ": Warning: no corresponding paintable!");
  849. }
  850. }
  851. static void logVariableBurst(ApplicationConnection c,
  852. ArrayList<String> loggedBurst) {
  853. try {
  854. VConsole.log("Variable burst to be sent to server:");
  855. String curId = null;
  856. ArrayList<String[]> vars = new ArrayList<String[]>();
  857. for (int i = 0; i < loggedBurst.size(); i++) {
  858. String value = loggedBurst.get(i++);
  859. String[] split = loggedBurst
  860. .get(i)
  861. .split(String
  862. .valueOf(ApplicationConnection.VAR_FIELD_SEPARATOR));
  863. String id = split[0];
  864. if (curId == null) {
  865. curId = id;
  866. } else if (!curId.equals(id)) {
  867. printPaintablesVariables(vars, curId, c);
  868. vars.clear();
  869. curId = id;
  870. }
  871. split[0] = value;
  872. vars.add(split);
  873. }
  874. if (!vars.isEmpty()) {
  875. printPaintablesVariables(vars, curId, c);
  876. }
  877. } catch (Exception e) {
  878. VConsole.error(e);
  879. }
  880. }
  881. /**
  882. * Temporarily sets the {@code styleProperty} to {@code tempValue} and then
  883. * resets it to its current value. Used mainly to work around rendering
  884. * issues in IE (and possibly in other browsers)
  885. *
  886. * @param element
  887. * The target element
  888. * @param styleProperty
  889. * The name of the property to set
  890. * @param tempValue
  891. * The temporary value
  892. */
  893. public static void setStyleTemporarily(Element element,
  894. final String styleProperty, String tempValue) {
  895. final Style style = element.getStyle();
  896. final String currentValue = style.getProperty(styleProperty);
  897. style.setProperty(styleProperty, tempValue);
  898. element.getOffsetWidth();
  899. style.setProperty(styleProperty, currentValue);
  900. }
  901. /**
  902. * A helper method to return the client position from an event. Returns
  903. * position from either first changed touch (if touch event) or from the
  904. * event itself.
  905. *
  906. * @param event
  907. * @return
  908. */
  909. public static int getTouchOrMouseClientX(Event event) {
  910. if (isTouchEvent(event)) {
  911. return event.getChangedTouches().get(0).getClientX();
  912. } else {
  913. return event.getClientX();
  914. }
  915. }
  916. /**
  917. * A helper method to return the client position from an event. Returns
  918. * position from either first changed touch (if touch event) or from the
  919. * event itself.
  920. *
  921. * @param event
  922. * @return
  923. */
  924. public static int getTouchOrMouseClientY(Event event) {
  925. if (isTouchEvent(event)) {
  926. return event.getChangedTouches().get(0).getClientY();
  927. } else {
  928. return event.getClientY();
  929. }
  930. }
  931. /**
  932. *
  933. * @see #getTouchOrMouseClientY(Event)
  934. * @param currentGwtEvent
  935. * @return
  936. */
  937. public static int getTouchOrMouseClientY(NativeEvent currentGwtEvent) {
  938. return getTouchOrMouseClientY(Event.as(currentGwtEvent));
  939. }
  940. /**
  941. * @see #getTouchOrMouseClientX(Event)
  942. *
  943. * @param event
  944. * @return
  945. */
  946. public static int getTouchOrMouseClientX(NativeEvent event) {
  947. return getTouchOrMouseClientX(Event.as(event));
  948. }
  949. public static boolean isTouchEvent(Event event) {
  950. return event.getType().contains("touch");
  951. }
  952. public static boolean isTouchEvent(NativeEvent event) {
  953. return isTouchEvent(Event.as(event));
  954. }
  955. public static void simulateClickFromTouchEvent(Event touchevent,
  956. Widget widget) {
  957. Touch touch = touchevent.getChangedTouches().get(0);
  958. final NativeEvent createMouseUpEvent = Document.get()
  959. .createMouseUpEvent(0, touch.getScreenX(), touch.getScreenY(),
  960. touch.getClientX(), touch.getClientY(), false, false,
  961. false, false, NativeEvent.BUTTON_LEFT);
  962. final NativeEvent createMouseDownEvent = Document.get()
  963. .createMouseDownEvent(0, touch.getScreenX(),
  964. touch.getScreenY(), touch.getClientX(),
  965. touch.getClientY(), false, false, false, false,
  966. NativeEvent.BUTTON_LEFT);
  967. final NativeEvent createMouseClickEvent = Document.get()
  968. .createClickEvent(0, touch.getScreenX(), touch.getScreenY(),
  969. touch.getClientX(), touch.getClientY(), false, false,
  970. false, false);
  971. /*
  972. * Get target with element from point as we want the actual element, not
  973. * the one that sunk the event.
  974. */
  975. final Element target = getElementFromPoint(touch.getClientX(),
  976. touch.getClientY());
  977. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  978. public void execute() {
  979. try {
  980. target.dispatchEvent(createMouseDownEvent);
  981. target.dispatchEvent(createMouseUpEvent);
  982. target.dispatchEvent(createMouseClickEvent);
  983. } catch (Exception e) {
  984. }
  985. }
  986. });
  987. }
  988. /**
  989. * Gets the currently focused element for Internet Explorer.
  990. *
  991. * @return The currently focused element
  992. */
  993. public native static Element getIEFocusedElement()
  994. /*-{
  995. if ($wnd.document.activeElement) {
  996. return $wnd.document.activeElement;
  997. }
  998. return null;
  999. }-*/
  1000. ;
  1001. /**
  1002. * Kind of stronger version of isAttached(). In addition to std isAttached,
  1003. * this method checks that this widget nor any of its parents is hidden. Can
  1004. * be e.g used to check whether component should react to some events or
  1005. * not.
  1006. *
  1007. * @param widget
  1008. * @return true if attached and displayed
  1009. */
  1010. public static boolean isAttachedAndDisplayed(Widget widget) {
  1011. if (widget.isAttached()) {
  1012. /*
  1013. * Failfast using offset size, then by iterating the widget tree
  1014. */
  1015. boolean notZeroSized = widget.getOffsetHeight() > 0
  1016. || widget.getOffsetWidth() > 0;
  1017. return notZeroSized || checkVisibilityRecursively(widget);
  1018. } else {
  1019. return false;
  1020. }
  1021. }
  1022. private static boolean checkVisibilityRecursively(Widget widget) {
  1023. if (widget.isVisible()) {
  1024. Widget parent = widget.getParent();
  1025. if (parent == null) {
  1026. return true; // root panel
  1027. } else {
  1028. return checkVisibilityRecursively(parent);
  1029. }
  1030. } else {
  1031. return false;
  1032. }
  1033. }
  1034. /**
  1035. * Scrolls an element into view vertically only. Modified version of
  1036. * Element.scrollIntoView.
  1037. *
  1038. * @param elem
  1039. * The element to scroll into view
  1040. */
  1041. public static native void scrollIntoViewVertically(Element elem)
  1042. /*-{
  1043. var top = elem.offsetTop;
  1044. var height = elem.offsetHeight;
  1045. if (elem.parentNode != elem.offsetParent) {
  1046. top -= elem.parentNode.offsetTop;
  1047. }
  1048. var cur = elem.parentNode;
  1049. while (cur && (cur.nodeType == 1)) {
  1050. if (top < cur.scrollTop) {
  1051. cur.scrollTop = top;
  1052. }
  1053. if (top + height > cur.scrollTop + cur.clientHeight) {
  1054. cur.scrollTop = (top + height) - cur.clientHeight;
  1055. }
  1056. var offsetTop = cur.offsetTop;
  1057. if (cur.parentNode != cur.offsetParent) {
  1058. offsetTop -= cur.parentNode.offsetTop;
  1059. }
  1060. top += offsetTop - cur.scrollTop;
  1061. cur = cur.parentNode;
  1062. }
  1063. }-*/;
  1064. /**
  1065. * Checks if the given event is either a touch event or caused by the left
  1066. * mouse button
  1067. *
  1068. * @param event
  1069. * @return true if the event is a touch event or caused by the left mouse
  1070. * button, false otherwise
  1071. */
  1072. public static boolean isTouchEventOrLeftMouseButton(Event event) {
  1073. int eventType = event.getTypeInt();
  1074. boolean touchEvent = Util.isTouchEvent(event);
  1075. return touchEvent || event.getButton() == Event.BUTTON_LEFT;
  1076. }
  1077. }