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

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