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

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