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.

VCaption.java 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. /*
  2. * Copyright 2011 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.terminal.gwt.client;
  17. import com.google.gwt.user.client.DOM;
  18. import com.google.gwt.user.client.Element;
  19. import com.google.gwt.user.client.Event;
  20. import com.google.gwt.user.client.ui.HTML;
  21. import com.vaadin.shared.AbstractFieldState;
  22. import com.vaadin.shared.ComponentState;
  23. import com.vaadin.terminal.gwt.client.ui.AbstractFieldConnector;
  24. import com.vaadin.terminal.gwt.client.ui.Icon;
  25. public class VCaption extends HTML {
  26. public static final String CLASSNAME = "v-caption";
  27. private final ComponentConnector owner;
  28. private Element errorIndicatorElement;
  29. private Element requiredFieldIndicator;
  30. private Icon icon;
  31. private Element captionText;
  32. private final ApplicationConnection client;
  33. private boolean placedAfterComponent = false;
  34. private int maxWidth = -1;
  35. private enum InsertPosition {
  36. ICON, CAPTION, REQUIRED, ERROR
  37. }
  38. private TooltipInfo tooltipInfo = null;
  39. /**
  40. * Creates a caption that is not linked to a {@link ComponentConnector}.
  41. *
  42. * When using this constructor, {@link #getOwner()} returns null.
  43. *
  44. * @param client
  45. * ApplicationConnection
  46. * @deprecated all captions should be associated with a paintable widget and
  47. * be updated from shared state, not UIDL
  48. */
  49. @Deprecated
  50. public VCaption(ApplicationConnection client) {
  51. super();
  52. this.client = client;
  53. owner = null;
  54. setStyleName(CLASSNAME);
  55. sinkEvents(VTooltip.TOOLTIP_EVENTS);
  56. }
  57. /**
  58. * Creates a caption for a {@link ComponentConnector}.
  59. *
  60. * @param component
  61. * owner of caption, not null
  62. * @param client
  63. * ApplicationConnection
  64. */
  65. public VCaption(ComponentConnector component, ApplicationConnection client) {
  66. super();
  67. this.client = client;
  68. owner = component;
  69. if (client != null && owner != null) {
  70. setOwnerPid(getElement(), owner.getConnectorId());
  71. }
  72. setStyleName(CLASSNAME);
  73. }
  74. /**
  75. * Updates the caption from UIDL.
  76. *
  77. * This method may only be called when the caption has an owner - otherwise,
  78. * use {@link #updateCaptionWithoutOwner(UIDL, String, boolean, boolean)}.
  79. *
  80. * @return true if the position where the caption should be placed has
  81. * changed
  82. */
  83. public boolean updateCaption() {
  84. boolean wasPlacedAfterComponent = placedAfterComponent;
  85. // Caption is placed after component unless there is some part which
  86. // moves it above.
  87. placedAfterComponent = true;
  88. String style = CLASSNAME;
  89. if (owner.getState().hasStyles()) {
  90. for (String customStyle : owner.getState().getStyles()) {
  91. style += " " + CLASSNAME + "-" + customStyle;
  92. }
  93. }
  94. if (!owner.isEnabled()) {
  95. style += " " + ApplicationConnection.DISABLED_CLASSNAME;
  96. }
  97. setStyleName(style);
  98. boolean hasIcon = owner.getState().getIcon() != null;
  99. boolean showRequired = false;
  100. boolean showError = owner.getState().getErrorMessage() != null;
  101. if (owner.getState() instanceof AbstractFieldState) {
  102. AbstractFieldState abstractFieldState = (AbstractFieldState) owner
  103. .getState();
  104. showError = showError && !abstractFieldState.isHideErrors();
  105. }
  106. if (owner instanceof AbstractFieldConnector) {
  107. showRequired = ((AbstractFieldConnector) owner).isRequired();
  108. }
  109. if (hasIcon) {
  110. if (icon == null) {
  111. icon = new Icon(client);
  112. icon.setWidth("0");
  113. icon.setHeight("0");
  114. DOM.insertChild(getElement(), icon.getElement(),
  115. getInsertPosition(InsertPosition.ICON));
  116. }
  117. // Icon forces the caption to be above the component
  118. placedAfterComponent = false;
  119. icon.setUri(owner.getState().getIcon().getURL());
  120. } else if (icon != null) {
  121. // Remove existing
  122. DOM.removeChild(getElement(), icon.getElement());
  123. icon = null;
  124. }
  125. if (owner.getState().getCaption() != null) {
  126. // A caption text should be shown if the attribute is set
  127. // If the caption is null the ATTRIBUTE_CAPTION should not be set to
  128. // avoid ending up here.
  129. if (captionText == null) {
  130. captionText = DOM.createDiv();
  131. captionText.setClassName("v-captiontext");
  132. DOM.insertChild(getElement(), captionText,
  133. getInsertPosition(InsertPosition.CAPTION));
  134. }
  135. // Update caption text
  136. String c = owner.getState().getCaption();
  137. // A text forces the caption to be above the component.
  138. placedAfterComponent = false;
  139. if (c == null || c.trim().equals("")) {
  140. // Not sure if c even can be null. Should not.
  141. // This is required to ensure that the caption uses space in all
  142. // browsers when it is set to the empty string. If there is an
  143. // icon, error indicator or required indicator they will ensure
  144. // that space is reserved.
  145. if (!hasIcon && !showRequired && !showError) {
  146. captionText.setInnerHTML(" ");
  147. }
  148. } else {
  149. DOM.setInnerText(captionText, c);
  150. }
  151. } else if (captionText != null) {
  152. // Remove existing
  153. DOM.removeChild(getElement(), captionText);
  154. captionText = null;
  155. }
  156. if (owner.getState().hasDescription() && captionText != null) {
  157. addStyleDependentName("hasdescription");
  158. } else {
  159. removeStyleDependentName("hasdescription");
  160. }
  161. if (showRequired) {
  162. if (requiredFieldIndicator == null) {
  163. requiredFieldIndicator = DOM.createDiv();
  164. requiredFieldIndicator
  165. .setClassName("v-required-field-indicator");
  166. DOM.setInnerText(requiredFieldIndicator, "*");
  167. DOM.insertChild(getElement(), requiredFieldIndicator,
  168. getInsertPosition(InsertPosition.REQUIRED));
  169. }
  170. } else if (requiredFieldIndicator != null) {
  171. // Remove existing
  172. DOM.removeChild(getElement(), requiredFieldIndicator);
  173. requiredFieldIndicator = null;
  174. }
  175. if (showError) {
  176. if (errorIndicatorElement == null) {
  177. errorIndicatorElement = DOM.createDiv();
  178. DOM.setInnerHTML(errorIndicatorElement, " ");
  179. DOM.setElementProperty(errorIndicatorElement, "className",
  180. "v-errorindicator");
  181. DOM.insertChild(getElement(), errorIndicatorElement,
  182. getInsertPosition(InsertPosition.ERROR));
  183. }
  184. } else if (errorIndicatorElement != null) {
  185. // Remove existing
  186. getElement().removeChild(errorIndicatorElement);
  187. errorIndicatorElement = null;
  188. }
  189. return (wasPlacedAfterComponent != placedAfterComponent);
  190. }
  191. private int getInsertPosition(InsertPosition element) {
  192. int pos = 0;
  193. if (InsertPosition.ICON.equals(element)) {
  194. return pos;
  195. }
  196. if (icon != null) {
  197. pos++;
  198. }
  199. if (InsertPosition.CAPTION.equals(element)) {
  200. return pos;
  201. }
  202. if (captionText != null) {
  203. pos++;
  204. }
  205. if (InsertPosition.REQUIRED.equals(element)) {
  206. return pos;
  207. }
  208. if (requiredFieldIndicator != null) {
  209. pos++;
  210. }
  211. // if (InsertPosition.ERROR.equals(element)) {
  212. // }
  213. return pos;
  214. }
  215. @Deprecated
  216. public boolean updateCaptionWithoutOwner(String caption, boolean disabled,
  217. boolean hasDescription, boolean hasError, String iconURL) {
  218. boolean wasPlacedAfterComponent = placedAfterComponent;
  219. // Caption is placed after component unless there is some part which
  220. // moves it above.
  221. placedAfterComponent = true;
  222. String style = VCaption.CLASSNAME;
  223. if (disabled) {
  224. style += " " + ApplicationConnection.DISABLED_CLASSNAME;
  225. }
  226. setStyleName(style);
  227. if (hasDescription) {
  228. if (captionText != null) {
  229. addStyleDependentName("hasdescription");
  230. } else {
  231. removeStyleDependentName("hasdescription");
  232. }
  233. }
  234. boolean hasIcon = iconURL != null;
  235. if (hasIcon) {
  236. if (icon == null) {
  237. icon = new Icon(client);
  238. icon.setWidth("0");
  239. icon.setHeight("0");
  240. DOM.insertChild(getElement(), icon.getElement(),
  241. getInsertPosition(InsertPosition.ICON));
  242. }
  243. // Icon forces the caption to be above the component
  244. placedAfterComponent = false;
  245. icon.setUri(iconURL);
  246. } else if (icon != null) {
  247. // Remove existing
  248. DOM.removeChild(getElement(), icon.getElement());
  249. icon = null;
  250. }
  251. if (caption != null) {
  252. // A caption text should be shown if the attribute is set
  253. // If the caption is null the ATTRIBUTE_CAPTION should not be set to
  254. // avoid ending up here.
  255. if (captionText == null) {
  256. captionText = DOM.createDiv();
  257. captionText.setClassName("v-captiontext");
  258. DOM.insertChild(getElement(), captionText,
  259. getInsertPosition(InsertPosition.CAPTION));
  260. }
  261. // Update caption text
  262. // A text forces the caption to be above the component.
  263. placedAfterComponent = false;
  264. if (caption.trim().equals("")) {
  265. // This is required to ensure that the caption uses space in all
  266. // browsers when it is set to the empty string. If there is an
  267. // icon, error indicator or required indicator they will ensure
  268. // that space is reserved.
  269. if (!hasIcon && !hasError) {
  270. captionText.setInnerHTML(" ");
  271. }
  272. } else {
  273. DOM.setInnerText(captionText, caption);
  274. }
  275. } else if (captionText != null) {
  276. // Remove existing
  277. DOM.removeChild(getElement(), captionText);
  278. captionText = null;
  279. }
  280. if (hasError) {
  281. if (errorIndicatorElement == null) {
  282. errorIndicatorElement = DOM.createDiv();
  283. DOM.setInnerHTML(errorIndicatorElement, " ");
  284. DOM.setElementProperty(errorIndicatorElement, "className",
  285. "v-errorindicator");
  286. DOM.insertChild(getElement(), errorIndicatorElement,
  287. getInsertPosition(InsertPosition.ERROR));
  288. }
  289. } else if (errorIndicatorElement != null) {
  290. // Remove existing
  291. getElement().removeChild(errorIndicatorElement);
  292. errorIndicatorElement = null;
  293. }
  294. return (wasPlacedAfterComponent != placedAfterComponent);
  295. }
  296. @Override
  297. public void onBrowserEvent(Event event) {
  298. super.onBrowserEvent(event);
  299. final Element target = DOM.eventGetTarget(event);
  300. if (DOM.eventGetType(event) == Event.ONLOAD
  301. && icon.getElement() == target) {
  302. icon.setWidth("");
  303. icon.setHeight("");
  304. // if max width defined, recalculate
  305. if (maxWidth != -1) {
  306. setMaxWidth(maxWidth);
  307. } else {
  308. String width = getElement().getStyle().getProperty("width");
  309. if (width != null && !width.equals("")) {
  310. setWidth(getRequiredWidth() + "px");
  311. }
  312. }
  313. /*
  314. * The size of the icon might affect the size of the component so we
  315. * must report the size change to the parent TODO consider moving
  316. * the responsibility of reacting to ONLOAD from VCaption to layouts
  317. */
  318. if (owner != null) {
  319. Util.notifyParentOfSizeChange(owner.getWidget(), true);
  320. } else {
  321. VConsole.log("Warning: Icon load event was not propagated because VCaption owner is unknown.");
  322. }
  323. }
  324. }
  325. public static boolean isNeeded(ComponentState state) {
  326. if (state.getCaption() != null) {
  327. return true;
  328. }
  329. if (state.getIcon() != null) {
  330. return true;
  331. }
  332. if (state.getErrorMessage() != null) {
  333. return true;
  334. }
  335. return false;
  336. }
  337. /**
  338. * Returns Paintable for which this Caption belongs to.
  339. *
  340. * @return owner Widget
  341. */
  342. public ComponentConnector getOwner() {
  343. return owner;
  344. }
  345. public boolean shouldBePlacedAfterComponent() {
  346. return placedAfterComponent;
  347. }
  348. public int getRenderedWidth() {
  349. int width = 0;
  350. if (icon != null) {
  351. width += Util.getRequiredWidth(icon.getElement());
  352. }
  353. if (captionText != null) {
  354. width += Util.getRequiredWidth(captionText);
  355. }
  356. if (requiredFieldIndicator != null) {
  357. width += Util.getRequiredWidth(requiredFieldIndicator);
  358. }
  359. if (errorIndicatorElement != null) {
  360. width += Util.getRequiredWidth(errorIndicatorElement);
  361. }
  362. return width;
  363. }
  364. public int getRequiredWidth() {
  365. int width = 0;
  366. if (icon != null) {
  367. width += Util.getRequiredWidth(icon.getElement());
  368. }
  369. if (captionText != null) {
  370. int textWidth = captionText.getScrollWidth();
  371. if (BrowserInfo.get().isFirefox()) {
  372. /*
  373. * In Firefox3 the caption might require more space than the
  374. * scrollWidth returns as scrollWidth is rounded down.
  375. */
  376. int requiredWidth = Util.getRequiredWidth(captionText);
  377. if (requiredWidth > textWidth) {
  378. textWidth = requiredWidth;
  379. }
  380. }
  381. width += textWidth;
  382. }
  383. if (requiredFieldIndicator != null) {
  384. width += Util.getRequiredWidth(requiredFieldIndicator);
  385. }
  386. if (errorIndicatorElement != null) {
  387. width += Util.getRequiredWidth(errorIndicatorElement);
  388. }
  389. return width;
  390. }
  391. public int getHeight() {
  392. int height = 0;
  393. int h;
  394. if (icon != null) {
  395. h = Util.getRequiredHeight(icon.getElement());
  396. if (h > height) {
  397. height = h;
  398. }
  399. }
  400. if (captionText != null) {
  401. h = Util.getRequiredHeight(captionText);
  402. if (h > height) {
  403. height = h;
  404. }
  405. }
  406. if (requiredFieldIndicator != null) {
  407. h = Util.getRequiredHeight(requiredFieldIndicator);
  408. if (h > height) {
  409. height = h;
  410. }
  411. }
  412. if (errorIndicatorElement != null) {
  413. h = Util.getRequiredHeight(errorIndicatorElement);
  414. if (h > height) {
  415. height = h;
  416. }
  417. }
  418. return height;
  419. }
  420. public void setAlignment(String alignment) {
  421. DOM.setStyleAttribute(getElement(), "textAlign", alignment);
  422. }
  423. public void setMaxWidth(int maxWidth) {
  424. this.maxWidth = maxWidth;
  425. DOM.setStyleAttribute(getElement(), "width", maxWidth + "px");
  426. if (icon != null) {
  427. DOM.setStyleAttribute(icon.getElement(), "width", "");
  428. }
  429. if (captionText != null) {
  430. DOM.setStyleAttribute(captionText, "width", "");
  431. }
  432. int requiredWidth = getRequiredWidth();
  433. /*
  434. * ApplicationConnection.getConsole().log( "Caption maxWidth: " +
  435. * maxWidth + ", requiredWidth: " + requiredWidth);
  436. */
  437. if (requiredWidth > maxWidth) {
  438. // Needs to truncate and clip
  439. int availableWidth = maxWidth;
  440. // DOM.setStyleAttribute(getElement(), "width", maxWidth + "px");
  441. if (requiredFieldIndicator != null) {
  442. availableWidth -= Util.getRequiredWidth(requiredFieldIndicator);
  443. }
  444. if (errorIndicatorElement != null) {
  445. availableWidth -= Util.getRequiredWidth(errorIndicatorElement);
  446. }
  447. if (availableWidth < 0) {
  448. availableWidth = 0;
  449. }
  450. if (icon != null) {
  451. int iconRequiredWidth = Util
  452. .getRequiredWidth(icon.getElement());
  453. if (availableWidth > iconRequiredWidth) {
  454. availableWidth -= iconRequiredWidth;
  455. } else {
  456. DOM.setStyleAttribute(icon.getElement(), "width",
  457. availableWidth + "px");
  458. availableWidth = 0;
  459. }
  460. }
  461. if (captionText != null) {
  462. int captionWidth = Util.getRequiredWidth(captionText);
  463. if (availableWidth > captionWidth) {
  464. availableWidth -= captionWidth;
  465. } else {
  466. DOM.setStyleAttribute(captionText, "width", availableWidth
  467. + "px");
  468. availableWidth = 0;
  469. }
  470. }
  471. }
  472. }
  473. /**
  474. * Sets the tooltip that should be shown for the caption
  475. *
  476. * @param tooltipInfo
  477. * The tooltip that should be shown or null if no tooltip should
  478. * be shown
  479. */
  480. public void setTooltipInfo(TooltipInfo tooltipInfo) {
  481. this.tooltipInfo = tooltipInfo;
  482. }
  483. /**
  484. * Returns the tooltip that should be shown for the caption
  485. *
  486. * @return The tooltip to show or null if no tooltip should be shown
  487. */
  488. public TooltipInfo getTooltipInfo() {
  489. return tooltipInfo;
  490. }
  491. protected Element getTextElement() {
  492. return captionText;
  493. }
  494. public static String getCaptionOwnerPid(Element e) {
  495. return getOwnerPid(e);
  496. }
  497. private native static void setOwnerPid(Element el, String pid)
  498. /*-{
  499. el.vOwnerPid = pid;
  500. }-*/;
  501. public native static String getOwnerPid(Element el)
  502. /*-{
  503. return el.vOwnerPid;
  504. }-*/;
  505. }