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.

Label.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  1. /*
  2. * Copyright 2000-2016 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.ui;
  17. import java.lang.reflect.Method;
  18. import java.util.Collection;
  19. import java.util.Locale;
  20. import org.jsoup.nodes.Element;
  21. import com.vaadin.shared.ui.label.ContentMode;
  22. import com.vaadin.shared.ui.label.LabelState;
  23. import com.vaadin.shared.util.SharedUtil;
  24. import com.vaadin.ui.declarative.DesignContext;
  25. import com.vaadin.ui.declarative.DesignFormatter;
  26. import com.vaadin.v7.data.Property;
  27. import com.vaadin.v7.data.util.converter.Converter;
  28. import com.vaadin.v7.data.util.converter.ConverterUtil;
  29. /**
  30. * Label component for showing non-editable short texts.
  31. *
  32. * The label content can be set to the modes specified by {@link ContentMode}
  33. *
  34. * <p>
  35. * The contents of the label may contain simple formatting:
  36. * <ul>
  37. * <li><b>&lt;b></b> Bold
  38. * <li><b>&lt;i></b> Italic
  39. * <li><b>&lt;u></b> Underlined
  40. * <li><b>&lt;br/></b> Linebreak
  41. * <li><b>&lt;ul>&lt;li>item 1&lt;/li>&lt;li>item 2&lt;/li>&lt;/ul></b> List of
  42. * items
  43. * </ul>
  44. * The <b>b</b>,<b>i</b>,<b>u</b> and <b>li</b> tags can contain all the tags in
  45. * the list recursively.
  46. * </p>
  47. *
  48. * @author Vaadin Ltd.
  49. * @since 3.0
  50. */
  51. @SuppressWarnings("serial")
  52. public class Label extends AbstractComponent implements Property<String>,
  53. Property.Viewer, Property.ValueChangeListener,
  54. Property.ValueChangeNotifier, Comparable<Label> {
  55. /**
  56. * @deprecated As of 7.0, use {@link ContentMode#TEXT} instead
  57. */
  58. @Deprecated
  59. public static final ContentMode CONTENT_TEXT = ContentMode.TEXT;
  60. /**
  61. * @deprecated As of 7.0, use {@link ContentMode#PREFORMATTED} instead
  62. */
  63. @Deprecated
  64. public static final ContentMode CONTENT_PREFORMATTED = ContentMode.PREFORMATTED;
  65. /**
  66. * @deprecated As of 7.0, use {@link ContentMode#HTML} instead
  67. */
  68. @Deprecated
  69. public static final ContentMode CONTENT_XHTML = ContentMode.HTML;
  70. /**
  71. * @deprecated As of 7.0, use {@link ContentMode#XML} instead
  72. */
  73. @Deprecated
  74. public static final ContentMode CONTENT_XML = ContentMode.XML;
  75. /**
  76. * @deprecated As of 7.0, use {@link ContentMode#RAW} instead
  77. */
  78. @Deprecated
  79. public static final ContentMode CONTENT_RAW = ContentMode.RAW;
  80. /**
  81. * @deprecated As of 7.0, use {@link ContentMode#TEXT} instead
  82. */
  83. @Deprecated
  84. public static final ContentMode CONTENT_DEFAULT = ContentMode.TEXT;
  85. /**
  86. * A converter used to convert from the data model type to the field type
  87. * and vice versa. Label type is always String.
  88. */
  89. private Converter<String, Object> converter = null;
  90. private Property<String> dataSource = null;
  91. /**
  92. * Creates an empty Label.
  93. */
  94. public Label() {
  95. this("");
  96. }
  97. /**
  98. * Creates a new instance of Label with text-contents.
  99. *
  100. * @param content
  101. */
  102. public Label(String content) {
  103. this(content, ContentMode.TEXT);
  104. }
  105. /**
  106. * Creates a new instance of Label with text-contents read from given
  107. * datasource.
  108. *
  109. * @param contentSource
  110. */
  111. public Label(Property contentSource) {
  112. this(contentSource, ContentMode.TEXT);
  113. }
  114. /**
  115. * Creates a new instance of Label with text-contents.
  116. *
  117. * @param content
  118. * @param contentMode
  119. */
  120. public Label(String content, ContentMode contentMode) {
  121. setValue(content);
  122. setContentMode(contentMode);
  123. setWidth(100, Unit.PERCENTAGE);
  124. }
  125. /**
  126. * Creates a new instance of Label with text-contents read from given
  127. * datasource.
  128. *
  129. * @param contentSource
  130. * @param contentMode
  131. */
  132. public Label(Property contentSource, ContentMode contentMode) {
  133. setPropertyDataSource(contentSource);
  134. setContentMode(contentMode);
  135. setWidth(100, Unit.PERCENTAGE);
  136. }
  137. @Override
  138. protected LabelState getState() {
  139. return (LabelState) super.getState();
  140. }
  141. @Override
  142. protected LabelState getState(boolean markAsDirty) {
  143. return (LabelState) super.getState(markAsDirty);
  144. }
  145. /**
  146. * Gets the value of the label.
  147. * <p>
  148. * The value of the label is the text that is shown to the end user.
  149. * Depending on the {@link ContentMode} it is plain text or markup.
  150. * </p>
  151. *
  152. * @return the value of the label.
  153. */
  154. @Override
  155. public String getValue() {
  156. if (getPropertyDataSource() == null) {
  157. // Use internal value if we are running without a data source
  158. return getState(false).text;
  159. }
  160. return getDataSourceValue();
  161. }
  162. /**
  163. * Returns the current value of the data source converted using the current
  164. * locale.
  165. *
  166. * @return
  167. */
  168. private String getDataSourceValue() {
  169. return ConverterUtil.convertFromModel(
  170. getPropertyDataSource().getValue(), String.class,
  171. getConverter(), getLocale());
  172. }
  173. /**
  174. * Set the value of the label. Value of the label is the XML contents of the
  175. * label. Since Vaadin 7.2, changing the value of Label instance with that
  176. * method will fire ValueChangeEvent.
  177. *
  178. * @param newStringValue
  179. * the New value of the label.
  180. */
  181. @Override
  182. public void setValue(String newStringValue) {
  183. if (getPropertyDataSource() == null) {
  184. LabelState state = getState(false);
  185. String oldTextValue = state.text;
  186. if (!SharedUtil.equals(oldTextValue, newStringValue)) {
  187. getState().text = newStringValue;
  188. fireValueChange();
  189. }
  190. } else {
  191. throw new IllegalStateException(
  192. "Label is only a Property.Viewer and cannot update its data source");
  193. }
  194. }
  195. /**
  196. * Gets the type of the Property.
  197. *
  198. * @see com.vaadin.v7.data.Property#getType()
  199. */
  200. @Override
  201. public Class<String> getType() {
  202. return String.class;
  203. }
  204. /**
  205. * Gets the viewing data-source property.
  206. *
  207. * @return the data source property.
  208. * @see com.vaadin.v7.data.Property.Viewer#getPropertyDataSource()
  209. */
  210. @Override
  211. public Property getPropertyDataSource() {
  212. return dataSource;
  213. }
  214. /**
  215. * Sets the property as data-source for viewing. Since Vaadin 7.2 a
  216. * ValueChangeEvent is fired if the new value is different from previous.
  217. *
  218. * @param newDataSource
  219. * the new data source Property
  220. * @see com.vaadin.v7.data.Property.Viewer#setPropertyDataSource(com.vaadin.v7.data.Property)
  221. */
  222. @Override
  223. public void setPropertyDataSource(Property newDataSource) {
  224. // Stops listening the old data source changes
  225. if (dataSource != null && Property.ValueChangeNotifier.class
  226. .isAssignableFrom(dataSource.getClass())) {
  227. ((Property.ValueChangeNotifier) dataSource).removeListener(this);
  228. }
  229. // Check if the current converter is compatible.
  230. if (newDataSource != null
  231. && !ConverterUtil.canConverterPossiblyHandle(
  232. getConverter(), getType(), newDataSource.getType())) {
  233. // There is no converter set or there is no way the current
  234. // converter can be compatible.
  235. Converter<String, ?> c = ConverterUtil.getConverter(
  236. String.class, newDataSource.getType(), getSession());
  237. setConverter(c);
  238. }
  239. dataSource = newDataSource;
  240. if (dataSource != null) {
  241. // Update the value from the data source. If data source was set to
  242. // null, retain the old value
  243. updateValueFromDataSource();
  244. }
  245. // Listens the new data source if possible
  246. if (dataSource != null && Property.ValueChangeNotifier.class
  247. .isAssignableFrom(dataSource.getClass())) {
  248. ((Property.ValueChangeNotifier) dataSource).addListener(this);
  249. }
  250. markAsDirty();
  251. }
  252. /**
  253. * Gets the content mode of the Label.
  254. *
  255. * @return the Content mode of the label.
  256. *
  257. * @see ContentMode
  258. */
  259. public ContentMode getContentMode() {
  260. return getState(false).contentMode;
  261. }
  262. /**
  263. * Sets the content mode of the Label.
  264. *
  265. * @param contentMode
  266. * the New content mode of the label.
  267. *
  268. * @see ContentMode
  269. */
  270. public void setContentMode(ContentMode contentMode) {
  271. if (contentMode == null) {
  272. throw new IllegalArgumentException("Content mode can not be null");
  273. }
  274. getState().contentMode = contentMode;
  275. }
  276. /* Value change events */
  277. private static final Method VALUE_CHANGE_METHOD;
  278. static {
  279. try {
  280. VALUE_CHANGE_METHOD = Property.ValueChangeListener.class
  281. .getDeclaredMethod("valueChange",
  282. new Class[] { Property.ValueChangeEvent.class });
  283. } catch (final java.lang.NoSuchMethodException e) {
  284. // This should never happen
  285. throw new java.lang.RuntimeException(
  286. "Internal error finding methods in Label");
  287. }
  288. }
  289. /**
  290. * Value change event
  291. *
  292. * @author Vaadin Ltd.
  293. * @since 3.0
  294. */
  295. public static class ValueChangeEvent extends Component.Event
  296. implements Property.ValueChangeEvent {
  297. /**
  298. * New instance of text change event
  299. *
  300. * @param source
  301. * the Source of the event.
  302. */
  303. public ValueChangeEvent(Label source) {
  304. super(source);
  305. }
  306. /**
  307. * Gets the Property that has been modified.
  308. *
  309. * @see com.vaadin.v7.data.Property.ValueChangeEvent#getProperty()
  310. */
  311. @Override
  312. public Property getProperty() {
  313. return (Property) getSource();
  314. }
  315. }
  316. /**
  317. * Adds the value change listener.
  318. *
  319. * @param listener
  320. * the Listener to be added.
  321. * @see com.vaadin.v7.data.Property.ValueChangeNotifier#addListener(com.vaadin.v7.data.Property.ValueChangeListener)
  322. */
  323. @Override
  324. public void addValueChangeListener(Property.ValueChangeListener listener) {
  325. addListener(Label.ValueChangeEvent.class, listener,
  326. VALUE_CHANGE_METHOD);
  327. }
  328. /**
  329. * @deprecated As of 7.0, replaced by
  330. * {@link #addValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)}
  331. **/
  332. @Override
  333. @Deprecated
  334. public void addListener(Property.ValueChangeListener listener) {
  335. addValueChangeListener(listener);
  336. }
  337. /**
  338. * Removes the value change listener.
  339. *
  340. * @param listener
  341. * the Listener to be removed.
  342. * @see com.vaadin.v7.data.Property.ValueChangeNotifier#removeListener(com.vaadin.v7.data.Property.ValueChangeListener)
  343. */
  344. @Override
  345. public void removeValueChangeListener(
  346. Property.ValueChangeListener listener) {
  347. removeListener(Label.ValueChangeEvent.class, listener,
  348. VALUE_CHANGE_METHOD);
  349. }
  350. /**
  351. * @deprecated As of 7.0, replaced by
  352. * {@link #removeValueChangeListener(com.vaadin.v7.data.Property.ValueChangeListener)}
  353. **/
  354. @Override
  355. @Deprecated
  356. public void removeListener(Property.ValueChangeListener listener) {
  357. removeValueChangeListener(listener);
  358. }
  359. /**
  360. * Emits the options change event.
  361. */
  362. protected void fireValueChange() {
  363. // Set the error message
  364. fireEvent(new Label.ValueChangeEvent(this));
  365. }
  366. /**
  367. * Listens the value change events from data source.
  368. *
  369. * @see com.vaadin.v7.data.Property.ValueChangeListener#valueChange(Property.ValueChangeEvent)
  370. */
  371. @Override
  372. public void valueChange(Property.ValueChangeEvent event) {
  373. updateValueFromDataSource();
  374. }
  375. private void updateValueFromDataSource() {
  376. // Update the internal value from the data source
  377. String newConvertedValue = getDataSourceValue();
  378. if (!SharedUtil.equals(newConvertedValue, getState(false).text)) {
  379. getState().text = newConvertedValue;
  380. fireValueChange();
  381. }
  382. }
  383. @Override
  384. public void attach() {
  385. super.attach();
  386. localeMightHaveChanged();
  387. }
  388. @Override
  389. public void setLocale(Locale locale) {
  390. super.setLocale(locale);
  391. localeMightHaveChanged();
  392. }
  393. private void localeMightHaveChanged() {
  394. if (getPropertyDataSource() != null) {
  395. updateValueFromDataSource();
  396. }
  397. }
  398. private String getComparableValue() {
  399. String stringValue = getValue();
  400. if (stringValue == null) {
  401. stringValue = "";
  402. }
  403. if (getContentMode() == ContentMode.HTML
  404. || getContentMode() == ContentMode.XML) {
  405. return stripTags(stringValue);
  406. } else {
  407. return stringValue;
  408. }
  409. }
  410. /**
  411. * Compares the Label to other objects.
  412. *
  413. * <p>
  414. * Labels can be compared to other labels for sorting label contents. This
  415. * is especially handy for sorting table columns.
  416. * </p>
  417. *
  418. * <p>
  419. * In RAW, PREFORMATTED and TEXT modes, the label contents are compared as
  420. * is. In XML, UIDL and HTML modes, only CDATA is compared and tags ignored.
  421. * If the other object is not a Label, its toString() return value is used
  422. * in comparison.
  423. * </p>
  424. *
  425. * @param other
  426. * the Other object to compare to.
  427. * @return a negative integer, zero, or a positive integer as this object is
  428. * less than, equal to, or greater than the specified object.
  429. * @see java.lang.Comparable#compareTo(java.lang.Object)
  430. */
  431. @Override
  432. public int compareTo(Label other) {
  433. String thisValue = getComparableValue();
  434. String otherValue = other.getComparableValue();
  435. return thisValue.compareTo(otherValue);
  436. }
  437. /**
  438. * Strips the tags from the XML.
  439. *
  440. * @param xml
  441. * the String containing a XML snippet.
  442. * @return the original XML without tags.
  443. */
  444. private String stripTags(String xml) {
  445. final StringBuffer res = new StringBuffer();
  446. int processed = 0;
  447. final int xmlLen = xml.length();
  448. while (processed < xmlLen) {
  449. int next = xml.indexOf('<', processed);
  450. if (next < 0) {
  451. next = xmlLen;
  452. }
  453. res.append(xml.substring(processed, next));
  454. if (processed < xmlLen) {
  455. next = xml.indexOf('>', processed);
  456. if (next < 0) {
  457. next = xmlLen;
  458. }
  459. processed = next + 1;
  460. }
  461. }
  462. return res.toString();
  463. }
  464. /**
  465. * Gets the converter used to convert the property data source value to the
  466. * label value.
  467. *
  468. * @return The converter or null if none is set.
  469. */
  470. public Converter<String, Object> getConverter() {
  471. return converter;
  472. }
  473. /**
  474. * Sets the converter used to convert the label value to the property data
  475. * source type. The converter must have a presentation type of String.
  476. *
  477. * @param converter
  478. * The new converter to use.
  479. */
  480. public void setConverter(Converter<String, ?> converter) {
  481. this.converter = (Converter<String, Object>) converter;
  482. markAsDirty();
  483. }
  484. /*
  485. * (non-Javadoc)
  486. *
  487. * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
  488. * com.vaadin.ui.declarative.DesignContext)
  489. */
  490. @Override
  491. public void readDesign(Element design, DesignContext designContext) {
  492. super.readDesign(design, designContext);
  493. String innerHtml = design.html();
  494. boolean plainText = design.hasAttr(DESIGN_ATTR_PLAIN_TEXT);
  495. if (plainText) {
  496. setContentMode(ContentMode.TEXT);
  497. } else {
  498. setContentMode(ContentMode.HTML);
  499. }
  500. if (innerHtml != null && !"".equals(innerHtml)) {
  501. if (plainText) {
  502. innerHtml = DesignFormatter.decodeFromTextNode(innerHtml);
  503. }
  504. setValue(innerHtml);
  505. }
  506. }
  507. /*
  508. * (non-Javadoc)
  509. *
  510. * @see com.vaadin.ui.AbstractComponent#getCustomAttributes()
  511. */
  512. @Override
  513. protected Collection<String> getCustomAttributes() {
  514. Collection<String> result = super.getCustomAttributes();
  515. result.add("value");
  516. result.add("content-mode");
  517. result.add("plain-text");
  518. return result;
  519. }
  520. /*
  521. * (non-Javadoc)
  522. *
  523. * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element
  524. * , com.vaadin.ui.declarative.DesignContext)
  525. */
  526. @Override
  527. public void writeDesign(Element design, DesignContext designContext) {
  528. super.writeDesign(design, designContext);
  529. String content = getValue();
  530. if (content != null) {
  531. switch (getContentMode()) {
  532. case TEXT:
  533. case PREFORMATTED:
  534. case XML:
  535. case RAW: {
  536. // FIXME This attribute is not enough to be able to restore the
  537. // content mode in readDesign. The content mode should instead
  538. // be written directly in the attribute and restored in
  539. // readDesign. See ticket #19435
  540. design.attr(DESIGN_ATTR_PLAIN_TEXT, true);
  541. String encodeForTextNode = DesignFormatter
  542. .encodeForTextNode(content);
  543. if (encodeForTextNode != null) {
  544. design.html(encodeForTextNode);
  545. }
  546. }
  547. break;
  548. case HTML:
  549. design.html(content);
  550. break;
  551. default:
  552. throw new IllegalStateException(
  553. "ContentMode " + getContentMode()
  554. + " is not supported by writeDesign");
  555. }
  556. }
  557. }
  558. }