您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

WidgetUtil.java 59KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.client;
  17. import java.io.Serializable;
  18. import java.util.HashMap;
  19. import java.util.Map;
  20. import java.util.logging.Logger;
  21. import com.google.gwt.core.client.JavaScriptObject;
  22. import com.google.gwt.core.client.Scheduler;
  23. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  24. import com.google.gwt.dom.client.AnchorElement;
  25. import com.google.gwt.dom.client.DivElement;
  26. import com.google.gwt.dom.client.Document;
  27. import com.google.gwt.dom.client.Element;
  28. import com.google.gwt.dom.client.NativeEvent;
  29. import com.google.gwt.dom.client.Node;
  30. import com.google.gwt.dom.client.NodeList;
  31. import com.google.gwt.dom.client.Style;
  32. import com.google.gwt.dom.client.Style.Display;
  33. import com.google.gwt.dom.client.Style.Unit;
  34. import com.google.gwt.dom.client.Touch;
  35. import com.google.gwt.event.dom.client.KeyEvent;
  36. import com.google.gwt.regexp.shared.MatchResult;
  37. import com.google.gwt.regexp.shared.RegExp;
  38. import com.google.gwt.user.client.Command;
  39. import com.google.gwt.user.client.DOM;
  40. import com.google.gwt.user.client.Event;
  41. import com.google.gwt.user.client.EventListener;
  42. import com.google.gwt.user.client.Window;
  43. import com.google.gwt.user.client.ui.RootPanel;
  44. import com.google.gwt.user.client.ui.Widget;
  45. import com.vaadin.shared.util.SharedUtil;
  46. /**
  47. * Utility methods which are related to client side code only
  48. */
  49. public class WidgetUtil {
  50. /**
  51. * Helper method for debugging purposes.
  52. *
  53. * Stops execution on firefox browsers on a breakpoint.
  54. *
  55. */
  56. public static native void browserDebugger()
  57. /*-{
  58. if($wnd.console)
  59. debugger;
  60. }-*/;
  61. /**
  62. * Redirects the browser to the given url or refreshes the page if url is
  63. * null
  64. *
  65. * @since 7.6
  66. * @param url
  67. * The url to redirect to or null to refresh
  68. */
  69. public static native void redirect(String url)
  70. /*-{
  71. if (url) {
  72. $wnd.location = url;
  73. } else {
  74. $wnd.location.reload(false);
  75. }
  76. }-*/;
  77. /**
  78. * Helper method for a bug fix #14041. For mozilla getKeyCode return 0 for
  79. * space bar (because space is considered as char). If return 0 use
  80. * getCharCode.
  81. *
  82. * @param event
  83. * @return return key code
  84. * @since 7.2.4
  85. */
  86. public static int getKeyCode(KeyEvent<?> event) {
  87. int keyCode = event.getNativeEvent().getKeyCode();
  88. if (keyCode == 0) {
  89. keyCode = event.getNativeEvent().getCharCode();
  90. }
  91. return keyCode;
  92. }
  93. /**
  94. *
  95. * Returns the topmost element of from given coordinates.
  96. *
  97. * TODO fix crossplat issues clientX vs pageX. See quircksmode. Not critical
  98. * for vaadin as we scroll div istead of page.
  99. *
  100. * @param x
  101. * @param y
  102. * @return the element at given coordinates
  103. */
  104. public static native Element getElementFromPoint(int clientX, int clientY)
  105. /*-{
  106. var el = $wnd.document.elementFromPoint(clientX, clientY);
  107. // Call elementFromPoint two times to make sure IE8 also returns something sensible if the application is running in an iframe
  108. el = $wnd.document.elementFromPoint(clientX, clientY);
  109. if(el != null && el.nodeType == 3) {
  110. el = el.parentNode;
  111. }
  112. return el;
  113. }-*/;
  114. public static float parseRelativeSize(String size) {
  115. if (size == null || !size.endsWith("%")) {
  116. return -1;
  117. }
  118. try {
  119. return Float.parseFloat(size.substring(0, size.length() - 1));
  120. } catch (Exception e) {
  121. getLogger().warning("Unable to parse relative size");
  122. return -1;
  123. }
  124. }
  125. private static final Element escapeHtmlHelper = DOM.createDiv();
  126. /**
  127. * Converts html entities to text.
  128. *
  129. * @param html
  130. * @return escaped string presentation of given html
  131. */
  132. public static String escapeHTML(String html) {
  133. DOM.setInnerText(escapeHtmlHelper, html);
  134. String escapedText = DOM.getInnerHTML(escapeHtmlHelper);
  135. if (BrowserInfo.get().isIE8()) {
  136. // #7478 IE8 "incorrectly" returns "<br>" for newlines set using
  137. // setInnerText. The same for " " which is converted to "&nbsp;"
  138. escapedText = escapedText.replaceAll("<(BR|br)>", "\n");
  139. escapedText = escapedText.replaceAll("&nbsp;", " ");
  140. }
  141. return escapedText;
  142. }
  143. /**
  144. * Escapes the string so it is safe to write inside an HTML attribute.
  145. *
  146. * @param attribute
  147. * The string to escape
  148. * @return An escaped version of <literal>attribute</literal>.
  149. */
  150. public static String escapeAttribute(String attribute) {
  151. if (attribute == null) {
  152. return "";
  153. }
  154. attribute = attribute.replace("\"", "&quot;");
  155. attribute = attribute.replace("'", "&#39;");
  156. attribute = attribute.replace(">", "&gt;");
  157. attribute = attribute.replace("<", "&lt;");
  158. attribute = attribute.replace("&", "&amp;");
  159. return attribute;
  160. }
  161. /**
  162. * Clones given element as in JavaScript.
  163. *
  164. * Deprecate this if there appears similar method into GWT someday.
  165. *
  166. * @param element
  167. * @param deep
  168. * clone child tree also
  169. * @return
  170. */
  171. public static native Element cloneNode(Element element, boolean deep)
  172. /*-{
  173. return element.cloneNode(deep);
  174. }-*/;
  175. public static int measureHorizontalPaddingAndBorder(Element element,
  176. int paddingGuess) {
  177. String originalWidth = DOM.getStyleAttribute(element, "width");
  178. int originalOffsetWidth = element.getOffsetWidth();
  179. int widthGuess = (originalOffsetWidth - paddingGuess);
  180. if (widthGuess < 1) {
  181. widthGuess = 1;
  182. }
  183. element.getStyle().setWidth(widthGuess, Unit.PX);
  184. int padding = element.getOffsetWidth() - widthGuess;
  185. element.getStyle().setProperty("width", originalWidth);
  186. return padding;
  187. }
  188. public static int measureVerticalPaddingAndBorder(Element element,
  189. int paddingGuess) {
  190. String originalHeight = DOM.getStyleAttribute(element, "height");
  191. int originalOffsetHeight = element.getOffsetHeight();
  192. int widthGuess = (originalOffsetHeight - paddingGuess);
  193. if (widthGuess < 1) {
  194. widthGuess = 1;
  195. }
  196. element.getStyle().setHeight(widthGuess, Unit.PX);
  197. int padding = element.getOffsetHeight() - widthGuess;
  198. element.getStyle().setProperty("height", originalHeight);
  199. return padding;
  200. }
  201. public static int measureHorizontalBorder(Element element) {
  202. int borders;
  203. if (BrowserInfo.get().isIE()) {
  204. String width = element.getStyle().getProperty("width");
  205. String height = element.getStyle().getProperty("height");
  206. int offsetWidth = element.getOffsetWidth();
  207. int offsetHeight = element.getOffsetHeight();
  208. if (offsetHeight < 1) {
  209. offsetHeight = 1;
  210. }
  211. if (offsetWidth < 1) {
  212. offsetWidth = 10;
  213. }
  214. element.getStyle().setPropertyPx("height", offsetHeight);
  215. element.getStyle().setPropertyPx("width", offsetWidth);
  216. borders = element.getOffsetWidth() - element.getClientWidth();
  217. element.getStyle().setProperty("width", width);
  218. element.getStyle().setProperty("height", height);
  219. } else {
  220. borders = element.getOffsetWidth()
  221. - element.getPropertyInt("clientWidth");
  222. }
  223. assert borders >= 0;
  224. return borders;
  225. }
  226. public static int measureVerticalBorder(Element element) {
  227. int borders;
  228. if (BrowserInfo.get().isIE()) {
  229. String width = element.getStyle().getProperty("width");
  230. String height = element.getStyle().getProperty("height");
  231. int offsetWidth = element.getOffsetWidth();
  232. int offsetHeight = element.getOffsetHeight();
  233. if (offsetHeight < 1) {
  234. offsetHeight = 1;
  235. }
  236. if (offsetWidth < 1) {
  237. offsetWidth = 10;
  238. }
  239. element.getStyle().setPropertyPx("width", offsetWidth);
  240. element.getStyle().setPropertyPx("height", offsetHeight);
  241. borders = element.getOffsetHeight()
  242. - element.getPropertyInt("clientHeight");
  243. element.getStyle().setProperty("height", height);
  244. element.getStyle().setProperty("width", width);
  245. } else {
  246. borders = element.getOffsetHeight()
  247. - element.getPropertyInt("clientHeight");
  248. }
  249. assert borders >= 0;
  250. return borders;
  251. }
  252. public static int measureMarginLeft(Element element) {
  253. return element.getAbsoluteLeft()
  254. - element.getParentElement().getAbsoluteLeft();
  255. }
  256. public static int setHeightExcludingPaddingAndBorder(Widget widget,
  257. String height, int paddingBorderGuess) {
  258. if (height.equals("")) {
  259. setHeight(widget, "");
  260. return paddingBorderGuess;
  261. } else if (height.endsWith("px")) {
  262. int pixelHeight = Integer.parseInt(height.substring(0,
  263. height.length() - 2));
  264. return setHeightExcludingPaddingAndBorder(widget.getElement(),
  265. pixelHeight, paddingBorderGuess, false);
  266. } else {
  267. // Set the height in unknown units
  268. setHeight(widget, height);
  269. // Use the offsetWidth
  270. return setHeightExcludingPaddingAndBorder(widget.getElement(),
  271. widget.getOffsetHeight(), paddingBorderGuess, true);
  272. }
  273. }
  274. private static void setWidth(Widget widget, String width) {
  275. widget.getElement().getStyle().setProperty("width", width);
  276. }
  277. private static void setHeight(Widget widget, String height) {
  278. widget.getElement().getStyle().setProperty("height", height);
  279. }
  280. public static int setWidthExcludingPaddingAndBorder(Widget widget,
  281. String width, int paddingBorderGuess) {
  282. if (width.equals("")) {
  283. setWidth(widget, "");
  284. return paddingBorderGuess;
  285. } else if (width.endsWith("px")) {
  286. int pixelWidth = Integer.parseInt(width.substring(0,
  287. width.length() - 2));
  288. return setWidthExcludingPaddingAndBorder(widget.getElement(),
  289. pixelWidth, paddingBorderGuess, false);
  290. } else {
  291. setWidth(widget, width);
  292. return setWidthExcludingPaddingAndBorder(widget.getElement(),
  293. widget.getOffsetWidth(), paddingBorderGuess, true);
  294. }
  295. }
  296. public static int setWidthExcludingPaddingAndBorder(Element element,
  297. int requestedWidth, int horizontalPaddingBorderGuess,
  298. boolean requestedWidthIncludesPaddingBorder) {
  299. int widthGuess = requestedWidth - horizontalPaddingBorderGuess;
  300. if (widthGuess < 0) {
  301. widthGuess = 0;
  302. }
  303. element.getStyle().setWidth(widthGuess, Unit.PX);
  304. int captionOffsetWidth = DOM.getElementPropertyInt(element,
  305. "offsetWidth");
  306. int actualPadding = captionOffsetWidth - widthGuess;
  307. if (requestedWidthIncludesPaddingBorder) {
  308. actualPadding += actualPadding;
  309. }
  310. if (actualPadding != horizontalPaddingBorderGuess) {
  311. int w = requestedWidth - actualPadding;
  312. if (w < 0) {
  313. // Cannot set negative width even if we would want to
  314. w = 0;
  315. }
  316. element.getStyle().setWidth(w, Unit.PX);
  317. }
  318. return actualPadding;
  319. }
  320. public static int setHeightExcludingPaddingAndBorder(Element element,
  321. int requestedHeight, int verticalPaddingBorderGuess,
  322. boolean requestedHeightIncludesPaddingBorder) {
  323. int heightGuess = requestedHeight - verticalPaddingBorderGuess;
  324. if (heightGuess < 0) {
  325. heightGuess = 0;
  326. }
  327. element.getStyle().setHeight(heightGuess, Unit.PX);
  328. int captionOffsetHeight = DOM.getElementPropertyInt(element,
  329. "offsetHeight");
  330. int actualPadding = captionOffsetHeight - heightGuess;
  331. if (requestedHeightIncludesPaddingBorder) {
  332. actualPadding += actualPadding;
  333. }
  334. if (actualPadding != verticalPaddingBorderGuess) {
  335. int h = requestedHeight - actualPadding;
  336. if (h < 0) {
  337. // Cannot set negative height even if we would want to
  338. h = 0;
  339. }
  340. element.getStyle().setHeight(h, Unit.PX);
  341. }
  342. return actualPadding;
  343. }
  344. public static void setFloat(Element element, String value) {
  345. if (BrowserInfo.get().isIE()) {
  346. element.getStyle().setProperty("styleFloat", value);
  347. } else {
  348. element.getStyle().setProperty("cssFloat", value);
  349. }
  350. }
  351. private static int detectedScrollbarSize = -1;
  352. private static int detectedSubPixelRoundingFactor = -1;
  353. public static int getNativeScrollbarSize() {
  354. if (detectedScrollbarSize < 0) {
  355. Element scroller = DOM.createDiv();
  356. scroller.getStyle().setProperty("width", "50px");
  357. scroller.getStyle().setProperty("height", "50px");
  358. scroller.getStyle().setProperty("overflow", "scroll");
  359. scroller.getStyle().setProperty("position", "absolute");
  360. scroller.getStyle().setProperty("marginLeft", "-5000px");
  361. RootPanel.getBodyElement().appendChild(scroller);
  362. detectedScrollbarSize = scroller.getOffsetWidth()
  363. - scroller.getPropertyInt("clientWidth");
  364. RootPanel.getBodyElement().removeChild(scroller);
  365. }
  366. return detectedScrollbarSize;
  367. }
  368. /**
  369. * Defers the execution of {@link #runWebkitOverflowAutoFix(Element)}
  370. *
  371. * @since 7.2.6
  372. * @param elem
  373. * with overflow auto
  374. */
  375. public static void runWebkitOverflowAutoFixDeferred(final Element elem) {
  376. Scheduler.get().scheduleDeferred(new Command() {
  377. @Override
  378. public void execute() {
  379. WidgetUtil.runWebkitOverflowAutoFix(elem);
  380. }
  381. });
  382. }
  383. /**
  384. * Run workaround for webkits overflow auto issue.
  385. *
  386. * See: our bug #2138 and https://bugs.webkit.org/show_bug.cgi?id=21462
  387. *
  388. * @param elem
  389. * with overflow auto
  390. */
  391. public static void runWebkitOverflowAutoFix(final Element elem) {
  392. // Add max version if fix lands sometime to Webkit
  393. // Starting from Opera 11.00, also a problem in Opera
  394. if (BrowserInfo.get().requiresOverflowAutoFix()) {
  395. final String originalOverflow = elem.getStyle().getProperty(
  396. "overflow");
  397. final String originalOverflowX = elem.getStyle().getProperty(
  398. "overflowX");
  399. final String originalOverflowY = elem.getStyle().getProperty(
  400. "overflowY");
  401. if ("hidden".equals(originalOverflow)
  402. || "hidden".equals(originalOverflowX)
  403. || "hidden".equals(originalOverflowY)) {
  404. return;
  405. }
  406. // check the scrolltop value before hiding the element
  407. final int scrolltop = elem.getScrollTop();
  408. final int scrollleft = elem.getScrollLeft();
  409. elem.getStyle().setProperty("overflow", "hidden");
  410. Scheduler.get().scheduleDeferred(new Command() {
  411. @Override
  412. public void execute() {
  413. // Dough, Safari scroll auto means actually just a moped
  414. elem.getStyle().setProperty("overflow", originalOverflow);
  415. if (!originalOverflowX.isEmpty()) {
  416. elem.getStyle().setProperty("overflowX",
  417. originalOverflowX);
  418. }
  419. if (!originalOverflowY.isEmpty()) {
  420. elem.getStyle().setProperty("overflowY",
  421. originalOverflowY);
  422. }
  423. if (scrolltop > 0 || elem.getScrollTop() > 0) {
  424. int scrollvalue = scrolltop;
  425. if (scrollvalue == 0) {
  426. // mysterious are the ways of webkits scrollbar
  427. // handling. In some cases webkit reports bad (0)
  428. // scrolltop before hiding the element temporary,
  429. // sometimes after.
  430. scrollvalue = elem.getScrollTop();
  431. }
  432. // fix another bug where scrollbar remains in wrong
  433. // position
  434. elem.setScrollTop(scrollvalue - 1);
  435. elem.setScrollTop(scrollvalue);
  436. }
  437. // fix for #6940 : Table horizontal scroll sometimes not
  438. // updated when collapsing/expanding columns
  439. // Also appeared in Safari 5.1 with webkit 534 (#7667)
  440. if ((BrowserInfo.get().isChrome() || (BrowserInfo.get()
  441. .isSafari() && BrowserInfo.get().getWebkitVersion() >= 534))
  442. && (scrollleft > 0 || elem.getScrollLeft() > 0)) {
  443. int scrollvalue = scrollleft;
  444. if (scrollvalue == 0) {
  445. // mysterious are the ways of webkits scrollbar
  446. // handling. In some cases webkit may report a bad
  447. // (0) scrollleft before hiding the element
  448. // temporary, sometimes after.
  449. scrollvalue = elem.getScrollLeft();
  450. }
  451. // fix another bug where scrollbar remains in wrong
  452. // position
  453. elem.setScrollLeft(scrollvalue - 1);
  454. elem.setScrollLeft(scrollvalue);
  455. }
  456. }
  457. });
  458. }
  459. }
  460. public static void alert(String string) {
  461. if (true) {
  462. Window.alert(string);
  463. }
  464. }
  465. /**
  466. * Gets the border-box width for the given element, i.e. element width +
  467. * border + padding. Always rounds up to nearest integer.
  468. *
  469. * @param element
  470. * The element to check
  471. * @return The border-box width for the element
  472. */
  473. public static int getRequiredWidth(com.google.gwt.dom.client.Element element) {
  474. int reqWidth = getRequiredWidthBoundingClientRect(element);
  475. if (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()) {
  476. int csSize = getRequiredWidthComputedStyle(element);
  477. if (csSize == reqWidth + 1) {
  478. // If computed style reports one pixel larger than requiredWidth
  479. // we would be rounding in the wrong direction in IE9. Round up
  480. // instead.
  481. // We do not always use csSize as it e.g. for 100% wide Labels
  482. // in GridLayouts produces senseless values (see e.g.
  483. // ThemeTestUI with Runo).
  484. return csSize;
  485. }
  486. }
  487. return reqWidth;
  488. }
  489. /**
  490. * Gets the border-box width for the given element, i.e. element width +
  491. * border + padding.
  492. *
  493. * @param element
  494. * The element to check
  495. * @return The border-box width for the element
  496. */
  497. public static double getRequiredWidthDouble(
  498. com.google.gwt.dom.client.Element element) {
  499. double reqWidth = getRequiredWidthBoundingClientRectDouble(element);
  500. if (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()) {
  501. double csWidth = getRequiredWidthComputedStyleDouble(element);
  502. if (csWidth > reqWidth && csWidth <= (reqWidth + 1)) {
  503. // IE9 rounds reqHeight to integers BUT sometimes reports wrong
  504. // csHeight it seems, so we only use csHeight if it is within a
  505. // rounding error
  506. return csWidth;
  507. }
  508. }
  509. return reqWidth;
  510. }
  511. /**
  512. * Gets the border-box height for the given element, i.e. element height +
  513. * border + padding. Always rounds up to nearest integer.
  514. *
  515. * @param element
  516. * The element to check
  517. * @return The border-box height for the element
  518. */
  519. public static int getRequiredHeight(
  520. com.google.gwt.dom.client.Element element) {
  521. int reqHeight = getRequiredHeightBoundingClientRect(element);
  522. if (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()) {
  523. int csSize = getRequiredHeightComputedStyle(element);
  524. if (csSize == reqHeight + 1) {
  525. // If computed style reports one pixel larger than
  526. // requiredHeight we would be rounding in the wrong direction in
  527. // IE9. Round up instead.
  528. // We do not always use csSize as it e.g. for 100% wide Labels
  529. // in GridLayouts produces senseless values (see e.g.
  530. // ThemeTestUI with Runo).
  531. return csSize;
  532. }
  533. }
  534. return reqHeight;
  535. }
  536. /**
  537. * Gets the border-box height for the given element, i.e. element height +
  538. * border + padding.
  539. *
  540. * @param element
  541. * The element to check
  542. * @return The border-box height for the element
  543. */
  544. public static double getRequiredHeightDouble(
  545. com.google.gwt.dom.client.Element element) {
  546. double reqHeight = getRequiredHeightBoundingClientRectDouble(element);
  547. if (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()) {
  548. double csHeight = getRequiredHeightComputedStyleDouble(element);
  549. if (csHeight > reqHeight && csHeight <= (reqHeight + 1)) {
  550. // IE9 rounds reqHeight to integers BUT sometimes reports wrong
  551. // csHeight it seems, so we only use csHeight if it is within a
  552. // rounding error
  553. // Although sometimes it also happens that IE9 returns an
  554. // incorrectly rounded down requiredHeight and a computed height
  555. // which is exactly one larger, hence the "<="...
  556. return csHeight;
  557. }
  558. }
  559. return reqHeight;
  560. }
  561. /**
  562. * Calculates the width of the element's bounding rectangle.
  563. * <p>
  564. * In case the browser doesn't support bounding rectangles, the returned
  565. * value is the offset width.
  566. *
  567. * @param element
  568. * the element of which to calculate the width
  569. * @return the width of the element
  570. */
  571. public static int getRequiredWidthBoundingClientRect(
  572. com.google.gwt.dom.client.Element element) {
  573. return (int) Math
  574. .ceil(getRequiredWidthBoundingClientRectDouble(element));
  575. }
  576. /**
  577. * Calculates the width of the element's bounding rectangle to subpixel
  578. * precision.
  579. * <p>
  580. * In case the browser doesn't support bounding rectangles, the returned
  581. * value is the offset width.
  582. *
  583. * @param element
  584. * the element of which to calculate the width
  585. * @return the subpixel-accurate width of the element
  586. * @since 7.4
  587. */
  588. public static native double getRequiredWidthBoundingClientRectDouble(
  589. com.google.gwt.dom.client.Element element)
  590. /*-{
  591. if (element.getBoundingClientRect) {
  592. var rect = element.getBoundingClientRect();
  593. return rect.right - rect.left;
  594. } else {
  595. return element.offsetWidth;
  596. }
  597. }-*/;
  598. public static int getRequiredHeightComputedStyle(
  599. com.google.gwt.dom.client.Element element) {
  600. return (int) Math.ceil(getRequiredHeightComputedStyleDouble(element));
  601. }
  602. public static native double getRequiredHeightComputedStyleDouble(
  603. com.google.gwt.dom.client.Element element)
  604. /*-{
  605. var cs = element.ownerDocument.defaultView.getComputedStyle(element);
  606. var heightPx = cs.height;
  607. if(heightPx == 'auto'){
  608. // Fallback for inline elements
  609. return @com.vaadin.client.WidgetUtil::getRequiredHeightBoundingClientRectDouble(Lcom/google/gwt/dom/client/Element;)(element);
  610. }
  611. var height = parseFloat(heightPx); // Will automatically skip "px" suffix
  612. var border = parseFloat(cs.borderTopWidth) + parseFloat(cs.borderBottomWidth); // Will automatically skip "px" suffix
  613. var padding = parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom); // Will automatically skip "px" suffix
  614. return height+border+padding;
  615. }-*/;
  616. public static int getRequiredWidthComputedStyle(
  617. com.google.gwt.dom.client.Element element) {
  618. return (int) Math.ceil(getRequiredWidthComputedStyleDouble(element));
  619. }
  620. public static native int getRequiredWidthComputedStyleDouble(
  621. com.google.gwt.dom.client.Element element)
  622. /*-{
  623. var cs = element.ownerDocument.defaultView.getComputedStyle(element);
  624. var widthPx = cs.width;
  625. if(widthPx == 'auto'){
  626. // Fallback for inline elements
  627. return @com.vaadin.client.WidgetUtil::getRequiredWidthBoundingClientRectDouble(Lcom/google/gwt/dom/client/Element;)(element);
  628. }
  629. var width = parseFloat(widthPx); // Will automatically skip "px" suffix
  630. var border = parseFloat(cs.borderLeftWidth) + parseFloat(cs.borderRightWidth); // Will automatically skip "px" suffix
  631. var padding = parseFloat(cs.paddingLeft) + parseFloat(cs.paddingRight); // Will automatically skip "px" suffix
  632. return width+border+padding;
  633. }-*/;
  634. /**
  635. * Calculates the height of the element's bounding rectangle.
  636. * <p>
  637. * In case the browser doesn't support bounding rectangles, the returned
  638. * value is the offset height.
  639. *
  640. * @param element
  641. * the element of which to calculate the height
  642. * @return the height of the element
  643. */
  644. public static int getRequiredHeightBoundingClientRect(
  645. com.google.gwt.dom.client.Element element) {
  646. return (int) Math
  647. .ceil(getRequiredHeightBoundingClientRectDouble(element));
  648. }
  649. /**
  650. * Calculates the height of the element's bounding rectangle to subpixel
  651. * precision.
  652. * <p>
  653. * In case the browser doesn't support bounding rectangles, the returned
  654. * value is the offset height.
  655. *
  656. * @param element
  657. * the element of which to calculate the height
  658. * @return the subpixel-accurate height of the element
  659. * @since 7.4
  660. */
  661. public static native double getRequiredHeightBoundingClientRectDouble(
  662. com.google.gwt.dom.client.Element element)
  663. /*-{
  664. var height;
  665. if (element.getBoundingClientRect != null) {
  666. var rect = element.getBoundingClientRect();
  667. height = rect.bottom - rect.top;
  668. } else {
  669. height = element.offsetHeight;
  670. }
  671. return height;
  672. }-*/;
  673. public static int getRequiredWidth(Widget widget) {
  674. return getRequiredWidth(widget.getElement());
  675. }
  676. public static int getRequiredHeight(Widget widget) {
  677. return getRequiredHeight(widget.getElement());
  678. }
  679. /**
  680. * Detects what is currently the overflow style attribute in given element.
  681. *
  682. * @param pe
  683. * the element to detect
  684. * @return true if auto or scroll
  685. */
  686. public static boolean mayHaveScrollBars(com.google.gwt.dom.client.Element pe) {
  687. String overflow = getComputedStyle(pe, "overflow");
  688. if (overflow != null) {
  689. if (overflow.equals("auto") || overflow.equals("scroll")) {
  690. return true;
  691. } else {
  692. return false;
  693. }
  694. } else {
  695. return false;
  696. }
  697. }
  698. /**
  699. * A simple helper method to detect "computed style" (aka style sheets +
  700. * element styles). Values returned differ a lot depending on browsers.
  701. * Always be very careful when using this.
  702. *
  703. * @param el
  704. * the element from which the style property is detected
  705. * @param p
  706. * the property to detect
  707. * @return String value of style property
  708. */
  709. private static native String getComputedStyle(
  710. com.google.gwt.dom.client.Element el, String p)
  711. /*-{
  712. try {
  713. if (el.currentStyle) {
  714. // IE
  715. return el.currentStyle[p];
  716. } else if (window.getComputedStyle) {
  717. // Sa, FF, Opera
  718. var view = el.ownerDocument.defaultView;
  719. return view.getComputedStyle(el,null).getPropertyValue(p);
  720. } else {
  721. // fall back for non IE, Sa, FF, Opera
  722. return "";
  723. }
  724. } catch (e) {
  725. return "";
  726. }
  727. }-*/;
  728. /**
  729. * Will (attempt) to focus the given DOM Element.
  730. *
  731. * @param el
  732. * the element to focus
  733. */
  734. public static native void focus(Element el)
  735. /*-{
  736. try {
  737. el.focus();
  738. } catch (e) {
  739. }
  740. }-*/;
  741. /**
  742. * Helper method to find first instance of given Widget type found by
  743. * traversing DOM upwards from given element.
  744. * <p>
  745. * <strong>Note:</strong> If {@code element} is inside some widget {@code W}
  746. * , <em>and</em> {@code W} in turn is wrapped in a {@link Composite}
  747. * {@code C}, this method will not find {@code W}. It returns either
  748. * {@code C} or null, depending on whether the class parameter matches. This
  749. * may also be the case with other Composite-like classes that hijack the
  750. * event handling of their child widget(s).
  751. *
  752. * @param element
  753. * the element where to start seeking of Widget
  754. * @param class1
  755. * the Widget type to seek for
  756. */
  757. @SuppressWarnings("unchecked")
  758. public static <T> T findWidget(Element element,
  759. Class<? extends Widget> class1) {
  760. if (element != null) {
  761. /* First seek for the first EventListener (~Widget) from dom */
  762. EventListener eventListener = null;
  763. while (eventListener == null && element != null) {
  764. eventListener = Event.getEventListener(element);
  765. if (eventListener == null) {
  766. element = element.getParentElement();
  767. }
  768. }
  769. if (eventListener instanceof Widget) {
  770. /*
  771. * Then find the first widget of type class1 from widget
  772. * hierarchy
  773. */
  774. Widget w = (Widget) eventListener;
  775. while (w != null) {
  776. if (class1 == null || w.getClass() == class1) {
  777. return (T) w;
  778. }
  779. w = w.getParent();
  780. }
  781. }
  782. }
  783. return null;
  784. }
  785. /**
  786. * Force webkit to redraw an element
  787. *
  788. * @param element
  789. * The element that should be redrawn
  790. */
  791. public static void forceWebkitRedraw(Element element) {
  792. Style style = element.getStyle();
  793. String s = style.getProperty("webkitTransform");
  794. if (s == null || s.length() == 0) {
  795. style.setProperty("webkitTransform", "scale(1)");
  796. } else {
  797. style.setProperty("webkitTransform", "");
  798. }
  799. }
  800. /**
  801. * Performs a hack to trigger a re-layout in the IE8. This is usually
  802. * necessary in cases where IE8 "forgets" to update child elements when they
  803. * resize.
  804. *
  805. * @param e
  806. * The element to perform the hack on
  807. */
  808. public static final void forceIE8Redraw(Element e) {
  809. if (BrowserInfo.get().isIE8()) {
  810. forceIERedraw(e);
  811. }
  812. }
  813. /**
  814. * Performs a hack to trigger a re-layout in the IE browser. This is usually
  815. * necessary in cases where IE "forgets" to update child elements when they
  816. * resize.
  817. *
  818. * @since 7.3
  819. * @param e
  820. * The element to perform the hack on
  821. */
  822. public static void forceIERedraw(Element e) {
  823. if (BrowserInfo.get().isIE()) {
  824. setStyleTemporarily(e, "zoom", "1");
  825. }
  826. }
  827. /**
  828. * Detaches and re-attaches the element from its parent. The element is
  829. * reattached at the same position in the DOM as it was before.
  830. *
  831. * Does nothing if the element is not attached to the DOM.
  832. *
  833. * @param element
  834. * The element to detach and re-attach
  835. */
  836. public static void detachAttach(Element element) {
  837. if (element == null) {
  838. return;
  839. }
  840. Node nextSibling = element.getNextSibling();
  841. Node parent = element.getParentNode();
  842. if (parent == null) {
  843. return;
  844. }
  845. parent.removeChild(element);
  846. if (nextSibling == null) {
  847. parent.appendChild(element);
  848. } else {
  849. parent.insertBefore(element, nextSibling);
  850. }
  851. }
  852. public static void sinkOnloadForImages(Element element) {
  853. NodeList<com.google.gwt.dom.client.Element> imgElements = element
  854. .getElementsByTagName("img");
  855. for (int i = 0; i < imgElements.getLength(); i++) {
  856. DOM.sinkEvents(imgElements.getItem(i), Event.ONLOAD);
  857. }
  858. }
  859. /**
  860. * Returns the index of the childElement within its parent.
  861. *
  862. * @param subElement
  863. * @return
  864. */
  865. public static int getChildElementIndex(Element childElement) {
  866. int idx = 0;
  867. Node n = childElement;
  868. while ((n = n.getPreviousSibling()) != null) {
  869. idx++;
  870. }
  871. return idx;
  872. }
  873. /**
  874. * Temporarily sets the {@code styleProperty} to {@code tempValue} and then
  875. * resets it to its current value. Used mainly to work around rendering
  876. * issues in IE (and possibly in other browsers)
  877. *
  878. * @param element
  879. * The target element
  880. * @param styleProperty
  881. * The name of the property to set
  882. * @param tempValue
  883. * The temporary value
  884. */
  885. public static void setStyleTemporarily(Element element,
  886. final String styleProperty, String tempValue) {
  887. final Style style = element.getStyle();
  888. final String currentValue = style.getProperty(styleProperty);
  889. style.setProperty(styleProperty, tempValue);
  890. element.getOffsetWidth();
  891. style.setProperty(styleProperty, currentValue);
  892. }
  893. /**
  894. * A helper method to return the client position from an event. Returns
  895. * position from either first changed touch (if touch event) or from the
  896. * event itself.
  897. *
  898. * @param event
  899. * @return
  900. */
  901. public static int getTouchOrMouseClientX(Event event) {
  902. if (isTouchEvent(event)) {
  903. return event.getChangedTouches().get(0).getClientX();
  904. } else {
  905. return event.getClientX();
  906. }
  907. }
  908. /**
  909. * Find the element corresponding to the coordinates in the passed mouse
  910. * event. Please note that this is not always the same as the target of the
  911. * event e.g. if event capture is used.
  912. *
  913. * @param event
  914. * the mouse event to get coordinates from
  915. * @return the element at the coordinates of the event
  916. */
  917. public static Element getElementUnderMouse(NativeEvent event) {
  918. int pageX = getTouchOrMouseClientX(event);
  919. int pageY = getTouchOrMouseClientY(event);
  920. return getElementFromPoint(pageX, pageY);
  921. }
  922. /**
  923. * A helper method to return the client position from an event. Returns
  924. * position from either first changed touch (if touch event) or from the
  925. * event itself.
  926. *
  927. * @param event
  928. * @return
  929. */
  930. public static int getTouchOrMouseClientY(Event event) {
  931. if (isTouchEvent(event)) {
  932. return event.getChangedTouches().get(0).getClientY();
  933. } else {
  934. return event.getClientY();
  935. }
  936. }
  937. /**
  938. *
  939. * @see #getTouchOrMouseClientY(Event)
  940. * @param currentGwtEvent
  941. * @return
  942. */
  943. public static int getTouchOrMouseClientY(NativeEvent currentGwtEvent) {
  944. return getTouchOrMouseClientY(Event.as(currentGwtEvent));
  945. }
  946. /**
  947. * @see #getTouchOrMouseClientX(Event)
  948. *
  949. * @param event
  950. * @return
  951. */
  952. public static int getTouchOrMouseClientX(NativeEvent event) {
  953. return getTouchOrMouseClientX(Event.as(event));
  954. }
  955. public static boolean isTouchEvent(Event event) {
  956. return event.getType().contains("touch");
  957. }
  958. public static boolean isTouchEvent(NativeEvent event) {
  959. return isTouchEvent(Event.as(event));
  960. }
  961. public static void simulateClickFromTouchEvent(Event touchevent,
  962. Widget widget) {
  963. Touch touch = touchevent.getChangedTouches().get(0);
  964. final NativeEvent createMouseUpEvent = Document.get()
  965. .createMouseUpEvent(0, touch.getScreenX(), touch.getScreenY(),
  966. touch.getClientX(), touch.getClientY(), false, false,
  967. false, false, NativeEvent.BUTTON_LEFT);
  968. final NativeEvent createMouseDownEvent = Document.get()
  969. .createMouseDownEvent(0, touch.getScreenX(),
  970. touch.getScreenY(), touch.getClientX(),
  971. touch.getClientY(), false, false, false, false,
  972. NativeEvent.BUTTON_LEFT);
  973. final NativeEvent createMouseClickEvent = Document.get()
  974. .createClickEvent(0, touch.getScreenX(), touch.getScreenY(),
  975. touch.getClientX(), touch.getClientY(), false, false,
  976. false, false);
  977. /*
  978. * Get target with element from point as we want the actual element, not
  979. * the one that sunk the event.
  980. */
  981. final Element target = getElementFromPoint(touch.getClientX(),
  982. touch.getClientY());
  983. /*
  984. * Fixes infocusable form fields in Safari of iOS 5.x and some Android
  985. * browsers.
  986. */
  987. Widget targetWidget = findWidget(target, null);
  988. if (targetWidget instanceof com.google.gwt.user.client.ui.Focusable) {
  989. final com.google.gwt.user.client.ui.Focusable toBeFocusedWidget = (com.google.gwt.user.client.ui.Focusable) targetWidget;
  990. toBeFocusedWidget.setFocus(true);
  991. } else if (targetWidget instanceof Focusable) {
  992. ((Focusable) targetWidget).focus();
  993. }
  994. Scheduler.get().scheduleDeferred(new ScheduledCommand() {
  995. @Override
  996. public void execute() {
  997. try {
  998. target.dispatchEvent(createMouseDownEvent);
  999. target.dispatchEvent(createMouseUpEvent);
  1000. target.dispatchEvent(createMouseClickEvent);
  1001. } catch (Exception e) {
  1002. }
  1003. }
  1004. });
  1005. }
  1006. /**
  1007. * Gets the currently focused element.
  1008. *
  1009. * @return The active element or null if no active element could be found.
  1010. */
  1011. public native static Element getFocusedElement()
  1012. /*-{
  1013. if ($wnd.document.activeElement) {
  1014. return $wnd.document.activeElement;
  1015. }
  1016. return null;
  1017. }-*/;
  1018. /**
  1019. * Gets currently focused element and checks if it's editable
  1020. *
  1021. * @since 7.4
  1022. *
  1023. * @return true if focused element is editable
  1024. */
  1025. public static boolean isFocusedElementEditable() {
  1026. Element focusedElement = WidgetUtil.getFocusedElement();
  1027. if (focusedElement != null) {
  1028. String tagName = focusedElement.getTagName();
  1029. String contenteditable = focusedElement
  1030. .getAttribute("contenteditable");
  1031. return "textarea".equalsIgnoreCase(tagName)
  1032. || "input".equalsIgnoreCase(tagName)
  1033. || "true".equalsIgnoreCase(contenteditable);
  1034. }
  1035. return false;
  1036. }
  1037. /**
  1038. * Kind of stronger version of isAttached(). In addition to std isAttached,
  1039. * this method checks that this widget nor any of its parents is hidden. Can
  1040. * be e.g used to check whether component should react to some events or
  1041. * not.
  1042. *
  1043. * @param widget
  1044. * @return true if attached and displayed
  1045. */
  1046. public static boolean isAttachedAndDisplayed(Widget widget) {
  1047. if (widget.isAttached()) {
  1048. /*
  1049. * Failfast using offset size, then by iterating the widget tree
  1050. */
  1051. boolean notZeroSized = widget.getOffsetHeight() > 0
  1052. || widget.getOffsetWidth() > 0;
  1053. return notZeroSized || checkVisibilityRecursively(widget);
  1054. } else {
  1055. return false;
  1056. }
  1057. }
  1058. private static boolean checkVisibilityRecursively(Widget widget) {
  1059. if (widget.isVisible()) {
  1060. Widget parent = widget.getParent();
  1061. if (parent == null) {
  1062. return true; // root panel
  1063. } else {
  1064. return checkVisibilityRecursively(parent);
  1065. }
  1066. } else {
  1067. return false;
  1068. }
  1069. }
  1070. /**
  1071. * Scrolls an element into view vertically only. Modified version of
  1072. * Element.scrollIntoView.
  1073. *
  1074. * @param elem
  1075. * The element to scroll into view
  1076. */
  1077. public static native void scrollIntoViewVertically(Element elem)
  1078. /*-{
  1079. var top = elem.offsetTop;
  1080. var height = elem.offsetHeight;
  1081. if (elem.parentNode != elem.offsetParent) {
  1082. top -= elem.parentNode.offsetTop;
  1083. }
  1084. var cur = elem.parentNode;
  1085. while (cur && (cur.nodeType == 1)) {
  1086. if (top < cur.scrollTop) {
  1087. cur.scrollTop = top;
  1088. }
  1089. if (top + height > cur.scrollTop + cur.clientHeight) {
  1090. cur.scrollTop = (top + height) - cur.clientHeight;
  1091. }
  1092. var offsetTop = cur.offsetTop;
  1093. if (cur.parentNode != cur.offsetParent) {
  1094. offsetTop -= cur.parentNode.offsetTop;
  1095. }
  1096. top += offsetTop - cur.scrollTop;
  1097. cur = cur.parentNode;
  1098. }
  1099. }-*/;
  1100. /**
  1101. * Checks if the given event is either a touch event or caused by the left
  1102. * mouse button
  1103. *
  1104. * @param event
  1105. * @return true if the event is a touch event or caused by the left mouse
  1106. * button, false otherwise
  1107. */
  1108. public static boolean isTouchEventOrLeftMouseButton(Event event) {
  1109. boolean touchEvent = WidgetUtil.isTouchEvent(event);
  1110. return touchEvent || event.getButton() == Event.BUTTON_LEFT;
  1111. }
  1112. /**
  1113. * Resolve a relative URL to an absolute URL based on the current document's
  1114. * location.
  1115. *
  1116. * @param url
  1117. * a string with the relative URL to resolve
  1118. * @return the corresponding absolute URL as a string
  1119. */
  1120. public static String getAbsoluteUrl(String url) {
  1121. if (BrowserInfo.get().isIE8()) {
  1122. // The hard way - must use innerHTML and attach to DOM in IE8
  1123. DivElement divElement = Document.get().createDivElement();
  1124. divElement.getStyle().setDisplay(Display.NONE);
  1125. RootPanel.getBodyElement().appendChild(divElement);
  1126. divElement.setInnerHTML("<a href='" + escapeAttribute(url)
  1127. + "' ></a>");
  1128. AnchorElement a = divElement.getChild(0).cast();
  1129. String href = a.getHref();
  1130. RootPanel.getBodyElement().removeChild(divElement);
  1131. return href;
  1132. } else {
  1133. AnchorElement a = Document.get().createAnchorElement();
  1134. a.setHref(url);
  1135. return a.getHref();
  1136. }
  1137. }
  1138. /**
  1139. * Sets the selection range of an input element.
  1140. *
  1141. * We need this JSNI function to set selection range so that we can use the
  1142. * optional direction attribute to set the anchor to the end and the focus
  1143. * to the start. This makes Firefox work the same way as other browsers
  1144. * (#13477)
  1145. *
  1146. * @param elem
  1147. * the html input element.
  1148. * @param pos
  1149. * the index of the first selected character.
  1150. * @param length
  1151. * the selection length.
  1152. * @param direction
  1153. * a string indicating the direction in which the selection was
  1154. * performed. This may be "forward" or "backward", or "none" if
  1155. * the direction is unknown or irrelevant.
  1156. *
  1157. * @since 7.3
  1158. */
  1159. public native static void setSelectionRange(Element elem, int pos,
  1160. int length, String direction)
  1161. /*-{
  1162. try {
  1163. elem.setSelectionRange(pos, pos + length, direction);
  1164. } catch (e) {
  1165. // Firefox throws exception if TextBox is not visible, even if attached
  1166. }
  1167. }-*/;
  1168. /**
  1169. * The allowed value inaccuracy when comparing two double-typed pixel
  1170. * values.
  1171. * <p>
  1172. * Since we're comparing pixels on a screen, epsilon must be less than 1.
  1173. * 0.49 was deemed a perfectly fine and beautifully round number.
  1174. */
  1175. public static final double PIXEL_EPSILON = 0.49d;
  1176. /**
  1177. * Compares two double values with the error margin of
  1178. * {@link #PIXEL_EPSILON} (i.e. {@value #PIXEL_EPSILON})
  1179. *
  1180. * @param num1
  1181. * the first value for which to compare equality
  1182. * @param num2
  1183. * the second value for which to compare equality
  1184. * @since 7.4
  1185. *
  1186. * @return true if the values are considered equals; false otherwise
  1187. */
  1188. public static boolean pixelValuesEqual(final double num1, final double num2) {
  1189. return Math.abs(num1 - num2) <= PIXEL_EPSILON;
  1190. }
  1191. public static native TextRectangle getBoundingClientRect(Element e)
  1192. /*-{
  1193. return e.getBoundingClientRect();
  1194. }-*/;
  1195. public static final class TextRectangle extends JavaScriptObject {
  1196. protected TextRectangle() {
  1197. }
  1198. public native double getBottom()
  1199. /*-{
  1200. return this.bottom;
  1201. }-*/;
  1202. public native double getHeight()
  1203. /*-{
  1204. return this.height;
  1205. }-*/;
  1206. public native double getLeft()
  1207. /*-{
  1208. return this.left;
  1209. }-*/;
  1210. public native double getRight()
  1211. /*-{
  1212. return this.right;
  1213. }-*/;
  1214. public native double getTop()
  1215. /*-{
  1216. return this.top;
  1217. }-*/;
  1218. public native double getWidth()
  1219. /*-{
  1220. return this.width;
  1221. }-*/;
  1222. }
  1223. /**
  1224. * Wrap a css size value and its unit and translate back and forth to the
  1225. * string representation.<br/>
  1226. * Eg. 50%, 123px, ...
  1227. *
  1228. * @since 7.2.6
  1229. * @author Vaadin Ltd
  1230. */
  1231. @SuppressWarnings("serial")
  1232. public static class CssSize implements Serializable {
  1233. /*
  1234. * Map the size units with their type.
  1235. */
  1236. private static Map<String, Unit> type2Unit = new HashMap<String, Style.Unit>();
  1237. static {
  1238. for (Unit unit : Unit.values()) {
  1239. type2Unit.put(unit.getType(), unit);
  1240. }
  1241. }
  1242. /**
  1243. * Gets the unit value by its type.
  1244. *
  1245. * @param type
  1246. * the type of the unit as found in the style.
  1247. * @return the unit value.
  1248. */
  1249. public static Unit unitByType(String type) {
  1250. return type2Unit.get(type);
  1251. }
  1252. /*
  1253. * Regex to parse the size.
  1254. */
  1255. private static final RegExp sizePattern = RegExp
  1256. .compile(SharedUtil.SIZE_PATTERN);
  1257. /**
  1258. * Parse the size from string format to {@link CssSize}.
  1259. *
  1260. * @param s
  1261. * the size as string.
  1262. * @return a {@link CssSize} object.
  1263. */
  1264. public static CssSize fromString(String s) {
  1265. if (s == null) {
  1266. return null;
  1267. }
  1268. s = s.trim();
  1269. if ("".equals(s)) {
  1270. return null;
  1271. }
  1272. float size = 0;
  1273. Unit unit = null;
  1274. MatchResult matcher = sizePattern.exec(s);
  1275. if (matcher.getGroupCount() > 1) {
  1276. size = Float.parseFloat(matcher.getGroup(1));
  1277. if (size < 0) {
  1278. size = -1;
  1279. unit = Unit.PX;
  1280. } else {
  1281. String symbol = matcher.getGroup(2);
  1282. unit = unitByType(symbol);
  1283. }
  1284. } else {
  1285. throw new IllegalArgumentException("Invalid size argument: \""
  1286. + s + "\" (should match " + sizePattern.getSource()
  1287. + ")");
  1288. }
  1289. return new CssSize(size, unit);
  1290. }
  1291. /**
  1292. * Creates a {@link CssSize} using a value and its measurement unit.
  1293. *
  1294. * @param value
  1295. * the value.
  1296. * @param unit
  1297. * the unit.
  1298. * @return the {@link CssSize} object.
  1299. */
  1300. public static CssSize fromValueUnit(float value, Unit unit) {
  1301. return new CssSize(value, unit);
  1302. }
  1303. /*
  1304. * The value.
  1305. */
  1306. private final float value;
  1307. /*
  1308. * The measure unit.
  1309. */
  1310. private final Unit unit;
  1311. private CssSize(float value, Unit unit) {
  1312. this.value = value;
  1313. this.unit = unit;
  1314. }
  1315. /**
  1316. * Gets the value for this css size.
  1317. *
  1318. * @return the value.
  1319. */
  1320. public float getValue() {
  1321. return value;
  1322. }
  1323. /**
  1324. * Gets the measurement unit for this css size.
  1325. *
  1326. * @return the unit.
  1327. */
  1328. public Unit getUnit() {
  1329. return unit;
  1330. }
  1331. @Override
  1332. public String toString() {
  1333. return value + unit.getType();
  1334. }
  1335. @Override
  1336. public boolean equals(Object obj) {
  1337. if (obj instanceof CssSize) {
  1338. CssSize size = (CssSize) obj;
  1339. return size.value == value && size.unit == unit;
  1340. }
  1341. return false;
  1342. }
  1343. /**
  1344. * Check whether the two sizes are equals.
  1345. *
  1346. * @param cssSize1
  1347. * the first size to compare.
  1348. * @param cssSize2
  1349. * the other size to compare with the first one.
  1350. * @return true if the two sizes are equals, otherwise false.
  1351. */
  1352. public static boolean equals(String cssSize1, String cssSize2) {
  1353. return CssSize.fromString(cssSize1).equals(
  1354. CssSize.fromString(cssSize2));
  1355. }
  1356. }
  1357. private static Logger getLogger() {
  1358. return Logger.getLogger(WidgetUtil.class.getName());
  1359. }
  1360. /**
  1361. * Returns the thickness of the given element's top border.
  1362. * <p>
  1363. * The value is determined using computed style when available and
  1364. * calculated otherwise.
  1365. *
  1366. * @since 7.5.0
  1367. * @param element
  1368. * the element to measure
  1369. * @return the top border thickness
  1370. */
  1371. public static double getBorderTopThickness(Element element) {
  1372. return getBorderThickness(element, new String[] { "borderTopWidth" });
  1373. }
  1374. /**
  1375. * Returns the thickness of the given element's bottom border.
  1376. * <p>
  1377. * The value is determined using computed style when available and
  1378. * calculated otherwise.
  1379. *
  1380. * @since 7.5.0
  1381. * @param element
  1382. * the element to measure
  1383. * @return the bottom border thickness
  1384. */
  1385. public static double getBorderBottomThickness(Element element) {
  1386. return getBorderThickness(element, new String[] { "borderBottomWidth" });
  1387. }
  1388. /**
  1389. * Returns the combined thickness of the given element's top and bottom
  1390. * borders.
  1391. * <p>
  1392. * The value is determined using computed style when available and
  1393. * calculated otherwise.
  1394. *
  1395. * @since 7.5.0
  1396. * @param element
  1397. * the element to measure
  1398. * @return the top and bottom border thickness
  1399. */
  1400. public static double getBorderTopAndBottomThickness(Element element) {
  1401. return getBorderThickness(element, new String[] { "borderTopWidth",
  1402. "borderBottomWidth" });
  1403. }
  1404. /**
  1405. * Returns the thickness of the given element's left border.
  1406. * <p>
  1407. * The value is determined using computed style when available and
  1408. * calculated otherwise.
  1409. *
  1410. * @since 7.5.0
  1411. * @param element
  1412. * the element to measure
  1413. * @return the left border thickness
  1414. */
  1415. public static double getBorderLeftThickness(Element element) {
  1416. return getBorderThickness(element, new String[] { "borderLeftWidth" });
  1417. }
  1418. /**
  1419. * Returns the thickness of the given element's right border.
  1420. * <p>
  1421. * The value is determined using computed style when available and
  1422. * calculated otherwise.
  1423. *
  1424. * @since 7.5.0
  1425. * @param element
  1426. * the element to measure
  1427. * @return the right border thickness
  1428. */
  1429. public static double getBorderRightThickness(Element element) {
  1430. return getBorderThickness(element, new String[] { "borderRightWidth" });
  1431. }
  1432. /**
  1433. * Returns the thickness of the given element's left and right borders.
  1434. * <p>
  1435. * The value is determined using computed style when available and
  1436. * calculated otherwise.
  1437. *
  1438. * @since 7.5.0
  1439. * @param element
  1440. * the element to measure
  1441. * @return the top border thickness
  1442. */
  1443. public static double getBorderLeftAndRightThickness(Element element) {
  1444. return getBorderThickness(element, new String[] { "borderLeftWidth",
  1445. "borderRightWidth" });
  1446. }
  1447. private static native double getBorderThickness(
  1448. com.google.gwt.dom.client.Element element, String[] borderNames)
  1449. /*-{
  1450. if (typeof $wnd.getComputedStyle === 'function') {
  1451. var computedStyle = $wnd.getComputedStyle(element);
  1452. var width = 0;
  1453. for (i=0; i< borderNames.length; i++) {
  1454. var borderWidth = computedStyle[borderNames[i]];
  1455. width += parseFloat(borderWidth);
  1456. }
  1457. return width;
  1458. } else {
  1459. var parentElement = element.offsetParent;
  1460. var cloneElement = element.cloneNode(false);
  1461. cloneElement.style.boxSizing ="content-box";
  1462. parentElement.appendChild(cloneElement);
  1463. cloneElement.style.height = "10px"; // IE8 wants the height to be set to something...
  1464. var heightWithBorder = cloneElement.offsetHeight;
  1465. for (i=0; i< borderNames.length; i++) {
  1466. cloneElement.style[borderNames[i]] = "0";
  1467. }
  1468. var heightWithoutBorder = cloneElement.offsetHeight;
  1469. parentElement.removeChild(cloneElement);
  1470. return heightWithBorder - heightWithoutBorder;
  1471. }
  1472. }-*/;
  1473. /**
  1474. * Rounds the given size up to a value which the browser will accept.
  1475. *
  1476. * Safari/WebKit uses 1/64th of a pixel to enable using integer math
  1477. * (http://trac.webkit.org/wiki/LayoutUnit).
  1478. *
  1479. * Firefox uses 1/60th of a pixel because it is divisible by three
  1480. * (https://bugzilla.mozilla.org/show_bug.cgi?id=1070940)
  1481. *
  1482. * @since 7.5.1
  1483. * @param size
  1484. * the value to round
  1485. * @return the rounded value
  1486. */
  1487. public static double roundSizeUp(double size) {
  1488. return roundSize(size, true);
  1489. }
  1490. /**
  1491. * Rounds the given size down to a value which the browser will accept.
  1492. *
  1493. * Safari/WebKit uses 1/64th of a pixel to enable using integer math
  1494. * (http://trac.webkit.org/wiki/LayoutUnit).
  1495. *
  1496. * Firefox uses 1/60th of a pixel because it is divisible by three
  1497. * (https://bugzilla.mozilla.org/show_bug.cgi?id=1070940)
  1498. *
  1499. * IE9+ uses 1/100th of a pixel
  1500. *
  1501. * @since 7.5.1
  1502. * @param size
  1503. * the value to round
  1504. * @return the rounded value
  1505. */
  1506. public static double roundSizeDown(double size) {
  1507. return roundSize(size, false);
  1508. }
  1509. private static double roundSize(double size, boolean roundUp) {
  1510. if (BrowserInfo.get().isIE8()) {
  1511. if (roundUp) {
  1512. return Math.ceil(size);
  1513. } else {
  1514. return (int) size;
  1515. }
  1516. }
  1517. double factor = getSubPixelRoundingFactor();
  1518. if (factor < 0 || size < 0) {
  1519. return size;
  1520. }
  1521. if (roundUp) {
  1522. return roundSizeUp(size, factor);
  1523. } else {
  1524. return roundSizeDown(size, factor);
  1525. }
  1526. }
  1527. /**
  1528. * Returns the factor used by browsers to round subpixel values
  1529. *
  1530. * @since 7.5.1
  1531. * @return the factor N used by the browser when storing subpixels as X+Y/N
  1532. */
  1533. private static double getSubPixelRoundingFactor() {
  1534. // Detects how the browser does subpixel rounding
  1535. // Currently Firefox uses 1/60th pixels
  1536. // and Safari uses 1/64th pixels
  1537. // IE 1/100th pixels
  1538. if (detectedSubPixelRoundingFactor != -1) {
  1539. return detectedSubPixelRoundingFactor;
  1540. }
  1541. double probeSize = 0.999999;
  1542. DivElement div = Document.get().createDivElement();
  1543. Document.get().getBody().appendChild(div);
  1544. div.getStyle().setHeight(probeSize, Unit.PX);
  1545. ComputedStyle computedStyle = new ComputedStyle(div);
  1546. double computedHeight = computedStyle.getHeight();
  1547. if (computedHeight < probeSize) {
  1548. // Rounded down by browser, all browsers but Firefox do this
  1549. // today
  1550. detectedSubPixelRoundingFactor = (int) Math
  1551. .round(1.0 / (1.0 - computedHeight));
  1552. } else {
  1553. // Rounded up / to nearest by browser
  1554. probeSize = 1;
  1555. while (computedStyle.getHeight() != 0.0) {
  1556. computedHeight = computedStyle.getHeight();
  1557. probeSize /= 2.0;
  1558. div.getStyle().setHeight(probeSize, Unit.PX);
  1559. }
  1560. detectedSubPixelRoundingFactor = (int) Math
  1561. .round(1.0 / computedHeight);
  1562. }
  1563. div.removeFromParent();
  1564. return detectedSubPixelRoundingFactor;
  1565. }
  1566. private static double roundSizeUp(double size, double divisor) {
  1567. // In: 12.51, 60.0
  1568. // 12
  1569. double integerPart = (int) size;
  1570. // (12.51 - 12) * 60 = 30.6
  1571. double nrFractions = (size - integerPart) * divisor;
  1572. // 12 + ceil(30.6) / 60 = 12 + 31/60 = 12.51666
  1573. return integerPart + (Math.ceil(nrFractions)) / divisor;
  1574. }
  1575. private static double roundSizeDown(double size, double divisor) {
  1576. // In: 12.51, 60.0
  1577. // 12
  1578. double integerPart = (int) size;
  1579. // (12.51 - 12) * 60 = 30.6
  1580. double nrFractions = (size - integerPart) * divisor;
  1581. // 12 + int(30.6) / 60 = 12 + 30/60 = 12.5
  1582. return integerPart + ((int) nrFractions) / divisor;
  1583. }
  1584. }