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.

AbstractComponent.java 38KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262
  1. /*
  2. @ITMillApache2LicenseForJavaFiles@
  3. */
  4. package com.itmill.toolkit.ui;
  5. import java.lang.reflect.Method;
  6. import java.util.ArrayList;
  7. import java.util.Collection;
  8. import java.util.Iterator;
  9. import java.util.LinkedList;
  10. import java.util.Locale;
  11. import java.util.Map;
  12. import java.util.regex.Matcher;
  13. import java.util.regex.Pattern;
  14. import com.itmill.toolkit.Application;
  15. import com.itmill.toolkit.event.EventRouter;
  16. import com.itmill.toolkit.event.MethodEventSource;
  17. import com.itmill.toolkit.terminal.ErrorMessage;
  18. import com.itmill.toolkit.terminal.PaintException;
  19. import com.itmill.toolkit.terminal.PaintTarget;
  20. import com.itmill.toolkit.terminal.Resource;
  21. import com.itmill.toolkit.terminal.Terminal;
  22. /**
  23. * An abstract class that defines default implementation for the
  24. * {@link Component} interface. Basic UI components that are not derived from an
  25. * external component can inherit this class to easily qualify as a IT Mill
  26. * Toolkit component. Most components in the toolkit do just that.
  27. *
  28. * @author IT Mill Ltd.
  29. * @version
  30. * @VERSION@
  31. * @since 3.0
  32. */
  33. public abstract class AbstractComponent implements Component, MethodEventSource {
  34. /* Private members */
  35. /**
  36. * Style names.
  37. */
  38. private ArrayList styles;
  39. /**
  40. * Caption text.
  41. */
  42. private String caption;
  43. /**
  44. * Application specific data object. The component does not use or modify
  45. * this.
  46. */
  47. private Object applicationData;
  48. /**
  49. * Icon to be shown together with caption.
  50. */
  51. private Resource icon;
  52. /**
  53. * Is the component enabled (its normal usage is allowed).
  54. */
  55. private boolean enabled = true;
  56. /**
  57. * Is the component visible (it is rendered).
  58. */
  59. private boolean visible = true;
  60. /**
  61. * Is the component read-only ?
  62. */
  63. private boolean readOnly = false;
  64. /**
  65. * Description of the usage (XML).
  66. */
  67. private String description = null;
  68. /**
  69. * The container this component resides in.
  70. */
  71. private Component parent = null;
  72. /**
  73. * The EventRouter used for the event model.
  74. */
  75. private EventRouter eventRouter = null;
  76. /**
  77. * The internal error message of the component.
  78. */
  79. private ErrorMessage componentError = null;
  80. /**
  81. * Immediate mode: if true, all variable changes are required to be sent
  82. * from the terminal immediately.
  83. */
  84. private boolean immediate = false;
  85. /**
  86. * Locale of this component.
  87. */
  88. private Locale locale;
  89. /**
  90. * List of repaint request listeners or null if not listened at all.
  91. */
  92. private LinkedList repaintRequestListeners = null;
  93. /**
  94. * Are all the repaint listeners notified about recent changes ?
  95. */
  96. private boolean repaintRequestListenersNotified = false;
  97. private String testingId;
  98. /* Sizeable fields */
  99. private float width = SIZE_UNDEFINED;
  100. private float height = SIZE_UNDEFINED;
  101. private int widthUnit = UNITS_PIXELS;
  102. private int heightUnit = UNITS_PIXELS;
  103. private static final Pattern sizePattern = Pattern
  104. .compile("^(-?\\d+(\\.\\d+)?)(%|px|em|ex|in|cm|mm|pt|pc)?$");
  105. private ComponentErrorHandler errorHandler = null;
  106. /* Constructor */
  107. /**
  108. * Constructs a new Component.
  109. */
  110. public AbstractComponent() {
  111. }
  112. /* Get/Set component properties */
  113. /**
  114. * Gets the UIDL tag corresponding to the component.
  115. *
  116. * @return the component's UIDL tag as <code>String</code>
  117. */
  118. public abstract String getTag();
  119. public void setDebugId(String id) {
  120. testingId = id;
  121. }
  122. public String getDebugId() {
  123. return testingId;
  124. }
  125. /**
  126. * Gets style for component. Multiple styles are joined with spaces.
  127. *
  128. * @return the component's styleValue of property style.
  129. * @deprecated Use getStyleName() instead; renamed for consistency and to
  130. * indicate that "style" should not be used to switch client
  131. * side implementation, only to style the component.
  132. */
  133. public String getStyle() {
  134. return getStyleName();
  135. }
  136. /**
  137. * Sets and replaces all previous style names of the component. This method
  138. * will trigger a
  139. * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent
  140. * RepaintRequestEvent}.
  141. *
  142. * @param style
  143. * the new style of the component.
  144. * @deprecated Use setStyleName() instead; renamed for consistency and to
  145. * indicate that "style" should not be used to switch client
  146. * side implementation, only to style the component.
  147. */
  148. public void setStyle(String style) {
  149. setStyleName(style);
  150. }
  151. /*
  152. * Gets the component's style. Don't add a JavaDoc comment here, we use the
  153. * default documentation from implemented interface.
  154. */
  155. public String getStyleName() {
  156. String s = "";
  157. if (styles != null) {
  158. for (final Iterator it = styles.iterator(); it.hasNext();) {
  159. s += (String) it.next();
  160. if (it.hasNext()) {
  161. s += " ";
  162. }
  163. }
  164. }
  165. return s;
  166. }
  167. /*
  168. * Sets the component's style. Don't add a JavaDoc comment here, we use the
  169. * default documentation from implemented interface.
  170. */
  171. public void setStyleName(String style) {
  172. if (style == null || "".equals(style)) {
  173. styles = null;
  174. requestRepaint();
  175. return;
  176. }
  177. if (styles == null) {
  178. styles = new ArrayList();
  179. }
  180. styles.clear();
  181. styles.add(style);
  182. requestRepaint();
  183. }
  184. public void addStyleName(String style) {
  185. if (style == null || "".equals(style)) {
  186. return;
  187. }
  188. if (styles == null) {
  189. styles = new ArrayList();
  190. }
  191. if (!styles.contains(style)) {
  192. styles.add(style);
  193. requestRepaint();
  194. }
  195. }
  196. public void removeStyleName(String style) {
  197. styles.remove(style);
  198. requestRepaint();
  199. }
  200. /*
  201. * Get's the component's caption. Don't add a JavaDoc comment here, we use
  202. * the default documentation from implemented interface.
  203. */
  204. public String getCaption() {
  205. return caption;
  206. }
  207. /**
  208. * Sets the component's caption <code>String</code>. Caption is the visible
  209. * name of the component. This method will trigger a
  210. * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent
  211. * RepaintRequestEvent}.
  212. *
  213. * @param caption
  214. * the new caption <code>String</code> for the component.
  215. */
  216. public void setCaption(String caption) {
  217. this.caption = caption;
  218. requestRepaint();
  219. }
  220. /*
  221. * Don't add a JavaDoc comment here, we use the default documentation from
  222. * implemented interface.
  223. */
  224. public Locale getLocale() {
  225. if (locale != null) {
  226. return locale;
  227. }
  228. if (parent != null) {
  229. return parent.getLocale();
  230. }
  231. final Application app = getApplication();
  232. if (app != null) {
  233. return app.getLocale();
  234. }
  235. return null;
  236. }
  237. /**
  238. * Sets the locale of this component.
  239. *
  240. * @param locale
  241. * the locale to become this component's locale.
  242. */
  243. public void setLocale(Locale locale) {
  244. this.locale = locale;
  245. }
  246. /*
  247. * Gets the component's icon resource. Don't add a JavaDoc comment here, we
  248. * use the default documentation from implemented interface.
  249. */
  250. public Resource getIcon() {
  251. return icon;
  252. }
  253. /**
  254. * Sets the component's icon. This method will trigger a
  255. * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent
  256. * RepaintRequestEvent}.
  257. *
  258. * @param icon
  259. * the icon to be shown with the component's caption.
  260. */
  261. public void setIcon(Resource icon) {
  262. this.icon = icon;
  263. requestRepaint();
  264. }
  265. /*
  266. * Tests if the component is enabled or not. Don't add a JavaDoc comment
  267. * here, we use the default documentation from implemented interface.
  268. */
  269. public boolean isEnabled() {
  270. return enabled && (parent == null || parent.isEnabled()) && isVisible();
  271. }
  272. /*
  273. * Enables or disables the component. Don't add a JavaDoc comment here, we
  274. * use the default documentation from implemented interface.
  275. */
  276. public void setEnabled(boolean enabled) {
  277. if (this.enabled != enabled) {
  278. boolean wasEnabled = isEnabled();
  279. this.enabled = enabled;
  280. // don't repaint if ancestor is disabled
  281. if (wasEnabled != isEnabled()) {
  282. requestRepaint();
  283. }
  284. }
  285. }
  286. /*
  287. * Tests if the component is in the immediate mode. Don't add a JavaDoc
  288. * comment here, we use the default documentation from implemented
  289. * interface.
  290. */
  291. public boolean isImmediate() {
  292. return immediate;
  293. }
  294. /**
  295. * Sets the component's immediate mode to the specified status. This method
  296. * will trigger a
  297. * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent
  298. * RepaintRequestEvent}.
  299. *
  300. * @param immediate
  301. * the boolean value specifying if the component should be in the
  302. * immediate mode after the call.
  303. * @see Component#isImmediate()
  304. */
  305. public void setImmediate(boolean immediate) {
  306. this.immediate = immediate;
  307. requestRepaint();
  308. }
  309. /*
  310. * Tests if the component is visible. Don't add a JavaDoc comment here, we
  311. * use the default documentation from implemented interface.
  312. */
  313. public boolean isVisible() {
  314. return visible;
  315. }
  316. /*
  317. * Sets the components visibility. Don't add a JavaDoc comment here, we use
  318. * the default documentation from implemented interface.
  319. */
  320. public void setVisible(boolean visible) {
  321. if (this.visible != visible) {
  322. this.visible = visible;
  323. // Instead of requesting repaint normally we
  324. // fire the event directly to assure that the
  325. // event goes through event in the component might
  326. // now be invisible
  327. fireRequestRepaintEvent(null);
  328. }
  329. }
  330. /**
  331. * <p>
  332. * Gets the component's description. The description can be used to briefly
  333. * describe the state of the component to the user. The description string
  334. * may contain certain XML tags:
  335. * </p>
  336. *
  337. * <p>
  338. * <table border=1>
  339. * <tr>
  340. * <td width=120><b>Tag</b></td>
  341. * <td width=120><b>Description</b></td>
  342. * <td width=120><b>Example</b></td>
  343. * </tr>
  344. * <tr>
  345. * <td>&lt;b></td>
  346. * <td>bold</td>
  347. * <td><b>bold text</b></td>
  348. * </tr>
  349. * <tr>
  350. * <td>&lt;i></td>
  351. * <td>italic</td>
  352. * <td><i>italic text</i></td>
  353. * </tr>
  354. * <tr>
  355. * <td>&lt;u></td>
  356. * <td>underlined</td>
  357. * <td><u>underlined text</u></td>
  358. * </tr>
  359. * <tr>
  360. * <td>&lt;br></td>
  361. * <td>linebreak</td>
  362. * <td>N/A</td>
  363. * </tr>
  364. * <tr>
  365. * <td>&lt;ul><br>
  366. * &lt;li>item1<br>
  367. * &lt;li>item1<br>
  368. * &lt;/ul></td>
  369. * <td>item list</td>
  370. * <td>
  371. * <ul>
  372. * <li>item1
  373. * <li>item2
  374. * </ul>
  375. * </td>
  376. * </tr>
  377. * </table>
  378. * </p>
  379. *
  380. * <p>
  381. * These tags may be nested.
  382. * </p>
  383. *
  384. * @return component's description <code>String</code>
  385. */
  386. public String getDescription() {
  387. return description;
  388. }
  389. /**
  390. * Sets the component's description. See {@link #getDescription()} for more
  391. * information on what the description is. This method will trigger a
  392. * {@link com.itmill.toolkit.terminal.Paintable.RepaintRequestEvent
  393. * RepaintRequestEvent}.
  394. *
  395. * @param description
  396. * the new description string for the component.
  397. */
  398. public void setDescription(String description) {
  399. this.description = description;
  400. requestRepaint();
  401. }
  402. /*
  403. * Gets the component's parent component. Don't add a JavaDoc comment here,
  404. * we use the default documentation from implemented interface.
  405. */
  406. public Component getParent() {
  407. return parent;
  408. }
  409. /*
  410. * Sets the parent component. Don't add a JavaDoc comment here, we use the
  411. * default documentation from implemented interface.
  412. */
  413. public void setParent(Component parent) {
  414. // If the parent is not changed, don't do anything
  415. if (parent == this.parent) {
  416. return;
  417. }
  418. if (parent != null && this.parent != null) {
  419. throw new IllegalStateException("Component already has a parent.");
  420. }
  421. // Send detach event if the component have been connected to a window
  422. if (getApplication() != null) {
  423. detach();
  424. }
  425. // Connect to new parent
  426. this.parent = parent;
  427. // Send attach event if connected to a window
  428. if (getApplication() != null) {
  429. attach();
  430. }
  431. }
  432. /**
  433. * Gets the error message for this component.
  434. *
  435. * @return ErrorMessage containing the description of the error state of the
  436. * component or null, if the component contains no errors. Extending
  437. * classes should override this method if they support other error
  438. * message types such as validation errors or buffering errors. The
  439. * returned error message contains information about all the errors.
  440. */
  441. public ErrorMessage getErrorMessage() {
  442. return componentError;
  443. }
  444. /**
  445. * Gets the component's error message.
  446. *
  447. * @link Terminal.ErrorMessage#ErrorMessage(String, int)
  448. *
  449. * @return the component's error message.
  450. */
  451. public ErrorMessage getComponentError() {
  452. return componentError;
  453. }
  454. /**
  455. * Sets the component's error message. The message may contain certain XML
  456. * tags, for more information see
  457. *
  458. * @link Component.ErrorMessage#ErrorMessage(String, int)
  459. *
  460. * @param componentError
  461. * the new <code>ErrorMessage</code> of the component.
  462. */
  463. public void setComponentError(ErrorMessage componentError) {
  464. this.componentError = componentError;
  465. fireComponentErrorEvent();
  466. requestRepaint();
  467. }
  468. /*
  469. * Tests if the component is in read-only mode. Don't add a JavaDoc comment
  470. * here, we use the default documentation from implemented interface.
  471. */
  472. public boolean isReadOnly() {
  473. return readOnly;
  474. }
  475. /*
  476. * Sets the component's read-only mode. Don't add a JavaDoc comment here, we
  477. * use the default documentation from implemented interface.
  478. */
  479. public void setReadOnly(boolean readOnly) {
  480. this.readOnly = readOnly;
  481. requestRepaint();
  482. }
  483. /*
  484. * Gets the parent window of the component. Don't add a JavaDoc comment
  485. * here, we use the default documentation from implemented interface.
  486. */
  487. public Window getWindow() {
  488. if (parent == null) {
  489. return null;
  490. } else {
  491. return parent.getWindow();
  492. }
  493. }
  494. /*
  495. * Notify the component that it's attached to a window. Don't add a JavaDoc
  496. * comment here, we use the default documentation from implemented
  497. * interface.
  498. */
  499. public void attach() {
  500. requestRepaint();
  501. }
  502. /*
  503. * Detach the component from application. Don't add a JavaDoc comment here,
  504. * we use the default documentation from implemented interface.
  505. */
  506. public void detach() {
  507. }
  508. /*
  509. * Gets the parent application of the component. Don't add a JavaDoc comment
  510. * here, we use the default documentation from implemented interface.
  511. */
  512. public Application getApplication() {
  513. if (parent == null) {
  514. return null;
  515. } else {
  516. return parent.getApplication();
  517. }
  518. }
  519. /* Component painting */
  520. /* Documented in super interface */
  521. public void requestRepaintRequests() {
  522. repaintRequestListenersNotified = false;
  523. }
  524. /*
  525. * Paints the component into a UIDL stream. Don't add a JavaDoc comment
  526. * here, we use the default documentation from implemented interface.
  527. */
  528. public final void paint(PaintTarget target) throws PaintException {
  529. if (!target.startTag(this, getTag()) || repaintRequestListenersNotified) {
  530. // Paint the contents of the component
  531. // Only paint content of visible components.
  532. if (isVisible()) {
  533. if (getHeight() >= 0) {
  534. target.addAttribute("height", "" + getCSSHeight());
  535. }
  536. if (getWidth() >= 0) {
  537. target.addAttribute("width", "" + getCSSWidth());
  538. }
  539. if (styles != null && styles.size() > 0) {
  540. target.addAttribute("style", getStyle());
  541. }
  542. if (isReadOnly()) {
  543. target.addAttribute("readonly", true);
  544. }
  545. if (isImmediate()) {
  546. target.addAttribute("immediate", true);
  547. }
  548. if (!isEnabled()) {
  549. target.addAttribute("disabled", true);
  550. }
  551. if (getCaption() != null) {
  552. target.addAttribute("caption", getCaption());
  553. }
  554. if (getIcon() != null) {
  555. target.addAttribute("icon", getIcon());
  556. }
  557. if (getDescription() != null && getDescription().length() > 0) {
  558. target.addAttribute("description", getDescription());
  559. }
  560. paintContent(target);
  561. final ErrorMessage error = getErrorMessage();
  562. if (error != null) {
  563. error.paint(target);
  564. }
  565. } else {
  566. target.addAttribute("invisible", true);
  567. }
  568. } else {
  569. // Contents have not changed, only cached presentation can be used
  570. target.addAttribute("cached", true);
  571. }
  572. target.endTag(getTag());
  573. repaintRequestListenersNotified = false;
  574. }
  575. /**
  576. * Build CSS compatible string representation of height.
  577. *
  578. * @return CSS height
  579. */
  580. private String getCSSHeight() {
  581. if (getHeightUnits() == UNITS_PIXELS) {
  582. return ((int) getHeight()) + UNIT_SYMBOLS[getHeightUnits()];
  583. } else {
  584. return getHeight() + UNIT_SYMBOLS[getHeightUnits()];
  585. }
  586. }
  587. /**
  588. * Build CSS compatible string representation of width.
  589. *
  590. * @return CSS width
  591. */
  592. private String getCSSWidth() {
  593. if (getWidthUnits() == UNITS_PIXELS) {
  594. return ((int) getWidth()) + UNIT_SYMBOLS[getWidthUnits()];
  595. } else {
  596. return getWidth() + UNIT_SYMBOLS[getWidthUnits()];
  597. }
  598. }
  599. /**
  600. * Paints any needed component-specific things to the given UIDL stream. The
  601. * more general {@link #paint(PaintTarget)} method handles all general
  602. * attributes common to all components, and it calls this method to paint
  603. * any component-specific attributes to the UIDL stream.
  604. *
  605. * @param target
  606. * the target UIDL stream where the component should paint itself
  607. * to
  608. * @throws PaintException
  609. * if the paint operation failed.
  610. */
  611. public void paintContent(PaintTarget target) throws PaintException {
  612. }
  613. /* Documentation copied from interface */
  614. public void requestRepaint() {
  615. // The effect of the repaint request is identical to case where a
  616. // child requests repaint
  617. childRequestedRepaint(null);
  618. }
  619. /* Documentation copied from interface */
  620. public void childRequestedRepaint(Collection alreadyNotified) {
  621. // Invisible components do not need repaints
  622. if (!isVisible()) {
  623. return;
  624. }
  625. fireRequestRepaintEvent(alreadyNotified);
  626. }
  627. /**
  628. * Fires the repaint request event.
  629. *
  630. * @param alreadyNotified
  631. */
  632. private void fireRequestRepaintEvent(Collection alreadyNotified) {
  633. // Notify listeners only once
  634. if (!repaintRequestListenersNotified) {
  635. // Notify the listeners
  636. if (repaintRequestListeners != null
  637. && !repaintRequestListeners.isEmpty()) {
  638. final Object[] listeners = repaintRequestListeners.toArray();
  639. final RepaintRequestEvent event = new RepaintRequestEvent(this);
  640. for (int i = 0; i < listeners.length; i++) {
  641. if (alreadyNotified == null) {
  642. alreadyNotified = new LinkedList();
  643. }
  644. if (!alreadyNotified.contains(listeners[i])) {
  645. ((RepaintRequestListener) listeners[i])
  646. .repaintRequested(event);
  647. alreadyNotified.add(listeners[i]);
  648. repaintRequestListenersNotified = true;
  649. }
  650. }
  651. }
  652. // Notify the parent
  653. final Component parent = getParent();
  654. if (parent != null) {
  655. parent.childRequestedRepaint(alreadyNotified);
  656. }
  657. }
  658. }
  659. /* Documentation copied from interface */
  660. public void addListener(RepaintRequestListener listener) {
  661. if (repaintRequestListeners == null) {
  662. repaintRequestListeners = new LinkedList();
  663. }
  664. if (!repaintRequestListeners.contains(listener)) {
  665. repaintRequestListeners.add(listener);
  666. }
  667. }
  668. /* Documentation copied from interface */
  669. public void removeListener(RepaintRequestListener listener) {
  670. if (repaintRequestListeners != null) {
  671. repaintRequestListeners.remove(listener);
  672. if (repaintRequestListeners.isEmpty()) {
  673. repaintRequestListeners = null;
  674. }
  675. }
  676. }
  677. /* Component variable changes */
  678. /*
  679. * Invoked when the value of a variable has changed. Don't add a JavaDoc
  680. * comment here, we use the default documentation from implemented
  681. * interface.
  682. */
  683. public void changeVariables(Object source, Map variables) {
  684. }
  685. /* General event framework */
  686. private static final Method COMPONENT_EVENT_METHOD;
  687. static {
  688. try {
  689. COMPONENT_EVENT_METHOD = Component.Listener.class
  690. .getDeclaredMethod("componentEvent",
  691. new Class[] { Component.Event.class });
  692. } catch (final java.lang.NoSuchMethodException e) {
  693. // This should never happen
  694. throw new java.lang.RuntimeException(
  695. "Internal error finding methods in AbstractComponent");
  696. }
  697. }
  698. /**
  699. * <p>
  700. * Registers a new listener with the specified activation method to listen
  701. * events generated by this component. If the activation method does not
  702. * have any arguments the event object will not be passed to it when it's
  703. * called.
  704. * </p>
  705. *
  706. * <p>
  707. * For more information on the inheritable event mechanism see the
  708. * {@link com.itmill.toolkit.event com.itmill.toolkit.event package
  709. * documentation}.
  710. * </p>
  711. *
  712. * @param eventType
  713. * the type of the listened event. Events of this type or its
  714. * subclasses activate the listener.
  715. * @param object
  716. * the object instance who owns the activation method.
  717. * @param method
  718. * the activation method.
  719. */
  720. public void addListener(Class eventType, Object object, Method method) {
  721. if (eventRouter == null) {
  722. eventRouter = new EventRouter();
  723. }
  724. eventRouter.addListener(eventType, object, method);
  725. }
  726. /**
  727. * <p>
  728. * Convenience method for registering a new listener with the specified
  729. * activation method to listen events generated by this component. If the
  730. * activation method does not have any arguments the event object will not
  731. * be passed to it when it's called.
  732. * </p>
  733. *
  734. * <p>
  735. * This version of <code>addListener</code> gets the name of the activation
  736. * method as a parameter. The actual method is reflected from
  737. * <code>object</code>, and unless exactly one match is found,
  738. * <code>java.lang.IllegalArgumentException</code> is thrown.
  739. * </p>
  740. *
  741. * <p>
  742. * For more information on the inheritable event mechanism see the
  743. * {@link com.itmill.toolkit.event com.itmill.toolkit.event package
  744. * documentation}.
  745. * </p>
  746. *
  747. * <p>
  748. * Note: Using this method is discouraged because it cannot be checked
  749. * during compilation. Use {@link #addListener(Class, Object, Method)} or
  750. * {@link #addListener(com.itmill.toolkit.ui.Component.Listener)} instead.
  751. * </p>
  752. *
  753. * @param eventType
  754. * the type of the listened event. Events of this type or its
  755. * subclasses activate the listener.
  756. * @param object
  757. * the object instance who owns the activation method.
  758. * @param methodName
  759. * the name of the activation method.
  760. */
  761. public void addListener(Class eventType, Object object, String methodName) {
  762. if (eventRouter == null) {
  763. eventRouter = new EventRouter();
  764. }
  765. eventRouter.addListener(eventType, object, methodName);
  766. }
  767. /**
  768. * Removes all registered listeners matching the given parameters. Since
  769. * this method receives the event type and the listener object as
  770. * parameters, it will unregister all <code>object</code>'s methods that are
  771. * registered to listen to events of type <code>eventType</code> generated
  772. * by this component.
  773. *
  774. * <p>
  775. * For more information on the inheritable event mechanism see the
  776. * {@link com.itmill.toolkit.event com.itmill.toolkit.event package
  777. * documentation}.
  778. * </p>
  779. *
  780. * @param eventType
  781. * the exact event type the <code>object</code> listens to.
  782. * @param target
  783. * the target object that has registered to listen to events of
  784. * type <code>eventType</code> with one or more methods.
  785. */
  786. public void removeListener(Class eventType, Object target) {
  787. if (eventRouter != null) {
  788. eventRouter.removeListener(eventType, target);
  789. }
  790. }
  791. /**
  792. * Removes one registered listener method. The given method owned by the
  793. * given object will no longer be called when the specified events are
  794. * generated by this component.
  795. *
  796. * <p>
  797. * For more information on the inheritable event mechanism see the
  798. * {@link com.itmill.toolkit.event com.itmill.toolkit.event package
  799. * documentation}.
  800. * </p>
  801. *
  802. * @param eventType
  803. * the exact event type the <code>object</code> listens to.
  804. * @param target
  805. * target object that has registered to listen to events of type
  806. * <code>eventType</code> with one or more methods.
  807. * @param method
  808. * the method owned by <code>target</code> that's registered to
  809. * listen to events of type <code>eventType</code>.
  810. */
  811. public void removeListener(Class eventType, Object target, Method method) {
  812. if (eventRouter != null) {
  813. eventRouter.removeListener(eventType, target, method);
  814. }
  815. }
  816. /**
  817. * <p>
  818. * Removes one registered listener method. The given method owned by the
  819. * given object will no longer be called when the specified events are
  820. * generated by this component.
  821. * </p>
  822. *
  823. * <p>
  824. * This version of <code>removeListener</code> gets the name of the
  825. * activation method as a parameter. The actual method is reflected from
  826. * <code>target</code>, and unless exactly one match is found,
  827. * <code>java.lang.IllegalArgumentException</code> is thrown.
  828. * </p>
  829. *
  830. * <p>
  831. * For more information on the inheritable event mechanism see the
  832. * {@link com.itmill.toolkit.event com.itmill.toolkit.event package
  833. * documentation}.
  834. * </p>
  835. *
  836. * @param eventType
  837. * the exact event type the <code>object</code> listens to.
  838. * @param target
  839. * the target object that has registered to listen to events of
  840. * type <code>eventType</code> with one or more methods.
  841. * @param methodName
  842. * the name of the method owned by <code>target</code> that's
  843. * registered to listen to events of type <code>eventType</code>.
  844. */
  845. public void removeListener(Class eventType, Object target, String methodName) {
  846. if (eventRouter != null) {
  847. eventRouter.removeListener(eventType, target, methodName);
  848. }
  849. }
  850. /**
  851. * Sends the event to all listeners.
  852. *
  853. * @param event
  854. * the Event to be sent to all listeners.
  855. */
  856. protected void fireEvent(Component.Event event) {
  857. if (eventRouter != null) {
  858. eventRouter.fireEvent(event);
  859. }
  860. }
  861. /* Component event framework */
  862. /*
  863. * Registers a new listener to listen events generated by this component.
  864. * Don't add a JavaDoc comment here, we use the default documentation from
  865. * implemented interface.
  866. */
  867. public void addListener(Component.Listener listener) {
  868. if (eventRouter == null) {
  869. eventRouter = new EventRouter();
  870. }
  871. eventRouter.addListener(Component.Event.class, listener,
  872. COMPONENT_EVENT_METHOD);
  873. }
  874. /*
  875. * Removes a previously registered listener from this component. Don't add a
  876. * JavaDoc comment here, we use the default documentation from implemented
  877. * interface.
  878. */
  879. public void removeListener(Component.Listener listener) {
  880. if (eventRouter != null) {
  881. eventRouter.removeListener(Component.Event.class, listener,
  882. COMPONENT_EVENT_METHOD);
  883. }
  884. }
  885. /**
  886. * Emits the component event. It is transmitted to all registered listeners
  887. * interested in such events.
  888. */
  889. protected void fireComponentEvent() {
  890. fireEvent(new Component.Event(this));
  891. }
  892. /**
  893. * Emits the component error event. It is transmitted to all registered
  894. * listeners interested in such events.
  895. */
  896. protected void fireComponentErrorEvent() {
  897. fireEvent(new Component.ErrorEvent(getComponentError(), this));
  898. }
  899. /**
  900. * Sets the data object, that can be used for any application specific data.
  901. * The component does not use or modify this data.
  902. *
  903. * @param data
  904. * the Application specific data.
  905. * @since 3.1
  906. */
  907. public void setData(Object data) {
  908. applicationData = data;
  909. }
  910. /**
  911. * Gets the application specific data. See {@link #setData(Object)}.
  912. *
  913. * @return the Application specific data set with setData function.
  914. * @since 3.1
  915. */
  916. public Object getData() {
  917. return applicationData;
  918. }
  919. /* Sizeable and other size related methods */
  920. public float getHeight() {
  921. return height;
  922. }
  923. public int getHeightUnits() {
  924. return heightUnit;
  925. }
  926. public float getWidth() {
  927. return width;
  928. }
  929. public int getWidthUnits() {
  930. return widthUnit;
  931. }
  932. public void setHeight(float height) {
  933. this.height = height;
  934. requestRepaint();
  935. }
  936. /**
  937. * Sets the height property units.
  938. *
  939. * @param units
  940. * the units used in height property.
  941. * @deprecated Consider setting height and unit simultaneously using
  942. * {@link #setHeight(String)} or {@link #setHeight(float, int)},
  943. * which is less error-prone.
  944. */
  945. public void setHeightUnits(int unit) {
  946. heightUnit = unit;
  947. requestRepaint();
  948. }
  949. /**
  950. * Sets the height of the object. Negative number implies unspecified size
  951. * (terminal is free to set the size).
  952. *
  953. * @param height
  954. * the height of the object in units specified by heightUnits
  955. * property.
  956. * @deprecated Consider using {@link #setHeight(String)} or
  957. * {@link #setHeight(float, int)} instead. This method works,
  958. * but is error-prone since the unit must be set separately (and
  959. * components might have different default unit).
  960. */
  961. public void setHeight(float height, int unit) {
  962. this.height = height;
  963. heightUnit = unit;
  964. requestRepaint();
  965. }
  966. public void setSizeFull() {
  967. height = 100;
  968. width = 100;
  969. heightUnit = UNITS_PERCENTAGE;
  970. widthUnit = UNITS_PERCENTAGE;
  971. requestRepaint();
  972. }
  973. public void setSizeUndefined() {
  974. height = -1;
  975. width = -1;
  976. heightUnit = UNITS_PIXELS;
  977. widthUnit = UNITS_PIXELS;
  978. requestRepaint();
  979. }
  980. /**
  981. * Sets the width of the object. Negative number implies unspecified size
  982. * (terminal is free to set the size).
  983. *
  984. * @param width
  985. * the width of the object in units specified by widthUnits
  986. * property.
  987. * @deprecated Consider using {@link #setWidth(String)} instead. This method
  988. * works, but is error-prone since the unit must be set
  989. * separately (and components might have different default
  990. * unit).
  991. */
  992. public void setWidth(float width) {
  993. this.width = width;
  994. requestRepaint();
  995. }
  996. /**
  997. * Sets the width property units.
  998. *
  999. * @param units
  1000. * the units used in width property.
  1001. * @deprecated Consider setting width and unit simultaneously using
  1002. * {@link #setWidth(String)} or {@link #setWidth(float, int)},
  1003. * which is less error-prone.
  1004. */
  1005. public void setWidthUnits(int unit) {
  1006. widthUnit = unit;
  1007. requestRepaint();
  1008. }
  1009. public void setWidth(float width, int unit) {
  1010. this.width = width;
  1011. widthUnit = unit;
  1012. requestRepaint();
  1013. }
  1014. public void setWidth(String width) {
  1015. float[] p = parseStringSize(width);
  1016. this.width = p[0];
  1017. widthUnit = (int) p[1];
  1018. requestRepaint();
  1019. }
  1020. public void setHeight(String height) {
  1021. float[] p = parseStringSize(height);
  1022. this.height = p[0];
  1023. heightUnit = (int) p[1];
  1024. requestRepaint();
  1025. }
  1026. /*
  1027. * Returns array with size in index 0 unit in index 1. Null or empty string
  1028. * will produce {-1,UNITS_PIXELS}
  1029. */
  1030. private static float[] parseStringSize(String s) {
  1031. float[] values = { -1, UNITS_PIXELS };
  1032. if (s == null) {
  1033. return values;
  1034. }
  1035. s = s.trim();
  1036. if ("".equals(s)) {
  1037. return values;
  1038. }
  1039. Matcher matcher = sizePattern.matcher(s);
  1040. if (matcher.find()) {
  1041. values[0] = Float.parseFloat(matcher.group(1));
  1042. if (values[0] < 0) {
  1043. values[0] = -1;
  1044. } else {
  1045. String unit = matcher.group(3);
  1046. if (unit == null) {
  1047. values[1] = UNITS_PIXELS;
  1048. } else if (unit.equals("px")) {
  1049. values[1] = UNITS_PIXELS;
  1050. } else if (unit.equals("%")) {
  1051. values[1] = UNITS_PERCENTAGE;
  1052. } else if (unit.equals("em")) {
  1053. values[1] = UNITS_EM;
  1054. } else if (unit.equals("ex")) {
  1055. values[1] = UNITS_EX;
  1056. } else if (unit.equals("in")) {
  1057. values[1] = UNITS_INCH;
  1058. } else if (unit.equals("cm")) {
  1059. values[1] = UNITS_CM;
  1060. } else if (unit.equals("mm")) {
  1061. values[1] = UNITS_MM;
  1062. } else if (unit.equals("pt")) {
  1063. values[1] = UNITS_POINTS;
  1064. } else if (unit.equals("pc")) {
  1065. values[1] = UNITS_PICAS;
  1066. }
  1067. }
  1068. } else {
  1069. throw new IllegalArgumentException("Invalid size argument: \"" + s
  1070. + "\" (should match " + sizePattern.pattern() + ")");
  1071. }
  1072. return values;
  1073. }
  1074. public interface ComponentErrorEvent extends Terminal.ErrorEvent {
  1075. }
  1076. public interface ComponentErrorHandler {
  1077. /**
  1078. * Handle the component error
  1079. *
  1080. * @param event
  1081. * @return True if the error has been handled False, otherwise
  1082. */
  1083. public boolean handleComponentError(ComponentErrorEvent event);
  1084. }
  1085. /**
  1086. * Gets the error handler for the component.
  1087. *
  1088. * The error handler is dispatched whenever there is an error processing the
  1089. * data coming from the client.
  1090. *
  1091. * @return
  1092. */
  1093. public ComponentErrorHandler getErrorHandler() {
  1094. return errorHandler;
  1095. }
  1096. /**
  1097. * Sets the error handler for the component.
  1098. *
  1099. * The error handler is dispatched whenever there is an error processing the
  1100. * data coming from the client.
  1101. *
  1102. * If the error handler is not set, the application error handler is used to
  1103. * handle the exception.
  1104. *
  1105. * @param errorHandler
  1106. * AbstractField specific error handler
  1107. */
  1108. public void setErrorHandler(ComponentErrorHandler errorHandler) {
  1109. this.errorHandler = errorHandler;
  1110. }
  1111. /**
  1112. * Handle the component error event.
  1113. *
  1114. * @param error
  1115. * Error event to handle
  1116. * @return True if the error has been handled False, otherwise. If the error
  1117. * haven't been handled by this component, it will be handled in the
  1118. * application error handler.
  1119. */
  1120. public boolean handleError(ComponentErrorEvent error) {
  1121. if (errorHandler != null) {
  1122. return errorHandler.handleComponentError(error);
  1123. }
  1124. return false;
  1125. }
  1126. }