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.

WidgetUtil.java 52KB

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