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.

MenuBar.java 38KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213
  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.ui;
  17. import java.io.Serializable;
  18. import java.util.ArrayDeque;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Deque;
  22. import java.util.List;
  23. import java.util.Map;
  24. import org.jsoup.nodes.Attributes;
  25. import org.jsoup.nodes.Element;
  26. import org.jsoup.nodes.Node;
  27. import org.jsoup.parser.Tag;
  28. import com.vaadin.server.AbstractClientConnector;
  29. import com.vaadin.server.EventTrigger;
  30. import com.vaadin.server.PaintException;
  31. import com.vaadin.server.PaintTarget;
  32. import com.vaadin.server.Resource;
  33. import com.vaadin.shared.ui.ContentMode;
  34. import com.vaadin.shared.ui.menubar.MenuBarConstants;
  35. import com.vaadin.shared.ui.menubar.MenuBarState;
  36. import com.vaadin.ui.Component.Focusable;
  37. import com.vaadin.ui.declarative.DesignAttributeHandler;
  38. import com.vaadin.ui.declarative.DesignContext;
  39. /**
  40. * <p>
  41. * A class representing a horizontal menu bar. The menu can contain MenuItem
  42. * objects, which in turn can contain more MenuBars. These sub-level MenuBars
  43. * are represented as vertical menu.
  44. * </p>
  45. */
  46. @SuppressWarnings("serial")
  47. public class MenuBar extends AbstractComponent
  48. implements LegacyComponent, Focusable {
  49. // Items of the top-level menu
  50. private final List<MenuItem> menuItems;
  51. // Number of items in this menu
  52. private int numberOfItems = 0;
  53. private MenuItem moreItem;
  54. private boolean openRootOnHover;
  55. private boolean htmlContentAllowed;
  56. @Override
  57. protected MenuBarState getState() {
  58. return (MenuBarState) super.getState();
  59. }
  60. @Override
  61. protected MenuBarState getState(boolean markAsDirty) {
  62. return (MenuBarState) super.getState(markAsDirty);
  63. }
  64. /** Paint (serialize) the component for the client. */
  65. @Override
  66. public void paintContent(PaintTarget target) throws PaintException {
  67. target.addAttribute(MenuBarConstants.OPEN_ROOT_MENU_ON_HOWER,
  68. openRootOnHover);
  69. if (isHtmlContentAllowed()) {
  70. target.addAttribute(MenuBarConstants.HTML_CONTENT_ALLOWED, true);
  71. }
  72. target.startTag("options");
  73. if (getWidth() > -1) {
  74. target.startTag("moreItem");
  75. target.addAttribute("text", moreItem.getText());
  76. if (moreItem.getIcon() != null) {
  77. target.addAttribute("icon", moreItem.getIcon());
  78. }
  79. target.endTag("moreItem");
  80. }
  81. target.endTag("options");
  82. target.startTag("items");
  83. // This generates the tree from the contents of the menu
  84. for (MenuItem item : menuItems) {
  85. paintItem(target, item);
  86. }
  87. target.endTag("items");
  88. }
  89. private void paintItem(PaintTarget target, MenuItem item)
  90. throws PaintException {
  91. if (!item.isVisible()) {
  92. return;
  93. }
  94. target.startTag("item");
  95. target.addAttribute("id", item.getId());
  96. if (item.getStyleName() != null) {
  97. target.addAttribute(MenuBarConstants.ATTRIBUTE_ITEM_STYLE,
  98. item.getStyleName());
  99. }
  100. if (item.isSeparator()) {
  101. target.addAttribute("separator", true);
  102. } else {
  103. target.addAttribute("text", item.getText());
  104. Command command = item.getCommand();
  105. if (command != null) {
  106. target.addAttribute("command", true);
  107. }
  108. Resource icon = item.getIcon();
  109. if (icon != null) {
  110. target.addAttribute(MenuBarConstants.ATTRIBUTE_ITEM_ICON, icon);
  111. }
  112. if (!item.isEnabled()) {
  113. target.addAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DISABLED,
  114. true);
  115. }
  116. String description = item.getDescription();
  117. if (description != null && !description.isEmpty()) {
  118. target.addAttribute(MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION,
  119. description);
  120. }
  121. ContentMode contentMode = item.getDescriptionContentMode();
  122. // If the contentMode is equal to ContentMode.PREFORMATTED, we don't
  123. // add any attribute.
  124. if (contentMode != null
  125. && contentMode != ContentMode.PREFORMATTED) {
  126. target.addAttribute(
  127. MenuBarConstants.ATTRIBUTE_ITEM_DESCRIPTION_CONTENT_MODE,
  128. contentMode.name());
  129. }
  130. if (item.isCheckable()) {
  131. // if the "checked" attribute is present (either true or false),
  132. // the item is checkable
  133. target.addAttribute(MenuBarConstants.ATTRIBUTE_CHECKED,
  134. item.isChecked());
  135. }
  136. if (item.hasChildren()) {
  137. for (MenuItem child : item.getChildren()) {
  138. paintItem(target, child);
  139. }
  140. }
  141. }
  142. target.endTag("item");
  143. }
  144. /** De-serialize changes received from client. */
  145. @Override
  146. public void changeVariables(Object source, Map<String, Object> variables) {
  147. final Deque<MenuItem> items = new ArrayDeque<>();
  148. boolean found = false;
  149. if (variables.containsKey("clickedId")) {
  150. Integer clickedId = (Integer) variables.get("clickedId");
  151. for (MenuItem i : getItems()) {
  152. items.push(i);
  153. }
  154. MenuItem tmpItem = null;
  155. // Go through all the items in the menu
  156. while (!found && !items.isEmpty()) {
  157. tmpItem = items.pop();
  158. found = (clickedId == tmpItem.getId());
  159. if (tmpItem.hasChildren()) {
  160. for (MenuItem i : tmpItem.getChildren()) {
  161. items.push(i);
  162. }
  163. }
  164. } // while
  165. // If we got the clicked item, launch the command.
  166. if (found && tmpItem.isEnabled()) {
  167. if (tmpItem.isCheckable()) {
  168. tmpItem.setChecked(!tmpItem.isChecked());
  169. }
  170. if (null != tmpItem.getCommand()) {
  171. tmpItem.getCommand().menuSelected(tmpItem);
  172. }
  173. }
  174. }
  175. }
  176. /**
  177. * Constructs an empty, horizontal menu.
  178. */
  179. public MenuBar() {
  180. menuItems = new ArrayList<>();
  181. setMoreMenuItem(null);
  182. }
  183. /**
  184. * Adds a new menu item to the menu bar
  185. * <p>
  186. * Clicking on this menu item has no effect. Use
  187. * {@link #addItem(String, Command)} or {@link MenuItem#setCommand(Command)}
  188. * to assign an action to the menu item.
  189. *
  190. * @param caption
  191. * the text for the menu item
  192. * @throws IllegalArgumentException
  193. *
  194. * @since 8.4
  195. */
  196. public MenuBar.MenuItem addItem(String caption) {
  197. return addItem(caption, null, null);
  198. }
  199. /**
  200. * Add a new item to the menu bar. Command can be null, but a caption must
  201. * be given.
  202. *
  203. * @param caption
  204. * the text for the menu item
  205. * @param command
  206. * the command for the menu item
  207. * @throws IllegalArgumentException
  208. */
  209. public MenuBar.MenuItem addItem(String caption, MenuBar.Command command) {
  210. return addItem(caption, null, command);
  211. }
  212. /**
  213. * Add a new item to the menu bar. Icon and command can be null, but a
  214. * caption must be given.
  215. *
  216. * @param caption
  217. * the text for the menu item
  218. * @param icon
  219. * the icon for the menu item
  220. * @param command
  221. * the command for the menu item
  222. * @throws IllegalArgumentException
  223. */
  224. public MenuBar.MenuItem addItem(String caption, Resource icon,
  225. MenuBar.Command command) {
  226. if (caption == null) {
  227. throw new IllegalArgumentException("caption cannot be null");
  228. }
  229. MenuItem newItem = new MenuItem(caption, icon, command);
  230. menuItems.add(newItem);
  231. markAsDirty();
  232. return newItem;
  233. }
  234. /**
  235. * Add an item before some item. If the given item does not exist the item
  236. * is added at the end of the menu. Icon and command can be null, but a
  237. * caption must be given.
  238. *
  239. * @param caption
  240. * the text for the menu item
  241. * @param icon
  242. * the icon for the menu item
  243. * @param command
  244. * the command for the menu item
  245. * @param itemToAddBefore
  246. * the item that will be after the new item
  247. * @throws IllegalArgumentException
  248. */
  249. public MenuBar.MenuItem addItemBefore(String caption, Resource icon,
  250. MenuBar.Command command, MenuBar.MenuItem itemToAddBefore) {
  251. if (caption == null) {
  252. throw new IllegalArgumentException("caption cannot be null");
  253. }
  254. MenuItem newItem = new MenuItem(caption, icon, command);
  255. if (menuItems.contains(itemToAddBefore)) {
  256. int index = menuItems.indexOf(itemToAddBefore);
  257. menuItems.add(index, newItem);
  258. } else {
  259. menuItems.add(newItem);
  260. }
  261. markAsDirty();
  262. return newItem;
  263. }
  264. /**
  265. * Returns a list with all the MenuItem objects in the menu bar.
  266. *
  267. * @return a list containing the MenuItem objects in the menu bar
  268. */
  269. public List<MenuItem> getItems() {
  270. return menuItems;
  271. }
  272. /**
  273. * Remove first occurrence the specified item from the main menu.
  274. *
  275. * @param item
  276. * The item to be removed
  277. */
  278. public void removeItem(MenuBar.MenuItem item) {
  279. if (item != null) {
  280. menuItems.remove(item);
  281. }
  282. markAsDirty();
  283. }
  284. /**
  285. * Empty the menu bar.
  286. */
  287. public void removeItems() {
  288. menuItems.clear();
  289. markAsDirty();
  290. }
  291. /**
  292. * Returns the size of the menu.
  293. *
  294. * @return The size of the menu
  295. */
  296. public int getSize() {
  297. return menuItems.size();
  298. }
  299. /**
  300. * Set the item that is used when collapsing the top level menu. All
  301. * "overflowing" items will be added below this. The item command will be
  302. * ignored. If set to null, the default item with a downwards arrow is used.
  303. *
  304. * The item command (if specified) is ignored.
  305. *
  306. * @param item
  307. */
  308. public void setMoreMenuItem(MenuItem item) {
  309. if (item != null) {
  310. moreItem = item;
  311. } else {
  312. moreItem = new MenuItem("", null, null);
  313. }
  314. markAsDirty();
  315. }
  316. /**
  317. * Get the MenuItem used as the collapse menu item.
  318. *
  319. * @return
  320. */
  321. public MenuItem getMoreMenuItem() {
  322. return moreItem;
  323. }
  324. /**
  325. * Using this method menubar can be put into a special mode where top level
  326. * menus opens without clicking on the menu, but automatically when mouse
  327. * cursor is moved over the menu. In this mode the menu also closes itself
  328. * if the mouse is moved out of the opened menu.
  329. * <p>
  330. * Note, that on touch devices the menu still opens on a click event.
  331. *
  332. * @param autoOpenTopLevelMenu
  333. * true if menus should be opened without click, the default is
  334. * false
  335. */
  336. public void setAutoOpen(boolean autoOpenTopLevelMenu) {
  337. if (autoOpenTopLevelMenu != openRootOnHover) {
  338. openRootOnHover = autoOpenTopLevelMenu;
  339. markAsDirty();
  340. }
  341. }
  342. /**
  343. * Detects whether the menubar is in a mode where top level menus are
  344. * automatically opened when the mouse cursor is moved over the menu.
  345. * Normally root menu opens only by clicking on the menu. Submenus always
  346. * open automatically.
  347. *
  348. * @return true if the root menus open without click, the default is false
  349. */
  350. public boolean isAutoOpen() {
  351. return openRootOnHover;
  352. }
  353. /**
  354. * Sets whether html is allowed in the item captions. If set to true, the
  355. * captions are passed to the browser as html and the developer is
  356. * responsible for ensuring no harmful html is used. If set to false, the
  357. * content is passed to the browser as plain text.
  358. *
  359. * @param htmlContentAllowed
  360. * true if the captions are used as html, false if used as plain
  361. * text
  362. */
  363. public void setHtmlContentAllowed(boolean htmlContentAllowed) {
  364. this.htmlContentAllowed = htmlContentAllowed;
  365. markAsDirty();
  366. }
  367. /**
  368. * Checks whether item captions are interpreted as html or plain text.
  369. *
  370. * @return true if the captions are used as html, false if used as plain
  371. * text
  372. * @see #setHtmlContentAllowed(boolean)
  373. */
  374. public boolean isHtmlContentAllowed() {
  375. return htmlContentAllowed;
  376. }
  377. @Override
  378. public int getTabIndex() {
  379. return getState(false).tabIndex;
  380. }
  381. /*
  382. * (non-Javadoc)
  383. *
  384. * @see com.vaadin.ui.Component.Focusable#setTabIndex(int)
  385. */
  386. @Override
  387. public void setTabIndex(int tabIndex) {
  388. getState().tabIndex = tabIndex;
  389. }
  390. @Override
  391. public void focus() {
  392. // Overridden only to make public
  393. super.focus();
  394. }
  395. /**
  396. * This interface contains the layer for menu commands of the
  397. * {@link com.vaadin.ui.MenuBar} class. It's method will fire when the user
  398. * clicks on the containing {@link com.vaadin.ui.MenuBar.MenuItem}. The
  399. * selected item is given as an argument.
  400. */
  401. @FunctionalInterface
  402. public interface Command extends Serializable {
  403. public void menuSelected(MenuBar.MenuItem selectedItem);
  404. }
  405. /**
  406. * A composite class for menu items and sub-menus. You can set commands to
  407. * be fired on user click by implementing the
  408. * {@link com.vaadin.ui.MenuBar.Command} interface. You can also add
  409. * multiple MenuItems to a MenuItem and create a sub-menu.
  410. *
  411. */
  412. public class MenuItem implements Serializable, EventTrigger {
  413. /** Private members * */
  414. private final int itsId;
  415. private Command itsCommand;
  416. private String itsText;
  417. private List<MenuItem> itsChildren;
  418. private Resource itsIcon;
  419. private MenuItem itsParent;
  420. private boolean enabled = true;
  421. private boolean visible = true;
  422. private boolean isSeparator = false;
  423. private String styleName;
  424. private String description;
  425. private ContentMode descriptionContentMode = ContentMode.PREFORMATTED;
  426. private boolean checkable = false;
  427. private boolean checked = false;
  428. /**
  429. * Constructs a new menu item that can optionally have an icon and a
  430. * command associated with it. Icon and command can be null, but a
  431. * caption must be given.
  432. *
  433. * @param caption
  434. * The text associated with the command
  435. * @param command
  436. * The command to be fired
  437. * @throws IllegalArgumentException
  438. */
  439. public MenuItem(String caption, Resource icon,
  440. MenuBar.Command command) {
  441. if (caption == null) {
  442. throw new IllegalArgumentException("caption cannot be null");
  443. }
  444. itsId = ++numberOfItems;
  445. itsText = caption;
  446. itsIcon = icon;
  447. itsCommand = command;
  448. }
  449. /**
  450. * Checks if the item has children (if it is a sub-menu).
  451. *
  452. * @return True if this item has children
  453. */
  454. public boolean hasChildren() {
  455. return !isSeparator() && itsChildren != null;
  456. }
  457. /**
  458. * Adds a separator to this menu. A separator is a way to visually group
  459. * items in a menu, to make it easier for users to find what they are
  460. * looking for in the menu.
  461. *
  462. * @author Jouni Koivuviita / Vaadin Ltd.
  463. * @since 6.2.0
  464. */
  465. public MenuBar.MenuItem addSeparator() {
  466. MenuItem item = addItem("", null, null);
  467. item.setSeparator(true);
  468. return item;
  469. }
  470. public MenuBar.MenuItem addSeparatorBefore(MenuItem itemToAddBefore) {
  471. MenuItem item = addItemBefore("", null, null, itemToAddBefore);
  472. item.setSeparator(true);
  473. return item;
  474. }
  475. /**
  476. * Add a new menu item inside this menu item, creating a sub-menu.
  477. * <p>
  478. * Clicking on the new item has no effect. Use
  479. * {@link #addItem(String, Command)} or {@link #setCommand(Command)} to
  480. * assign an action to the menu item.
  481. *
  482. * @param caption
  483. * the text for the menu item
  484. *
  485. * @since 8.4
  486. */
  487. public MenuBar.MenuItem addItem(String caption) {
  488. return addItem(caption, null, null);
  489. }
  490. /**
  491. * Add a new item inside this item, thus creating a sub-menu. Command
  492. * can be null, but a caption must be given.
  493. *
  494. * @param caption
  495. * the text for the menu item
  496. * @param command
  497. * the command for the menu item
  498. */
  499. public MenuBar.MenuItem addItem(String caption,
  500. MenuBar.Command command) {
  501. return addItem(caption, null, command);
  502. }
  503. /**
  504. * Add a new item inside this item, thus creating a sub-menu. Icon and
  505. * command can be null, but a caption must be given.
  506. *
  507. * @param caption
  508. * the text for the menu item
  509. * @param icon
  510. * the icon for the menu item
  511. * @param command
  512. * the command for the menu item
  513. * @throws IllegalStateException
  514. * If the item is checkable and thus cannot have children.
  515. */
  516. public MenuBar.MenuItem addItem(String caption, Resource icon,
  517. MenuBar.Command command) throws IllegalStateException {
  518. if (isSeparator()) {
  519. throw new UnsupportedOperationException(
  520. "Cannot add items to a separator");
  521. }
  522. if (isCheckable()) {
  523. throw new IllegalStateException(
  524. "A checkable item cannot have children");
  525. }
  526. if (caption == null) {
  527. throw new IllegalArgumentException("Caption cannot be null");
  528. }
  529. if (itsChildren == null) {
  530. itsChildren = new ArrayList<>();
  531. }
  532. MenuItem newItem = new MenuItem(caption, icon, command);
  533. // The only place where the parent is set
  534. newItem.setParent(this);
  535. itsChildren.add(newItem);
  536. markAsDirty();
  537. return newItem;
  538. }
  539. /**
  540. * Add an item before some item. If the given item does not exist the
  541. * item is added at the end of the menu. Icon and command can be null,
  542. * but a caption must be given.
  543. *
  544. * @param caption
  545. * the text for the menu item
  546. * @param icon
  547. * the icon for the menu item
  548. * @param command
  549. * the command for the menu item
  550. * @param itemToAddBefore
  551. * the item that will be after the new item
  552. * @throws IllegalStateException
  553. * If the item is checkable and thus cannot have children.
  554. */
  555. public MenuBar.MenuItem addItemBefore(String caption, Resource icon,
  556. MenuBar.Command command, MenuBar.MenuItem itemToAddBefore)
  557. throws IllegalStateException {
  558. if (isCheckable()) {
  559. throw new IllegalStateException(
  560. "A checkable item cannot have children");
  561. }
  562. MenuItem newItem = null;
  563. if (hasChildren() && itsChildren.contains(itemToAddBefore)) {
  564. int index = itsChildren.indexOf(itemToAddBefore);
  565. newItem = new MenuItem(caption, icon, command);
  566. newItem.setParent(this);
  567. itsChildren.add(index, newItem);
  568. } else {
  569. newItem = addItem(caption, icon, command);
  570. }
  571. markAsDirty();
  572. return newItem;
  573. }
  574. /**
  575. * For the associated command.
  576. *
  577. * @return The associated command, or null if there is none
  578. */
  579. public Command getCommand() {
  580. return itsCommand;
  581. }
  582. /**
  583. * Gets the objects icon.
  584. *
  585. * @return The icon of the item, null if the item doesn't have an icon
  586. */
  587. public Resource getIcon() {
  588. return itsIcon;
  589. }
  590. /**
  591. * For the containing item. This will return null if the item is in the
  592. * top-level menu bar.
  593. *
  594. * @return The containing {@link com.vaadin.ui.MenuBar.MenuItem} , or
  595. * null if there is none
  596. */
  597. public MenuBar.MenuItem getParent() {
  598. return itsParent;
  599. }
  600. /**
  601. * This will return the children of this item or null if there are none.
  602. *
  603. * @return List of children items, or null if there are none
  604. */
  605. public List<MenuItem> getChildren() {
  606. return itsChildren;
  607. }
  608. /**
  609. * Gets the objects text.
  610. *
  611. * @return The text
  612. */
  613. public java.lang.String getText() {
  614. return itsText;
  615. }
  616. /**
  617. * Returns the number of children.
  618. *
  619. * @return The number of child items
  620. */
  621. public int getSize() {
  622. if (itsChildren != null) {
  623. return itsChildren.size();
  624. }
  625. return -1;
  626. }
  627. /**
  628. * Get the unique identifier for this item.
  629. *
  630. * @return The id of this item
  631. */
  632. public int getId() {
  633. return itsId;
  634. }
  635. /**
  636. * Set the command for this item. Set null to remove.
  637. *
  638. * @param command
  639. * The MenuCommand of this item
  640. */
  641. public void setCommand(MenuBar.Command command) {
  642. itsCommand = command;
  643. }
  644. /**
  645. * Sets the icon. Set null to remove.
  646. *
  647. * @param icon
  648. * The icon for this item
  649. */
  650. public void setIcon(Resource icon) {
  651. itsIcon = icon;
  652. markAsDirty();
  653. }
  654. /**
  655. * Set the text of this object.
  656. *
  657. * @param text
  658. * Text for this object
  659. */
  660. public void setText(java.lang.String text) {
  661. if (text != null) {
  662. itsText = text;
  663. }
  664. markAsDirty();
  665. }
  666. /**
  667. * Remove the first occurrence of the item.
  668. *
  669. * @param item
  670. * The item to be removed
  671. */
  672. public void removeChild(MenuBar.MenuItem item) {
  673. if (item != null && itsChildren != null) {
  674. itsChildren.remove(item);
  675. if (itsChildren.isEmpty()) {
  676. itsChildren = null;
  677. }
  678. markAsDirty();
  679. }
  680. }
  681. /**
  682. * Empty the list of children items.
  683. */
  684. public void removeChildren() {
  685. if (itsChildren != null) {
  686. itsChildren.clear();
  687. itsChildren = null;
  688. markAsDirty();
  689. }
  690. }
  691. /**
  692. * Set the parent of this item. This is called by the addItem method.
  693. *
  694. * @param parent
  695. * The parent item
  696. */
  697. protected void setParent(MenuBar.MenuItem parent) {
  698. itsParent = parent;
  699. }
  700. public void setEnabled(boolean enabled) {
  701. this.enabled = enabled;
  702. markAsDirty();
  703. }
  704. public boolean isEnabled() {
  705. return enabled;
  706. }
  707. public void setVisible(boolean visible) {
  708. this.visible = visible;
  709. markAsDirty();
  710. }
  711. public boolean isVisible() {
  712. return visible;
  713. }
  714. private void setSeparator(boolean isSeparator) {
  715. this.isSeparator = isSeparator;
  716. markAsDirty();
  717. }
  718. public boolean isSeparator() {
  719. return isSeparator;
  720. }
  721. public void setStyleName(String styleName) {
  722. this.styleName = styleName;
  723. markAsDirty();
  724. }
  725. public String getStyleName() {
  726. return styleName;
  727. }
  728. /**
  729. * Analogous method to {@link AbstractComponent#setDescription(String)}.
  730. * Sets the item's description. See {@link #getDescription()} for more
  731. * information on what the description is.
  732. *
  733. * @param description
  734. * the new description string for the component.
  735. */
  736. public void setDescription(String description) {
  737. setDescription(description, ContentMode.PREFORMATTED);
  738. }
  739. /**
  740. * Analogous method to
  741. * {@link AbstractComponent#setDescription(String, ContentMode)}. Sets
  742. * the item's description using given content mode. See
  743. * {@link #getDescription()} for more information on what the
  744. * description is.
  745. * <p>
  746. * If the content {@code mode} is {@literal ContentMode.HTML} the
  747. * description is displayed as HTML in tooltips or directly in certain
  748. * components so care should be taken to avoid creating the possibility
  749. * for HTML injection and possibly XSS vulnerabilities.
  750. *
  751. * @see ContentMode
  752. *
  753. * @param description
  754. * the new description string for the component.
  755. * @param mode
  756. * the content mode for the description
  757. * @since 8.3
  758. */
  759. public void setDescription(String description, ContentMode mode) {
  760. this.description = description;
  761. this.descriptionContentMode = mode;
  762. markAsDirty();
  763. }
  764. /**
  765. * <p>
  766. * Gets the item's description. The description can be used to briefly
  767. * describe the state of the item to the user. The description string
  768. * may contain certain XML tags:
  769. * </p>
  770. *
  771. * <p>
  772. * <table border=1>
  773. * <tr>
  774. * <td width=120><b>Tag</b></td>
  775. * <td width=120><b>Description</b></td>
  776. * <td width=120><b>Example</b></td>
  777. * </tr>
  778. * <tr>
  779. * <td>&lt;b></td>
  780. * <td>bold</td>
  781. * <td><b>bold text</b></td>
  782. * </tr>
  783. * <tr>
  784. * <td>&lt;i></td>
  785. * <td>italic</td>
  786. * <td><i>italic text</i></td>
  787. * </tr>
  788. * <tr>
  789. * <td>&lt;u></td>
  790. * <td>underlined</td>
  791. * <td><u>underlined text</u></td>
  792. * </tr>
  793. * <tr>
  794. * <td>&lt;br></td>
  795. * <td>linebreak</td>
  796. * <td>N/A</td>
  797. * </tr>
  798. * <tr>
  799. * <td>&lt;ul><br>
  800. * &lt;li>item1<br>
  801. * &lt;li>item1<br>
  802. * &lt;/ul></td>
  803. * <td>item list</td>
  804. * <td>
  805. * <ul>
  806. * <li>item1
  807. * <li>item2
  808. * </ul>
  809. * </td>
  810. * </tr>
  811. * </table>
  812. * </p>
  813. *
  814. * <p>
  815. * These tags may be nested.
  816. * </p>
  817. *
  818. * @return item's description <code>String</code>
  819. */
  820. public String getDescription() {
  821. return description;
  822. }
  823. /**
  824. * Gets the content mode of the description of the menu item. The
  825. * description is displayed as the tooltip of the menu item in the UI.
  826. * <p>
  827. * If no content mode was explicitly set using the
  828. * {@link #setDescription(String, ContentMode)} method, the content mode
  829. * will be {@link ContentMode#PREFORMATTED}
  830. * </p>
  831. *
  832. * @return the {@link ContentMode} of the description of this menu item
  833. * @see ContentMode
  834. * @since 8.3
  835. */
  836. public ContentMode getDescriptionContentMode() {
  837. return descriptionContentMode;
  838. }
  839. /**
  840. * Gets the checkable state of the item - whether the item has checked
  841. * and unchecked states. If an item is checkable its checked state (as
  842. * returned by {@link #isChecked()}) is indicated in the UI.
  843. *
  844. * <p>
  845. * An item is not checkable by default.
  846. * </p>
  847. *
  848. * @return true if the item is checkable, false otherwise
  849. * @since 6.6.2
  850. */
  851. public boolean isCheckable() {
  852. return checkable;
  853. }
  854. /**
  855. * Sets the checkable state of the item. If an item is checkable its
  856. * checked state (as returned by {@link #isChecked()}) is indicated in
  857. * the UI.
  858. *
  859. * <p>
  860. * An item is not checkable by default.
  861. * </p>
  862. *
  863. * <p>
  864. * Items with sub items cannot be checkable.
  865. * </p>
  866. *
  867. * @param checkable
  868. * true if the item should be checkable, false otherwise
  869. * @throws IllegalStateException
  870. * If the item has children
  871. * @since 6.6.2
  872. */
  873. public void setCheckable(boolean checkable)
  874. throws IllegalStateException {
  875. if (hasChildren()) {
  876. throw new IllegalStateException(
  877. "A menu item with children cannot be checkable");
  878. }
  879. this.checkable = checkable;
  880. markAsDirty();
  881. }
  882. /**
  883. * Gets the checked state of the item (checked or unchecked). Only used
  884. * if the item is checkable (as indicated by {@link #isCheckable()}).
  885. * The checked state is indicated in the UI with the item, if the item
  886. * is checkable.
  887. *
  888. * <p>
  889. * An item is not checked by default.
  890. * </p>
  891. *
  892. * <p>
  893. * The CSS style corresponding to the checked state is "-checked".
  894. * </p>
  895. *
  896. * @return true if the item is checked, false otherwise
  897. * @since 6.6.2
  898. */
  899. public boolean isChecked() {
  900. return checked;
  901. }
  902. /**
  903. * Sets the checked state of the item. Only used if the item is
  904. * checkable (indicated by {@link #isCheckable()}). The checked state is
  905. * indicated in the UI with the item, if the item is checkable.
  906. *
  907. * <p>
  908. * An item is not checked by default.
  909. * </p>
  910. *
  911. * <p>
  912. * The CSS style corresponding to the checked state is "-checked".
  913. * </p>
  914. *
  915. * @since 6.6.2
  916. */
  917. public void setChecked(boolean checked) {
  918. this.checked = checked;
  919. markAsDirty();
  920. }
  921. /**
  922. * Gets the menu bar this item is part of.
  923. *
  924. * @return the menu bar this item is attached to
  925. * @since 8.4
  926. */
  927. public MenuBar getMenuBar() {
  928. return MenuBar.this;
  929. }
  930. @Override
  931. public AbstractClientConnector getConnector() {
  932. return getMenuBar();
  933. }
  934. @Override
  935. public String getPartInformation() {
  936. return String.valueOf(getId());
  937. }
  938. }
  939. @Override
  940. public void writeDesign(Element design, DesignContext designContext) {
  941. super.writeDesign(design, designContext);
  942. for (MenuItem item : getItems()) {
  943. design.appendChild(createMenuElement(item, designContext));
  944. }
  945. // in many cases there seems to be an empty more menu item
  946. if (getMoreMenuItem() != null
  947. && !getMoreMenuItem().getText().isEmpty()) {
  948. Element moreMenu = createMenuElement(getMoreMenuItem(),
  949. designContext);
  950. moreMenu.attr("more", true);
  951. design.appendChild(moreMenu);
  952. }
  953. if (!htmlContentAllowed) {
  954. design.attr(DESIGN_ATTR_PLAIN_TEXT, true);
  955. }
  956. }
  957. protected Element createMenuElement(MenuItem item, DesignContext context) {
  958. Element menuElement = new Element(Tag.valueOf("menu"), "");
  959. // Defaults
  960. MenuItem def = new MenuItem("", null, null);
  961. Attributes attr = menuElement.attributes();
  962. DesignAttributeHandler.writeAttribute("icon", attr, item.getIcon(),
  963. def.getIcon(), Resource.class, context);
  964. DesignAttributeHandler.writeAttribute("disabled", attr,
  965. !item.isEnabled(), !def.isEnabled(), boolean.class, context);
  966. DesignAttributeHandler.writeAttribute("visible", attr, item.isVisible(),
  967. def.isVisible(), boolean.class, context);
  968. DesignAttributeHandler.writeAttribute("separator", attr,
  969. item.isSeparator(), def.isSeparator(), boolean.class, context);
  970. DesignAttributeHandler.writeAttribute("checkable", attr,
  971. item.isCheckable(), def.isCheckable(), boolean.class, context);
  972. DesignAttributeHandler.writeAttribute("checked", attr, item.isChecked(),
  973. def.isChecked(), boolean.class, context);
  974. DesignAttributeHandler.writeAttribute("description", attr,
  975. item.getDescription(), def.getDescription(), String.class,
  976. context);
  977. DesignAttributeHandler.writeAttribute("descriptioncontentmode", attr,
  978. item.getDescriptionContentMode().name(),
  979. def.getDescriptionContentMode().name(), String.class, context);
  980. DesignAttributeHandler.writeAttribute("style-name", attr,
  981. item.getStyleName(), def.getStyleName(), String.class, context);
  982. menuElement.append(item.getText());
  983. if (item.hasChildren()) {
  984. for (MenuItem subMenu : item.getChildren()) {
  985. menuElement.appendChild(createMenuElement(subMenu, context));
  986. }
  987. }
  988. return menuElement;
  989. }
  990. protected MenuItem readMenuElement(Element menuElement) {
  991. Resource icon = null;
  992. if (menuElement.hasAttr("icon")) {
  993. icon = DesignAttributeHandler.getFormatter()
  994. .parse(menuElement.attr("icon"), Resource.class);
  995. }
  996. String caption = "";
  997. List<Element> subMenus = new ArrayList<>();
  998. for (Node node : menuElement.childNodes()) {
  999. if (node instanceof Element
  1000. && ((Element) node).tagName().equals("menu")) {
  1001. subMenus.add((Element) node);
  1002. } else {
  1003. caption += node.toString();
  1004. }
  1005. }
  1006. MenuItem menu = new MenuItem(caption.trim(), icon, null);
  1007. Attributes attr = menuElement.attributes();
  1008. if (menuElement.hasAttr("icon")) {
  1009. menu.setIcon(DesignAttributeHandler.readAttribute("icon", attr,
  1010. Resource.class));
  1011. }
  1012. if (menuElement.hasAttr("disabled")) {
  1013. menu.setEnabled(!DesignAttributeHandler.readAttribute("disabled",
  1014. attr, boolean.class));
  1015. }
  1016. if (menuElement.hasAttr("visible")) {
  1017. menu.setVisible(DesignAttributeHandler.readAttribute("visible",
  1018. attr, boolean.class));
  1019. }
  1020. if (menuElement.hasAttr("separator")) {
  1021. menu.setSeparator(DesignAttributeHandler.readAttribute("separator",
  1022. attr, boolean.class));
  1023. }
  1024. if (menuElement.hasAttr("checkable")) {
  1025. menu.setCheckable(DesignAttributeHandler.readAttribute("checkable",
  1026. attr, boolean.class));
  1027. }
  1028. if (menuElement.hasAttr("checked")) {
  1029. menu.setChecked(DesignAttributeHandler.readAttribute("checked",
  1030. attr, boolean.class));
  1031. }
  1032. if (menuElement.hasAttr("description")) {
  1033. String description = DesignAttributeHandler
  1034. .readAttribute("description", attr, String.class);
  1035. if (menuElement.hasAttr("descriptioncontentmode")) {
  1036. String contentModeString = DesignAttributeHandler.readAttribute(
  1037. "descriptioncontentmode", attr, String.class);
  1038. menu.setDescription(description,
  1039. ContentMode.valueOf(contentModeString));
  1040. } else {
  1041. menu.setDescription(description);
  1042. }
  1043. }
  1044. if (menuElement.hasAttr("style-name")) {
  1045. menu.setStyleName(DesignAttributeHandler.readAttribute("style-name",
  1046. attr, String.class));
  1047. }
  1048. if (!subMenus.isEmpty()) {
  1049. menu.itsChildren = new ArrayList<>();
  1050. }
  1051. for (Element subMenu : subMenus) {
  1052. MenuItem newItem = readMenuElement(subMenu);
  1053. newItem.setParent(menu);
  1054. menu.itsChildren.add(newItem);
  1055. }
  1056. return menu;
  1057. }
  1058. @Override
  1059. public void readDesign(Element design, DesignContext designContext) {
  1060. super.readDesign(design, designContext);
  1061. for (Element itemElement : design.children()) {
  1062. if (itemElement.tagName().equals("menu")) {
  1063. MenuItem menuItem = readMenuElement(itemElement);
  1064. if (itemElement.hasAttr("more")) {
  1065. setMoreMenuItem(menuItem);
  1066. } else {
  1067. menuItems.add(menuItem);
  1068. }
  1069. }
  1070. }
  1071. setHtmlContentAllowed(!design.hasAttr(DESIGN_ATTR_PLAIN_TEXT));
  1072. }
  1073. @Override
  1074. protected Collection<String> getCustomAttributes() {
  1075. Collection<String> result = super.getCustomAttributes();
  1076. result.add(DESIGN_ATTR_PLAIN_TEXT);
  1077. result.add("html-content-allowed");
  1078. return result;
  1079. }
  1080. }