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 22KB

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