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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235
  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. /**
  391. * Returns the delay before executing update logic inside
  392. * {@link com.vaadin.client.ui.menubar.MenuBarConnector#updateFromUIDL(UIDL, ApplicationConnection)}
  393. * after mouseDownEvent
  394. *
  395. * @since
  396. */
  397. public int getDelayMs() {
  398. return getState(false).delayMs;
  399. }
  400. /**
  401. * Set the delay before executing update logic inside
  402. * {@link com.vaadin.client.ui.menubar.MenuBarConnector#updateFromUIDL(UIDL, ApplicationConnection)}
  403. * after mouseDownEvent
  404. *
  405. * @since
  406. */
  407. public void setDelayMs(int delayMs) {
  408. getState().delayMs = delayMs;
  409. }
  410. @Override
  411. public void focus() {
  412. // Overridden only to make public
  413. super.focus();
  414. }
  415. /**
  416. * This interface contains the layer for menu commands of the
  417. * {@link com.vaadin.ui.MenuBar} class. It's method will fire when the user
  418. * clicks on the containing {@link com.vaadin.ui.MenuBar.MenuItem}. The
  419. * selected item is given as an argument.
  420. */
  421. @FunctionalInterface
  422. public interface Command extends Serializable {
  423. public void menuSelected(MenuBar.MenuItem selectedItem);
  424. }
  425. /**
  426. * A composite class for menu items and sub-menus. You can set commands to
  427. * be fired on user click by implementing the
  428. * {@link com.vaadin.ui.MenuBar.Command} interface. You can also add
  429. * multiple MenuItems to a MenuItem and create a sub-menu.
  430. *
  431. */
  432. public class MenuItem implements Serializable, EventTrigger {
  433. /** Private members * */
  434. private final int itsId;
  435. private Command itsCommand;
  436. private String itsText;
  437. private List<MenuItem> itsChildren;
  438. private Resource itsIcon;
  439. private MenuItem itsParent;
  440. private boolean enabled = true;
  441. private boolean visible = true;
  442. private boolean isSeparator = false;
  443. private String styleName;
  444. private String description;
  445. private ContentMode descriptionContentMode = ContentMode.PREFORMATTED;
  446. private boolean checkable = false;
  447. private boolean checked = false;
  448. /**
  449. * Constructs a new menu item that can optionally have an icon and a
  450. * command associated with it. Icon and command can be null, but a
  451. * caption must be given.
  452. *
  453. * @param caption
  454. * The text associated with the command
  455. * @param command
  456. * The command to be fired
  457. * @throws IllegalArgumentException
  458. */
  459. public MenuItem(String caption, Resource icon,
  460. MenuBar.Command command) {
  461. if (caption == null) {
  462. throw new IllegalArgumentException("caption cannot be null");
  463. }
  464. itsId = ++numberOfItems;
  465. itsText = caption;
  466. itsIcon = icon;
  467. itsCommand = command;
  468. }
  469. /**
  470. * Checks if the item has children (if it is a sub-menu).
  471. *
  472. * @return True if this item has children
  473. */
  474. public boolean hasChildren() {
  475. return !isSeparator() && itsChildren != null;
  476. }
  477. /**
  478. * Adds a separator to this menu. A separator is a way to visually group
  479. * items in a menu, to make it easier for users to find what they are
  480. * looking for in the menu.
  481. *
  482. * @author Jouni Koivuviita / Vaadin Ltd.
  483. * @since 6.2.0
  484. */
  485. public MenuBar.MenuItem addSeparator() {
  486. MenuItem item = addItem("", null, null);
  487. item.setSeparator(true);
  488. return item;
  489. }
  490. public MenuBar.MenuItem addSeparatorBefore(MenuItem itemToAddBefore) {
  491. MenuItem item = addItemBefore("", null, null, itemToAddBefore);
  492. item.setSeparator(true);
  493. return item;
  494. }
  495. /**
  496. * Add a new menu item inside this menu item, creating a sub-menu.
  497. * <p>
  498. * Clicking on the new item has no effect. Use
  499. * {@link #addItem(String, Command)} or {@link #setCommand(Command)} to
  500. * assign an action to the menu item.
  501. *
  502. * @param caption
  503. * the text for the menu item
  504. *
  505. * @since 8.4
  506. */
  507. public MenuBar.MenuItem addItem(String caption) {
  508. return addItem(caption, null, null);
  509. }
  510. /**
  511. * Add a new item inside this item, thus creating a sub-menu. Command
  512. * can be null, but a caption must be given.
  513. *
  514. * @param caption
  515. * the text for the menu item
  516. * @param command
  517. * the command for the menu item
  518. */
  519. public MenuBar.MenuItem addItem(String caption,
  520. MenuBar.Command command) {
  521. return addItem(caption, null, command);
  522. }
  523. /**
  524. * Add a new item inside this item, thus creating a sub-menu. Icon and
  525. * command can be null, but a caption must be given.
  526. *
  527. * @param caption
  528. * the text for the menu item
  529. * @param icon
  530. * the icon for the menu item
  531. * @param command
  532. * the command for the menu item
  533. * @throws IllegalStateException
  534. * If the item is checkable and thus cannot have children.
  535. */
  536. public MenuBar.MenuItem addItem(String caption, Resource icon,
  537. MenuBar.Command command) throws IllegalStateException {
  538. if (isSeparator()) {
  539. throw new UnsupportedOperationException(
  540. "Cannot add items to a separator");
  541. }
  542. if (isCheckable()) {
  543. throw new IllegalStateException(
  544. "A checkable item cannot have children");
  545. }
  546. if (caption == null) {
  547. throw new IllegalArgumentException("Caption cannot be null");
  548. }
  549. if (itsChildren == null) {
  550. itsChildren = new ArrayList<>();
  551. }
  552. MenuItem newItem = new MenuItem(caption, icon, command);
  553. // The only place where the parent is set
  554. newItem.setParent(this);
  555. itsChildren.add(newItem);
  556. markAsDirty();
  557. return newItem;
  558. }
  559. /**
  560. * Add an item before some item. If the given item does not exist the
  561. * item is added at the end of the menu. Icon and command can be null,
  562. * but a caption must be given.
  563. *
  564. * @param caption
  565. * the text for the menu item
  566. * @param icon
  567. * the icon for the menu item
  568. * @param command
  569. * the command for the menu item
  570. * @param itemToAddBefore
  571. * the item that will be after the new item
  572. * @throws IllegalStateException
  573. * If the item is checkable and thus cannot have children.
  574. */
  575. public MenuBar.MenuItem addItemBefore(String caption, Resource icon,
  576. MenuBar.Command command, MenuBar.MenuItem itemToAddBefore)
  577. throws IllegalStateException {
  578. if (isCheckable()) {
  579. throw new IllegalStateException(
  580. "A checkable item cannot have children");
  581. }
  582. MenuItem newItem = null;
  583. if (hasChildren() && itsChildren.contains(itemToAddBefore)) {
  584. int index = itsChildren.indexOf(itemToAddBefore);
  585. newItem = new MenuItem(caption, icon, command);
  586. newItem.setParent(this);
  587. itsChildren.add(index, newItem);
  588. } else {
  589. newItem = addItem(caption, icon, command);
  590. }
  591. markAsDirty();
  592. return newItem;
  593. }
  594. /**
  595. * For the associated command.
  596. *
  597. * @return The associated command, or null if there is none
  598. */
  599. public Command getCommand() {
  600. return itsCommand;
  601. }
  602. /**
  603. * Gets the objects icon.
  604. *
  605. * @return The icon of the item, null if the item doesn't have an icon
  606. */
  607. public Resource getIcon() {
  608. return itsIcon;
  609. }
  610. /**
  611. * For the containing item. This will return null if the item is in the
  612. * top-level menu bar.
  613. *
  614. * @return The containing {@link com.vaadin.ui.MenuBar.MenuItem} , or
  615. * null if there is none
  616. */
  617. public MenuBar.MenuItem getParent() {
  618. return itsParent;
  619. }
  620. /**
  621. * This will return the children of this item or null if there are none.
  622. *
  623. * @return List of children items, or null if there are none
  624. */
  625. public List<MenuItem> getChildren() {
  626. return itsChildren;
  627. }
  628. /**
  629. * Gets the objects text.
  630. *
  631. * @return The text
  632. */
  633. public java.lang.String getText() {
  634. return itsText;
  635. }
  636. /**
  637. * Returns the number of children.
  638. *
  639. * @return The number of child items
  640. */
  641. public int getSize() {
  642. if (itsChildren != null) {
  643. return itsChildren.size();
  644. }
  645. return -1;
  646. }
  647. /**
  648. * Get the unique identifier for this item.
  649. *
  650. * @return The id of this item
  651. */
  652. public int getId() {
  653. return itsId;
  654. }
  655. /**
  656. * Set the command for this item. Set null to remove.
  657. *
  658. * @param command
  659. * The MenuCommand of this item
  660. */
  661. public void setCommand(MenuBar.Command command) {
  662. itsCommand = command;
  663. }
  664. /**
  665. * Sets the icon. Set null to remove.
  666. *
  667. * @param icon
  668. * The icon for this item
  669. */
  670. public void setIcon(Resource icon) {
  671. itsIcon = icon;
  672. markAsDirty();
  673. }
  674. /**
  675. * Set the text of this object.
  676. *
  677. * @param text
  678. * Text for this object
  679. */
  680. public void setText(java.lang.String text) {
  681. if (text != null) {
  682. itsText = text;
  683. }
  684. markAsDirty();
  685. }
  686. /**
  687. * Remove the first occurrence of the item.
  688. *
  689. * @param item
  690. * The item to be removed
  691. */
  692. public void removeChild(MenuBar.MenuItem item) {
  693. if (item != null && itsChildren != null) {
  694. itsChildren.remove(item);
  695. if (itsChildren.isEmpty()) {
  696. itsChildren = null;
  697. }
  698. markAsDirty();
  699. }
  700. }
  701. /**
  702. * Empty the list of children items.
  703. */
  704. public void removeChildren() {
  705. if (itsChildren != null) {
  706. itsChildren.clear();
  707. itsChildren = null;
  708. markAsDirty();
  709. }
  710. }
  711. /**
  712. * Set the parent of this item. This is called by the addItem method.
  713. *
  714. * @param parent
  715. * The parent item
  716. */
  717. protected void setParent(MenuBar.MenuItem parent) {
  718. itsParent = parent;
  719. }
  720. public void setEnabled(boolean enabled) {
  721. this.enabled = enabled;
  722. markAsDirty();
  723. }
  724. public boolean isEnabled() {
  725. return enabled;
  726. }
  727. public void setVisible(boolean visible) {
  728. this.visible = visible;
  729. markAsDirty();
  730. }
  731. public boolean isVisible() {
  732. return visible;
  733. }
  734. private void setSeparator(boolean isSeparator) {
  735. this.isSeparator = isSeparator;
  736. markAsDirty();
  737. }
  738. public boolean isSeparator() {
  739. return isSeparator;
  740. }
  741. public void setStyleName(String styleName) {
  742. this.styleName = styleName;
  743. markAsDirty();
  744. }
  745. public String getStyleName() {
  746. return styleName;
  747. }
  748. /**
  749. * Analogous method to {@link AbstractComponent#setDescription(String)}.
  750. * Sets the item's description. See {@link #getDescription()} for more
  751. * information on what the description is.
  752. *
  753. * @param description
  754. * the new description string for the component.
  755. */
  756. public void setDescription(String description) {
  757. setDescription(description, ContentMode.PREFORMATTED);
  758. }
  759. /**
  760. * Analogous method to
  761. * {@link AbstractComponent#setDescription(String, ContentMode)}. Sets
  762. * the item's description using given content mode. See
  763. * {@link #getDescription()} for more information on what the
  764. * description is.
  765. * <p>
  766. * If the content {@code mode} is {@literal ContentMode.HTML} the
  767. * description is displayed as HTML in tooltips or directly in certain
  768. * components so care should be taken to avoid creating the possibility
  769. * for HTML injection and possibly XSS vulnerabilities.
  770. *
  771. * @see ContentMode
  772. *
  773. * @param description
  774. * the new description string for the component.
  775. * @param mode
  776. * the content mode for the description
  777. * @since 8.3
  778. */
  779. public void setDescription(String description, ContentMode mode) {
  780. this.description = description;
  781. this.descriptionContentMode = mode;
  782. markAsDirty();
  783. }
  784. /**
  785. * <p>
  786. * Gets the item's description. The description can be used to briefly
  787. * describe the state of the item to the user. The description string
  788. * may contain certain XML tags:
  789. * </p>
  790. *
  791. * <p>
  792. * <table border=1>
  793. * <tr>
  794. * <td width=120><b>Tag</b></td>
  795. * <td width=120><b>Description</b></td>
  796. * <td width=120><b>Example</b></td>
  797. * </tr>
  798. * <tr>
  799. * <td>&lt;b></td>
  800. * <td>bold</td>
  801. * <td><b>bold text</b></td>
  802. * </tr>
  803. * <tr>
  804. * <td>&lt;i></td>
  805. * <td>italic</td>
  806. * <td><i>italic text</i></td>
  807. * </tr>
  808. * <tr>
  809. * <td>&lt;u></td>
  810. * <td>underlined</td>
  811. * <td><u>underlined text</u></td>
  812. * </tr>
  813. * <tr>
  814. * <td>&lt;br></td>
  815. * <td>linebreak</td>
  816. * <td>N/A</td>
  817. * </tr>
  818. * <tr>
  819. * <td>&lt;ul><br>
  820. * &lt;li>item1<br>
  821. * &lt;li>item1<br>
  822. * &lt;/ul></td>
  823. * <td>item list</td>
  824. * <td>
  825. * <ul>
  826. * <li>item1
  827. * <li>item2
  828. * </ul>
  829. * </td>
  830. * </tr>
  831. * </table>
  832. * </p>
  833. *
  834. * <p>
  835. * These tags may be nested.
  836. * </p>
  837. *
  838. * @return item's description <code>String</code>
  839. */
  840. public String getDescription() {
  841. return description;
  842. }
  843. /**
  844. * Gets the content mode of the description of the menu item. The
  845. * description is displayed as the tooltip of the menu item in the UI.
  846. * <p>
  847. * If no content mode was explicitly set using the
  848. * {@link #setDescription(String, ContentMode)} method, the content mode
  849. * will be {@link ContentMode#PREFORMATTED}
  850. * </p>
  851. *
  852. * @return the {@link ContentMode} of the description of this menu item
  853. * @see ContentMode
  854. * @since 8.3
  855. */
  856. public ContentMode getDescriptionContentMode() {
  857. return descriptionContentMode;
  858. }
  859. /**
  860. * Gets the checkable state of the item - whether the item has checked
  861. * and unchecked states. If an item is checkable its checked state (as
  862. * returned by {@link #isChecked()}) is indicated in the UI.
  863. *
  864. * <p>
  865. * An item is not checkable by default.
  866. * </p>
  867. *
  868. * @return true if the item is checkable, false otherwise
  869. * @since 6.6.2
  870. */
  871. public boolean isCheckable() {
  872. return checkable;
  873. }
  874. /**
  875. * Sets the checkable state of the item. If an item is checkable its
  876. * checked state (as returned by {@link #isChecked()}) is indicated in
  877. * the UI.
  878. *
  879. * <p>
  880. * An item is not checkable by default.
  881. * </p>
  882. *
  883. * <p>
  884. * Items with sub items cannot be checkable.
  885. * </p>
  886. *
  887. * @param checkable
  888. * true if the item should be checkable, false otherwise
  889. * @throws IllegalStateException
  890. * If the item has children
  891. * @since 6.6.2
  892. */
  893. public void setCheckable(boolean checkable)
  894. throws IllegalStateException {
  895. if (hasChildren()) {
  896. throw new IllegalStateException(
  897. "A menu item with children cannot be checkable");
  898. }
  899. this.checkable = checkable;
  900. markAsDirty();
  901. }
  902. /**
  903. * Gets the checked state of the item (checked or unchecked). Only used
  904. * if the item is checkable (as indicated by {@link #isCheckable()}).
  905. * The checked state is indicated in the UI with the item, if the item
  906. * is checkable.
  907. *
  908. * <p>
  909. * An item is not checked by default.
  910. * </p>
  911. *
  912. * <p>
  913. * The CSS style corresponding to the checked state is "-checked".
  914. * </p>
  915. *
  916. * @return true if the item is checked, false otherwise
  917. * @since 6.6.2
  918. */
  919. public boolean isChecked() {
  920. return checked;
  921. }
  922. /**
  923. * Sets the checked state of the item. Only used if the item is
  924. * checkable (indicated by {@link #isCheckable()}). The checked state is
  925. * indicated in the UI with the item, if the item is checkable.
  926. *
  927. * <p>
  928. * An item is not checked by default.
  929. * </p>
  930. *
  931. * <p>
  932. * The CSS style corresponding to the checked state is "-checked".
  933. * </p>
  934. *
  935. * @since 6.6.2
  936. */
  937. public void setChecked(boolean checked) {
  938. this.checked = checked;
  939. markAsDirty();
  940. }
  941. /**
  942. * Gets the menu bar this item is part of.
  943. *
  944. * @return the menu bar this item is attached to
  945. * @since 8.4
  946. */
  947. public MenuBar getMenuBar() {
  948. return MenuBar.this;
  949. }
  950. @Override
  951. public AbstractClientConnector getConnector() {
  952. return getMenuBar();
  953. }
  954. @Override
  955. public String getPartInformation() {
  956. return String.valueOf(getId());
  957. }
  958. }
  959. @Override
  960. public void writeDesign(Element design, DesignContext designContext) {
  961. super.writeDesign(design, designContext);
  962. for (MenuItem item : getItems()) {
  963. design.appendChild(createMenuElement(item, designContext));
  964. }
  965. // in many cases there seems to be an empty more menu item
  966. if (getMoreMenuItem() != null
  967. && !getMoreMenuItem().getText().isEmpty()) {
  968. Element moreMenu = createMenuElement(getMoreMenuItem(),
  969. designContext);
  970. moreMenu.attr("more", true);
  971. design.appendChild(moreMenu);
  972. }
  973. if (!htmlContentAllowed) {
  974. design.attr(DESIGN_ATTR_PLAIN_TEXT, true);
  975. }
  976. }
  977. protected Element createMenuElement(MenuItem item, DesignContext context) {
  978. Element menuElement = new Element(Tag.valueOf("menu"), "");
  979. // Defaults
  980. MenuItem def = new MenuItem("", null, null);
  981. Attributes attr = menuElement.attributes();
  982. DesignAttributeHandler.writeAttribute("icon", attr, item.getIcon(),
  983. def.getIcon(), Resource.class, context);
  984. DesignAttributeHandler.writeAttribute("disabled", attr,
  985. !item.isEnabled(), !def.isEnabled(), boolean.class, context);
  986. DesignAttributeHandler.writeAttribute("visible", attr, item.isVisible(),
  987. def.isVisible(), boolean.class, context);
  988. DesignAttributeHandler.writeAttribute("separator", attr,
  989. item.isSeparator(), def.isSeparator(), boolean.class, context);
  990. DesignAttributeHandler.writeAttribute("checkable", attr,
  991. item.isCheckable(), def.isCheckable(), boolean.class, context);
  992. DesignAttributeHandler.writeAttribute("checked", attr, item.isChecked(),
  993. def.isChecked(), boolean.class, context);
  994. DesignAttributeHandler.writeAttribute("description", attr,
  995. item.getDescription(), def.getDescription(), String.class,
  996. context);
  997. DesignAttributeHandler.writeAttribute("descriptioncontentmode", attr,
  998. item.getDescriptionContentMode().name(),
  999. def.getDescriptionContentMode().name(), String.class, context);
  1000. DesignAttributeHandler.writeAttribute("style-name", attr,
  1001. item.getStyleName(), def.getStyleName(), String.class, context);
  1002. menuElement.append(item.getText());
  1003. if (item.hasChildren()) {
  1004. for (MenuItem subMenu : item.getChildren()) {
  1005. menuElement.appendChild(createMenuElement(subMenu, context));
  1006. }
  1007. }
  1008. return menuElement;
  1009. }
  1010. protected MenuItem readMenuElement(Element menuElement) {
  1011. Resource icon = null;
  1012. if (menuElement.hasAttr("icon")) {
  1013. icon = DesignAttributeHandler.getFormatter()
  1014. .parse(menuElement.attr("icon"), Resource.class);
  1015. }
  1016. String caption = "";
  1017. List<Element> subMenus = new ArrayList<>();
  1018. for (Node node : menuElement.childNodes()) {
  1019. if (node instanceof Element
  1020. && ((Element) node).tagName().equals("menu")) {
  1021. subMenus.add((Element) node);
  1022. } else {
  1023. caption += node.toString();
  1024. }
  1025. }
  1026. MenuItem menu = new MenuItem(caption.trim(), icon, null);
  1027. Attributes attr = menuElement.attributes();
  1028. if (menuElement.hasAttr("icon")) {
  1029. menu.setIcon(DesignAttributeHandler.readAttribute("icon", attr,
  1030. Resource.class));
  1031. }
  1032. if (menuElement.hasAttr("disabled")) {
  1033. menu.setEnabled(!DesignAttributeHandler.readAttribute("disabled",
  1034. attr, boolean.class));
  1035. }
  1036. if (menuElement.hasAttr("visible")) {
  1037. menu.setVisible(DesignAttributeHandler.readAttribute("visible",
  1038. attr, boolean.class));
  1039. }
  1040. if (menuElement.hasAttr("separator")) {
  1041. menu.setSeparator(DesignAttributeHandler.readAttribute("separator",
  1042. attr, boolean.class));
  1043. }
  1044. if (menuElement.hasAttr("checkable")) {
  1045. menu.setCheckable(DesignAttributeHandler.readAttribute("checkable",
  1046. attr, boolean.class));
  1047. }
  1048. if (menuElement.hasAttr("checked")) {
  1049. menu.setChecked(DesignAttributeHandler.readAttribute("checked",
  1050. attr, boolean.class));
  1051. }
  1052. if (menuElement.hasAttr("description")) {
  1053. String description = DesignAttributeHandler
  1054. .readAttribute("description", attr, String.class);
  1055. if (menuElement.hasAttr("descriptioncontentmode")) {
  1056. String contentModeString = DesignAttributeHandler.readAttribute(
  1057. "descriptioncontentmode", attr, String.class);
  1058. menu.setDescription(description,
  1059. ContentMode.valueOf(contentModeString));
  1060. } else {
  1061. menu.setDescription(description);
  1062. }
  1063. }
  1064. if (menuElement.hasAttr("style-name")) {
  1065. menu.setStyleName(DesignAttributeHandler.readAttribute("style-name",
  1066. attr, String.class));
  1067. }
  1068. if (!subMenus.isEmpty()) {
  1069. menu.itsChildren = new ArrayList<>();
  1070. }
  1071. for (Element subMenu : subMenus) {
  1072. MenuItem newItem = readMenuElement(subMenu);
  1073. newItem.setParent(menu);
  1074. menu.itsChildren.add(newItem);
  1075. }
  1076. return menu;
  1077. }
  1078. @Override
  1079. public void readDesign(Element design, DesignContext designContext) {
  1080. super.readDesign(design, designContext);
  1081. for (Element itemElement : design.children()) {
  1082. if (itemElement.tagName().equals("menu")) {
  1083. MenuItem menuItem = readMenuElement(itemElement);
  1084. if (itemElement.hasAttr("more")) {
  1085. setMoreMenuItem(menuItem);
  1086. } else {
  1087. menuItems.add(menuItem);
  1088. }
  1089. }
  1090. }
  1091. setHtmlContentAllowed(!design.hasAttr(DESIGN_ATTR_PLAIN_TEXT));
  1092. }
  1093. @Override
  1094. protected Collection<String> getCustomAttributes() {
  1095. Collection<String> result = super.getCustomAttributes();
  1096. result.add(DESIGN_ATTR_PLAIN_TEXT);
  1097. result.add("html-content-allowed");
  1098. return result;
  1099. }
  1100. }