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.

Button.java 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. /*
  2. * Copyright 2000-2014 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui;
  17. import java.io.Serializable;
  18. import java.lang.reflect.Method;
  19. import java.util.Collection;
  20. import org.jsoup.nodes.Attributes;
  21. import org.jsoup.nodes.Element;
  22. import com.vaadin.event.Action;
  23. import com.vaadin.event.ShortcutAction;
  24. import com.vaadin.event.ShortcutAction.KeyCode;
  25. import com.vaadin.event.ShortcutAction.ModifierKey;
  26. import com.vaadin.event.ShortcutListener;
  27. import com.vaadin.server.Resource;
  28. import com.vaadin.shared.MouseEventDetails;
  29. import com.vaadin.shared.ui.button.ButtonServerRpc;
  30. import com.vaadin.shared.ui.button.ButtonState;
  31. import com.vaadin.ui.declarative.DesignAttributeHandler;
  32. import com.vaadin.ui.declarative.DesignContext;
  33. import com.vaadin.ui.declarative.DesignFormatter;
  34. import com.vaadin.util.ReflectTools;
  35. /**
  36. * A generic button component.
  37. *
  38. * @author Vaadin Ltd.
  39. * @since 3.0
  40. */
  41. @SuppressWarnings("serial")
  42. public class Button extends AbstractFocusable implements
  43. Action.ShortcutNotifier {
  44. private ButtonServerRpc rpc = new ButtonServerRpc() {
  45. @Override
  46. public void click(MouseEventDetails mouseEventDetails) {
  47. fireClick(mouseEventDetails);
  48. }
  49. @Override
  50. public void disableOnClick() throws RuntimeException {
  51. setEnabled(false);
  52. // Makes sure the enabled=false state is noticed at once - otherwise
  53. // a following setEnabled(true) call might have no effect. see
  54. // ticket #10030
  55. getUI().getConnectorTracker().getDiffState(Button.this)
  56. .put("enabled", false);
  57. }
  58. };
  59. /**
  60. * Creates a new push button.
  61. */
  62. public Button() {
  63. registerRpc(rpc);
  64. }
  65. /**
  66. * Creates a new push button with the given caption.
  67. *
  68. * @param caption
  69. * the Button caption.
  70. */
  71. public Button(String caption) {
  72. this();
  73. setCaption(caption);
  74. }
  75. /**
  76. * Creates a new push button with the given icon.
  77. *
  78. * @param icon
  79. * the icon
  80. */
  81. public Button(Resource icon) {
  82. this();
  83. setIcon(icon);
  84. }
  85. /**
  86. * Creates a new push button with the given caption and icon.
  87. *
  88. * @param caption
  89. * the caption
  90. * @param icon
  91. * the icon
  92. */
  93. public Button(String caption, Resource icon) {
  94. this();
  95. setCaption(caption);
  96. setIcon(icon);
  97. }
  98. /**
  99. * Creates a new push button with a click listener.
  100. *
  101. * @param caption
  102. * the Button caption.
  103. * @param listener
  104. * the Button click listener.
  105. */
  106. public Button(String caption, ClickListener listener) {
  107. this(caption);
  108. addListener(listener);
  109. }
  110. /**
  111. * Click event. This event is thrown, when the button is clicked.
  112. *
  113. * @author Vaadin Ltd.
  114. * @since 3.0
  115. */
  116. public static class ClickEvent extends Component.Event {
  117. private final MouseEventDetails details;
  118. /**
  119. * New instance of text change event.
  120. *
  121. * @param source
  122. * the Source of the event.
  123. */
  124. public ClickEvent(Component source) {
  125. super(source);
  126. details = null;
  127. }
  128. /**
  129. * Constructor with mouse details
  130. *
  131. * @param source
  132. * The source where the click took place
  133. * @param details
  134. * Details about the mouse click
  135. */
  136. public ClickEvent(Component source, MouseEventDetails details) {
  137. super(source);
  138. this.details = details;
  139. }
  140. /**
  141. * Gets the Button where the event occurred.
  142. *
  143. * @return the Source of the event.
  144. */
  145. public Button getButton() {
  146. return (Button) getSource();
  147. }
  148. /**
  149. * Returns the mouse position (x coordinate) when the click took place.
  150. * The position is relative to the browser client area.
  151. *
  152. * @return The mouse cursor x position or -1 if unknown
  153. */
  154. public int getClientX() {
  155. if (null != details) {
  156. return details.getClientX();
  157. } else {
  158. return -1;
  159. }
  160. }
  161. /**
  162. * Returns the mouse position (y coordinate) when the click took place.
  163. * The position is relative to the browser client area.
  164. *
  165. * @return The mouse cursor y position or -1 if unknown
  166. */
  167. public int getClientY() {
  168. if (null != details) {
  169. return details.getClientY();
  170. } else {
  171. return -1;
  172. }
  173. }
  174. /**
  175. * Returns the relative mouse position (x coordinate) when the click
  176. * took place. The position is relative to the clicked component.
  177. *
  178. * @return The mouse cursor x position relative to the clicked layout
  179. * component or -1 if no x coordinate available
  180. */
  181. public int getRelativeX() {
  182. if (null != details) {
  183. return details.getRelativeX();
  184. } else {
  185. return -1;
  186. }
  187. }
  188. /**
  189. * Returns the relative mouse position (y coordinate) when the click
  190. * took place. The position is relative to the clicked component.
  191. *
  192. * @return The mouse cursor y position relative to the clicked layout
  193. * component or -1 if no y coordinate available
  194. */
  195. public int getRelativeY() {
  196. if (null != details) {
  197. return details.getRelativeY();
  198. } else {
  199. return -1;
  200. }
  201. }
  202. /**
  203. * Checks if the Alt key was down when the mouse event took place.
  204. *
  205. * @return true if Alt was down when the event occured, false otherwise
  206. * or if unknown
  207. */
  208. public boolean isAltKey() {
  209. if (null != details) {
  210. return details.isAltKey();
  211. } else {
  212. return false;
  213. }
  214. }
  215. /**
  216. * Checks if the Ctrl key was down when the mouse event took place.
  217. *
  218. * @return true if Ctrl was pressed when the event occured, false
  219. * otherwise or if unknown
  220. */
  221. public boolean isCtrlKey() {
  222. if (null != details) {
  223. return details.isCtrlKey();
  224. } else {
  225. return false;
  226. }
  227. }
  228. /**
  229. * Checks if the Meta key was down when the mouse event took place.
  230. *
  231. * @return true if Meta was pressed when the event occured, false
  232. * otherwise or if unknown
  233. */
  234. public boolean isMetaKey() {
  235. if (null != details) {
  236. return details.isMetaKey();
  237. } else {
  238. return false;
  239. }
  240. }
  241. /**
  242. * Checks if the Shift key was down when the mouse event took place.
  243. *
  244. * @return true if Shift was pressed when the event occured, false
  245. * otherwise or if unknown
  246. */
  247. public boolean isShiftKey() {
  248. if (null != details) {
  249. return details.isShiftKey();
  250. } else {
  251. return false;
  252. }
  253. }
  254. }
  255. /**
  256. * Interface for listening for a {@link ClickEvent} fired by a
  257. * {@link Component}.
  258. *
  259. * @author Vaadin Ltd.
  260. * @since 3.0
  261. */
  262. public interface ClickListener extends Serializable {
  263. public static final Method BUTTON_CLICK_METHOD = ReflectTools
  264. .findMethod(ClickListener.class, "buttonClick",
  265. ClickEvent.class);
  266. /**
  267. * Called when a {@link Button} has been clicked. A reference to the
  268. * button is given by {@link ClickEvent#getButton()}.
  269. *
  270. * @param event
  271. * An event containing information about the click.
  272. */
  273. public void buttonClick(ClickEvent event);
  274. }
  275. /**
  276. * Adds the button click listener.
  277. *
  278. * @param listener
  279. * the Listener to be added.
  280. */
  281. public void addClickListener(ClickListener listener) {
  282. addListener(ClickEvent.class, listener,
  283. ClickListener.BUTTON_CLICK_METHOD);
  284. }
  285. /**
  286. * @deprecated As of 7.0, replaced by
  287. * {@link #addClickListener(ClickListener)}
  288. **/
  289. @Deprecated
  290. public void addListener(ClickListener listener) {
  291. addClickListener(listener);
  292. }
  293. /**
  294. * Removes the button click listener.
  295. *
  296. * @param listener
  297. * the Listener to be removed.
  298. */
  299. public void removeClickListener(ClickListener listener) {
  300. removeListener(ClickEvent.class, listener,
  301. ClickListener.BUTTON_CLICK_METHOD);
  302. }
  303. /**
  304. * @deprecated As of 7.0, replaced by
  305. * {@link #removeClickListener(ClickListener)}
  306. **/
  307. @Deprecated
  308. public void removeListener(ClickListener listener) {
  309. removeClickListener(listener);
  310. }
  311. /**
  312. * Simulates a button click, notifying all server-side listeners.
  313. *
  314. * No action is taken is the button is disabled.
  315. */
  316. public void click() {
  317. if (isEnabled() && !isReadOnly()) {
  318. fireClick();
  319. }
  320. }
  321. /**
  322. * Fires a click event to all listeners without any event details.
  323. *
  324. * In subclasses, override {@link #fireClick(MouseEventDetails)} instead of
  325. * this method.
  326. */
  327. protected void fireClick() {
  328. fireEvent(new Button.ClickEvent(this));
  329. }
  330. /**
  331. * Fires a click event to all listeners.
  332. *
  333. * @param details
  334. * MouseEventDetails from which keyboard modifiers and other
  335. * information about the mouse click can be obtained. If the
  336. * button was clicked by a keyboard event, some of the fields may
  337. * be empty/undefined.
  338. */
  339. protected void fireClick(MouseEventDetails details) {
  340. fireEvent(new Button.ClickEvent(this, details));
  341. }
  342. /*
  343. * Actions
  344. */
  345. protected ClickShortcut clickShortcut;
  346. /**
  347. * Makes it possible to invoke a click on this button by pressing the given
  348. * {@link KeyCode} and (optional) {@link ModifierKey}s.<br/>
  349. * The shortcut is global (bound to the containing Window).
  350. *
  351. * @param keyCode
  352. * the keycode for invoking the shortcut
  353. * @param modifiers
  354. * the (optional) modifiers for invoking the shortcut, null for
  355. * none
  356. */
  357. public void setClickShortcut(int keyCode, int... modifiers) {
  358. if (clickShortcut != null) {
  359. removeShortcutListener(clickShortcut);
  360. }
  361. clickShortcut = new ClickShortcut(this, keyCode, modifiers);
  362. addShortcutListener(clickShortcut);
  363. getState().clickShortcutKeyCode = clickShortcut.getKeyCode();
  364. }
  365. /**
  366. * Removes the keyboard shortcut previously set with
  367. * {@link #setClickShortcut(int, int...)}.
  368. */
  369. public void removeClickShortcut() {
  370. if (clickShortcut != null) {
  371. removeShortcutListener(clickShortcut);
  372. clickShortcut = null;
  373. getState().clickShortcutKeyCode = 0;
  374. }
  375. }
  376. /**
  377. * A {@link ShortcutListener} specifically made to define a keyboard
  378. * shortcut that invokes a click on the given button.
  379. *
  380. */
  381. public static class ClickShortcut extends ShortcutListener {
  382. protected Button button;
  383. /**
  384. * Creates a keyboard shortcut for clicking the given button using the
  385. * shorthand notation defined in {@link ShortcutAction}.
  386. *
  387. * @param button
  388. * to be clicked when the shortcut is invoked
  389. * @param shorthandCaption
  390. * the caption with shortcut keycode and modifiers indicated
  391. */
  392. public ClickShortcut(Button button, String shorthandCaption) {
  393. super(shorthandCaption);
  394. this.button = button;
  395. }
  396. /**
  397. * Creates a keyboard shortcut for clicking the given button using the
  398. * given {@link KeyCode} and {@link ModifierKey}s.
  399. *
  400. * @param button
  401. * to be clicked when the shortcut is invoked
  402. * @param keyCode
  403. * KeyCode to react to
  404. * @param modifiers
  405. * optional modifiers for shortcut
  406. */
  407. public ClickShortcut(Button button, int keyCode, int... modifiers) {
  408. super(null, keyCode, modifiers);
  409. this.button = button;
  410. }
  411. /**
  412. * Creates a keyboard shortcut for clicking the given button using the
  413. * given {@link KeyCode}.
  414. *
  415. * @param button
  416. * to be clicked when the shortcut is invoked
  417. * @param keyCode
  418. * KeyCode to react to
  419. */
  420. public ClickShortcut(Button button, int keyCode) {
  421. this(button, keyCode, null);
  422. }
  423. @Override
  424. public void handleAction(Object sender, Object target) {
  425. button.click();
  426. }
  427. }
  428. /**
  429. * Determines if a button is automatically disabled when clicked. See
  430. * {@link #setDisableOnClick(boolean)} for details.
  431. *
  432. * @return true if the button is disabled when clicked, false otherwise
  433. */
  434. public boolean isDisableOnClick() {
  435. return getState(false).disableOnClick;
  436. }
  437. /**
  438. * Determines if a button is automatically disabled when clicked. If this is
  439. * set to true the button will be automatically disabled when clicked,
  440. * typically to prevent (accidental) extra clicks on a button.
  441. * <p>
  442. * Note that this is only used when the click comes from the user, not when
  443. * calling {@link #click()}.
  444. * </p>
  445. *
  446. * @param disableOnClick
  447. * true to disable button when it is clicked, false otherwise
  448. */
  449. public void setDisableOnClick(boolean disableOnClick) {
  450. getState().disableOnClick = disableOnClick;
  451. }
  452. @Override
  453. protected ButtonState getState() {
  454. return (ButtonState) super.getState();
  455. }
  456. @Override
  457. protected ButtonState getState(boolean markAsDirty) {
  458. return (ButtonState) super.getState(markAsDirty);
  459. }
  460. /**
  461. * Sets the component's icon and alt text.
  462. *
  463. * An alt text is shown when an image could not be loaded, and read by
  464. * assisitve devices.
  465. *
  466. * @param icon
  467. * the icon to be shown with the component's caption.
  468. * @param iconAltText
  469. * String to use as alt text
  470. */
  471. public void setIcon(Resource icon, String iconAltText) {
  472. super.setIcon(icon);
  473. getState().iconAltText = iconAltText == null ? "" : iconAltText;
  474. }
  475. /**
  476. * Returns the icon's alt text.
  477. *
  478. * @return String with the alt text
  479. */
  480. public String getIconAlternateText() {
  481. return getState(false).iconAltText;
  482. }
  483. public void setIconAlternateText(String iconAltText) {
  484. getState().iconAltText = iconAltText;
  485. }
  486. /**
  487. * Set whether the caption text is rendered as HTML or not. You might need
  488. * to re-theme button to allow higher content than the original text style.
  489. *
  490. * If set to true, the captions are passed to the browser as html and the
  491. * developer is responsible for ensuring no harmful html is used. If set to
  492. * false, the content is passed to the browser as plain text.
  493. *
  494. * @param htmlContentAllowed
  495. * <code>true</code> if caption is rendered as HTML,
  496. * <code>false</code> otherwise
  497. */
  498. public void setHtmlContentAllowed(boolean htmlContentAllowed) {
  499. getState().captionAsHtml = htmlContentAllowed;
  500. }
  501. /**
  502. * Return HTML rendering setting
  503. *
  504. * @return <code>true</code> if the caption text is to be rendered as HTML,
  505. * <code>false</code> otherwise
  506. */
  507. public boolean isHtmlContentAllowed() {
  508. return getState(false).captionAsHtml;
  509. }
  510. /*
  511. * (non-Javadoc)
  512. *
  513. * @see com.vaadin.ui.AbstractComponent#readDesign(org.jsoup.nodes .Element,
  514. * com.vaadin.ui.declarative.DesignContext)
  515. */
  516. @Override
  517. public void readDesign(Element design, DesignContext designContext) {
  518. super.readDesign(design, designContext);
  519. Attributes attr = design.attributes();
  520. String content;
  521. // plain-text (default is html)
  522. Boolean plain = DesignAttributeHandler.readAttribute(
  523. DESIGN_ATTR_PLAIN_TEXT, attr, Boolean.class);
  524. if (plain == null || !plain) {
  525. setHtmlContentAllowed(true);
  526. content = design.html();
  527. } else {
  528. // content is not intended to be interpreted as HTML,
  529. // so html entities need to be decoded
  530. content = DesignFormatter.decodeFromTextNode(design.html());
  531. }
  532. setCaption(content);
  533. if (attr.hasKey("icon-alt")) {
  534. setIconAlternateText(DesignAttributeHandler.readAttribute(
  535. "icon-alt", attr, String.class));
  536. }
  537. // click-shortcut
  538. removeClickShortcut();
  539. ShortcutAction action = DesignAttributeHandler.readAttribute(
  540. "click-shortcut", attr, ShortcutAction.class);
  541. if (action != null) {
  542. setClickShortcut(action.getKeyCode(), action.getModifiers());
  543. }
  544. }
  545. /*
  546. * (non-Javadoc)
  547. *
  548. * @see com.vaadin.ui.AbstractComponent#getCustomAttributes()
  549. */
  550. @Override
  551. protected Collection<String> getCustomAttributes() {
  552. Collection<String> result = super.getCustomAttributes();
  553. result.add(DESIGN_ATTR_PLAIN_TEXT);
  554. result.add("caption");
  555. result.add("icon-alt");
  556. result.add("icon-alternate-text");
  557. result.add("click-shortcut");
  558. result.add("html-content-allowed");
  559. result.add("caption-as-html");
  560. return result;
  561. }
  562. /*
  563. * (non-Javadoc)
  564. *
  565. * @see com.vaadin.ui.AbstractComponent#writeDesign(org.jsoup.nodes.Element
  566. * , com.vaadin.ui.declarative.DesignContext)
  567. */
  568. @Override
  569. public void writeDesign(Element design, DesignContext designContext) {
  570. super.writeDesign(design, designContext);
  571. Attributes attr = design.attributes();
  572. Button def = (Button) designContext.getDefaultInstance(this);
  573. String content = getCaption();
  574. if (content != null) {
  575. design.html(content);
  576. }
  577. // plain-text (default is html)
  578. if (!isHtmlContentAllowed()) {
  579. design.attr(DESIGN_ATTR_PLAIN_TEXT, true);
  580. // encode HTML entities
  581. if (content != null) {
  582. design.html(DesignFormatter.encodeForTextNode(content));
  583. }
  584. }
  585. // icon-alt
  586. DesignAttributeHandler.writeAttribute("icon-alt", attr,
  587. getIconAlternateText(), def.getIconAlternateText(),
  588. String.class);
  589. // click-shortcut
  590. if (clickShortcut != null) {
  591. DesignAttributeHandler.writeAttribute("click-shortcut", attr,
  592. clickShortcut, null, ShortcutAction.class);
  593. }
  594. }
  595. }