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.

VMenuBar.java 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644
  1. package com.vaadin.terminal.gwt.client.ui;
  2. import java.util.ArrayList;
  3. import java.util.Iterator;
  4. import java.util.List;
  5. import java.util.Stack;
  6. import com.google.gwt.user.client.Command;
  7. import com.google.gwt.user.client.DOM;
  8. import com.google.gwt.user.client.DeferredCommand;
  9. import com.google.gwt.user.client.Element;
  10. import com.google.gwt.user.client.Event;
  11. import com.google.gwt.user.client.ui.HasHTML;
  12. import com.google.gwt.user.client.ui.PopupListener;
  13. import com.google.gwt.user.client.ui.PopupPanel;
  14. import com.google.gwt.user.client.ui.UIObject;
  15. import com.google.gwt.user.client.ui.Widget;
  16. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  17. import com.vaadin.terminal.gwt.client.Paintable;
  18. import com.vaadin.terminal.gwt.client.UIDL;
  19. public class VMenuBar extends Widget implements Paintable, PopupListener {
  20. /** Set the CSS class name to allow styling. */
  21. public static final String CLASSNAME = "i-menubar";
  22. /** For server connections **/
  23. protected String uidlId;
  24. protected ApplicationConnection client;
  25. protected final VMenuBar hostReference = this;
  26. protected String submenuIcon = null;
  27. protected boolean collapseItems = true;
  28. protected CustomMenuItem moreItem = null;
  29. // Construct an empty command to be used when the item has no command
  30. // associated
  31. protected static final Command emptyCommand = null;
  32. /** Widget fields **/
  33. protected boolean subMenu;
  34. protected ArrayList<CustomMenuItem> items;
  35. protected Element containerElement;
  36. protected VToolkitOverlay popup;
  37. protected VMenuBar visibleChildMenu;
  38. protected VMenuBar parentMenu;
  39. protected CustomMenuItem selected;
  40. public VMenuBar() {
  41. // Create an empty horizontal menubar
  42. this(false);
  43. }
  44. public VMenuBar(boolean subMenu) {
  45. super();
  46. setElement(DOM.createDiv());
  47. items = new ArrayList<CustomMenuItem>();
  48. popup = null;
  49. visibleChildMenu = null;
  50. Element table = DOM.createTable();
  51. Element tbody = DOM.createTBody();
  52. DOM.appendChild(getElement(), table);
  53. DOM.appendChild(table, tbody);
  54. if (!subMenu) {
  55. setStyleName(CLASSNAME);
  56. Element tr = DOM.createTR();
  57. DOM.appendChild(tbody, tr);
  58. containerElement = tr;
  59. } else {
  60. setStyleName(CLASSNAME + "-submenu");
  61. containerElement = tbody;
  62. }
  63. this.subMenu = subMenu;
  64. sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT);
  65. }
  66. /**
  67. * This method must be implemented to update the client-side component from
  68. * UIDL data received from server.
  69. *
  70. * This method is called when the page is loaded for the first time, and
  71. * every time UI changes in the component are received from the server.
  72. */
  73. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  74. // This call should be made first. Ensure correct implementation,
  75. // and let the containing layout manage caption, etc.
  76. if (client.updateComponent(this, uidl, true)) {
  77. return;
  78. }
  79. // For future connections
  80. this.client = client;
  81. uidlId = uidl.getId();
  82. // Empty the menu every time it receives new information
  83. if (!getItems().isEmpty()) {
  84. clearItems();
  85. }
  86. UIDL options = uidl.getChildUIDL(0);
  87. if (options.hasAttribute("submenuIcon")) {
  88. submenuIcon = client.translateToolkitUri(uidl.getChildUIDL(0)
  89. .getStringAttribute("submenuIcon"));
  90. } else {
  91. submenuIcon = null;
  92. }
  93. collapseItems = options.getBooleanAttribute("collapseItems");
  94. if (collapseItems) {
  95. UIDL moreItemUIDL = options.getChildUIDL(0);
  96. StringBuffer itemHTML = new StringBuffer();
  97. if (moreItemUIDL.hasAttribute("icon")) {
  98. itemHTML.append("<img src=\""
  99. + client.translateToolkitUri(moreItemUIDL
  100. .getStringAttribute("icon"))
  101. + "\" align=\"left\" />");
  102. }
  103. itemHTML.append(moreItemUIDL.getStringAttribute("text"));
  104. moreItem = new CustomMenuItem(itemHTML.toString(), emptyCommand);
  105. }
  106. UIDL uidlItems = uidl.getChildUIDL(1);
  107. Iterator<UIDL> itr = uidlItems.getChildIterator();
  108. Stack<Iterator<UIDL>> iteratorStack = new Stack<Iterator<UIDL>>();
  109. Stack<VMenuBar> menuStack = new Stack<VMenuBar>();
  110. VMenuBar currentMenu = this;
  111. while (itr.hasNext()) {
  112. UIDL item = (UIDL) itr.next();
  113. CustomMenuItem currentItem = null;
  114. String itemText = item.getStringAttribute("text");
  115. final int itemId = item.getIntAttribute("id");
  116. boolean itemHasCommand = item.getBooleanAttribute("command");
  117. // Construct html from the text and the optional icon
  118. StringBuffer itemHTML = new StringBuffer();
  119. if (item.hasAttribute("icon")) {
  120. itemHTML.append("<img src=\""
  121. + client.translateToolkitUri(item
  122. .getStringAttribute("icon"))
  123. + "\" align=\"left\" />");
  124. }
  125. itemHTML.append(itemText);
  126. if (currentMenu != this && item.getChildCount() > 0
  127. && submenuIcon != null) {
  128. itemHTML.append("<img src=\"" + submenuIcon
  129. + "\" align=\"right\" />");
  130. }
  131. Command cmd = null;
  132. if (itemHasCommand) {
  133. // Construct a command that fires onMenuClick(int) with the
  134. // item's id-number
  135. cmd = new Command() {
  136. public void execute() {
  137. hostReference.onMenuClick(itemId);
  138. }
  139. };
  140. }
  141. currentItem = currentMenu.addItem(itemHTML.toString(), cmd);
  142. if (item.getChildCount() > 0) {
  143. menuStack.push(currentMenu);
  144. iteratorStack.push(itr);
  145. itr = item.getChildIterator();
  146. currentMenu = new VMenuBar(true);
  147. currentItem.setSubMenu(currentMenu);
  148. }
  149. while (!itr.hasNext() && !iteratorStack.empty()) {
  150. itr = iteratorStack.pop();
  151. currentMenu = (VMenuBar) menuStack.pop();
  152. }
  153. }// while
  154. // we might need to collapse the top-level menu
  155. if (collapseItems) {
  156. int topLevelWidth = 0;
  157. int ourWidth = getOffsetWidth();
  158. int i = 0;
  159. for (; i < getItems().size() && topLevelWidth < ourWidth; i++) {
  160. CustomMenuItem item = (CustomMenuItem) getItems().get(i);
  161. topLevelWidth += item.getOffsetWidth();
  162. }
  163. if (topLevelWidth > getOffsetWidth()) {
  164. ArrayList<CustomMenuItem> toBeCollapsed = new ArrayList<CustomMenuItem>();
  165. VMenuBar collapsed = new VMenuBar(true);
  166. for (int j = i - 2; j < getItems().size(); j++) {
  167. toBeCollapsed.add(getItems().get(j));
  168. }
  169. for (int j = 0; j < toBeCollapsed.size(); j++) {
  170. CustomMenuItem item = (CustomMenuItem) toBeCollapsed.get(j);
  171. removeItem(item);
  172. // it's ugly, but we have to insert the submenu icon
  173. if (item.getSubMenu() != null && submenuIcon != null) {
  174. StringBuffer itemText = new StringBuffer(item.getHTML());
  175. itemText.append("<img src=\"");
  176. itemText.append(submenuIcon);
  177. itemText.append("\" align=\"right\" />");
  178. item.setHTML(itemText.toString());
  179. }
  180. collapsed.addItem(item);
  181. }
  182. moreItem.setSubMenu(collapsed);
  183. addItem(moreItem);
  184. }
  185. }
  186. }// updateFromUIDL
  187. /**
  188. * This is called by the items in the menu and it communicates the
  189. * information to the server
  190. *
  191. * @param clickedItemId
  192. * id of the item that was clicked
  193. */
  194. public void onMenuClick(int clickedItemId) {
  195. // Updating the state to the server can not be done before
  196. // the server connection is known, i.e., before updateFromUIDL()
  197. // has been called.
  198. if (uidlId != null && client != null) {
  199. // Communicate the user interaction parameters to server. This call
  200. // will initiate an AJAX request to the server.
  201. client.updateVariable(uidlId, "clickedId", clickedItemId, true);
  202. }
  203. }
  204. /** Widget methods **/
  205. /**
  206. * Returns a list of items in this menu
  207. */
  208. public List<CustomMenuItem> getItems() {
  209. return items;
  210. }
  211. /**
  212. * Remove all the items in this menu
  213. */
  214. public void clearItems() {
  215. Element e = getContainingElement();
  216. while (DOM.getChildCount(e) > 0) {
  217. DOM.removeChild(e, DOM.getChild(e, 0));
  218. }
  219. items.clear();
  220. }
  221. /**
  222. * Returns the containing element of the menu
  223. *
  224. * @return
  225. */
  226. public Element getContainingElement() {
  227. return containerElement;
  228. }
  229. /**
  230. * Returns a new child element to add an item to
  231. *
  232. * @return
  233. */
  234. public Element getNewChildElement() {
  235. if (subMenu) {
  236. Element tr = DOM.createTR();
  237. DOM.appendChild(getContainingElement(), tr);
  238. return tr;
  239. } else {
  240. return getContainingElement();
  241. }
  242. }
  243. /**
  244. * Add a new item to this menu
  245. *
  246. * @param html
  247. * items text
  248. * @param cmd
  249. * items command
  250. * @return the item created
  251. */
  252. public CustomMenuItem addItem(String html, Command cmd) {
  253. CustomMenuItem item = new CustomMenuItem(html, cmd);
  254. addItem(item);
  255. return item;
  256. }
  257. /**
  258. * Add a new item to this menu
  259. *
  260. * @param item
  261. */
  262. public void addItem(CustomMenuItem item) {
  263. DOM.appendChild(getNewChildElement(), item.getElement());
  264. item.setParentMenu(this);
  265. item.setSelected(false);
  266. items.add(item);
  267. }
  268. /**
  269. * Remove the given item from this menu
  270. *
  271. * @param item
  272. */
  273. public void removeItem(CustomMenuItem item) {
  274. if (items.contains(item)) {
  275. int index = items.indexOf(item);
  276. Element container = getContainingElement();
  277. DOM.removeChild(container, DOM.getChild(container, index));
  278. items.remove(index);
  279. }
  280. }
  281. /*
  282. * @see
  283. * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
  284. * .client.Event)
  285. */
  286. @Override
  287. public void onBrowserEvent(Event e) {
  288. super.onBrowserEvent(e);
  289. Element targetElement = DOM.eventGetTarget(e);
  290. CustomMenuItem targetItem = null;
  291. for (int i = 0; i < items.size(); i++) {
  292. CustomMenuItem item = (CustomMenuItem) items.get(i);
  293. if (DOM.isOrHasChild(item.getElement(), targetElement)) {
  294. targetItem = item;
  295. }
  296. }
  297. if (targetItem != null) {
  298. switch (DOM.eventGetType(e)) {
  299. case Event.ONCLICK:
  300. itemClick(targetItem);
  301. break;
  302. case Event.ONMOUSEOVER:
  303. itemOver(targetItem);
  304. break;
  305. case Event.ONMOUSEOUT:
  306. itemOut(targetItem);
  307. break;
  308. }
  309. }
  310. }
  311. /**
  312. * When an item is clicked
  313. *
  314. * @param item
  315. */
  316. public void itemClick(CustomMenuItem item) {
  317. if (item.getCommand() != null) {
  318. setSelected(null);
  319. if (visibleChildMenu != null) {
  320. visibleChildMenu.hideChildren();
  321. }
  322. hideParents();
  323. DeferredCommand.addCommand(item.getCommand());
  324. } else {
  325. if (item.getSubMenu() != null
  326. && item.getSubMenu() != visibleChildMenu) {
  327. setSelected(item);
  328. showChildMenu(item);
  329. }
  330. }
  331. }
  332. /**
  333. * When the user hovers the mouse over the item
  334. *
  335. * @param item
  336. */
  337. public void itemOver(CustomMenuItem item) {
  338. setSelected(item);
  339. boolean menuWasVisible = visibleChildMenu != null;
  340. if (menuWasVisible && visibleChildMenu != item.getSubMenu()) {
  341. popup.hide();
  342. visibleChildMenu = null;
  343. }
  344. if (item.getSubMenu() != null && (parentMenu != null || menuWasVisible)
  345. && visibleChildMenu != item.getSubMenu()) {
  346. showChildMenu(item);
  347. }
  348. }
  349. /**
  350. * When the mouse is moved away from an item
  351. *
  352. * @param item
  353. */
  354. public void itemOut(CustomMenuItem item) {
  355. if (visibleChildMenu != item.getSubMenu() || visibleChildMenu == null) {
  356. hideChildMenu(item);
  357. setSelected(null);
  358. }
  359. }
  360. /**
  361. * Shows the child menu of an item. The caller must ensure that the item has
  362. * a submenu.
  363. *
  364. * @param item
  365. */
  366. public void showChildMenu(CustomMenuItem item) {
  367. popup = new VToolkitOverlay(true, false, true);
  368. popup.setWidget(item.getSubMenu());
  369. popup.addPopupListener(this);
  370. if (subMenu) {
  371. popup.setPopupPosition(item.getParentMenu().getAbsoluteLeft()
  372. + item.getParentMenu().getOffsetWidth(), item
  373. .getAbsoluteTop());
  374. } else {
  375. popup.setPopupPosition(item.getAbsoluteLeft(), item.getParentMenu()
  376. .getAbsoluteTop()
  377. + item.getParentMenu().getOffsetHeight());
  378. }
  379. item.getSubMenu().onShow();
  380. visibleChildMenu = item.getSubMenu();
  381. item.getSubMenu().setParentMenu(this);
  382. popup.show();
  383. }
  384. /**
  385. * Hides the submenu of an item
  386. *
  387. * @param item
  388. */
  389. public void hideChildMenu(CustomMenuItem item) {
  390. if (visibleChildMenu != null
  391. && !(visibleChildMenu == item.getSubMenu())) {
  392. popup.hide();
  393. }
  394. }
  395. /**
  396. * When the menu is shown.
  397. */
  398. public void onShow() {
  399. if (!items.isEmpty()) {
  400. ((CustomMenuItem) items.get(0)).setSelected(true);
  401. }
  402. }
  403. /**
  404. * Recursively hide all child menus
  405. */
  406. public void hideChildren() {
  407. if (visibleChildMenu != null) {
  408. visibleChildMenu.hideChildren();
  409. popup.hide();
  410. }
  411. }
  412. /**
  413. * Recursively hide all parent menus
  414. */
  415. public void hideParents() {
  416. if (visibleChildMenu != null) {
  417. popup.hide();
  418. setSelected(null);
  419. }
  420. if (getParentMenu() != null) {
  421. getParentMenu().hideParents();
  422. }
  423. }
  424. /**
  425. * Returns the parent menu of this menu, or null if this is the top-level
  426. * menu
  427. *
  428. * @return
  429. */
  430. public VMenuBar getParentMenu() {
  431. return parentMenu;
  432. }
  433. /**
  434. * Set the parent menu of this menu
  435. *
  436. * @param parent
  437. */
  438. public void setParentMenu(VMenuBar parent) {
  439. parentMenu = parent;
  440. }
  441. /**
  442. * Returns the currently selected item of this menu, or null if nothing is
  443. * selected
  444. *
  445. * @return
  446. */
  447. public CustomMenuItem getSelected() {
  448. return selected;
  449. }
  450. /**
  451. * Set the currently selected item of this menu
  452. *
  453. * @param item
  454. */
  455. public void setSelected(CustomMenuItem item) {
  456. // If we had something selected, unselect
  457. if (item != selected && selected != null) {
  458. selected.setSelected(false);
  459. }
  460. // If we have a valid selection, select it
  461. if (item != null) {
  462. item.setSelected(true);
  463. }
  464. selected = item;
  465. }
  466. /**
  467. * Listener method, fired when this menu is closed
  468. */
  469. public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
  470. hideChildren();
  471. if (autoClosed) {
  472. hideParents();
  473. }
  474. // setSelected(null);
  475. visibleChildMenu = null;
  476. popup = null;
  477. }
  478. /**
  479. *
  480. * A class to hold information on menu items
  481. *
  482. */
  483. private class CustomMenuItem extends UIObject implements HasHTML {
  484. protected String html = null;
  485. protected Command command = null;
  486. protected VMenuBar subMenu = null;
  487. protected VMenuBar parentMenu = null;
  488. public CustomMenuItem(String html, Command cmd) {
  489. setElement(DOM.createTD());
  490. setHTML(html);
  491. setCommand(cmd);
  492. setSelected(false);
  493. addStyleName("menuitem");
  494. }
  495. public void setSelected(boolean selected) {
  496. if (selected) {
  497. addStyleDependentName("selected");
  498. } else {
  499. removeStyleDependentName("selected");
  500. }
  501. }
  502. /*
  503. * setters and getters for the fields
  504. */
  505. public void setSubMenu(VMenuBar subMenu) {
  506. this.subMenu = subMenu;
  507. }
  508. public VMenuBar getSubMenu() {
  509. return subMenu;
  510. }
  511. public void setParentMenu(VMenuBar parentMenu) {
  512. this.parentMenu = parentMenu;
  513. }
  514. public VMenuBar getParentMenu() {
  515. return parentMenu;
  516. }
  517. public void setCommand(Command command) {
  518. this.command = command;
  519. }
  520. public Command getCommand() {
  521. return command;
  522. }
  523. public String getHTML() {
  524. return html;
  525. }
  526. public void setHTML(String html) {
  527. this.html = html;
  528. DOM.setInnerHTML(getElement(), html);
  529. }
  530. public String getText() {
  531. return html;
  532. }
  533. public void setText(String text) {
  534. setHTML(text);
  535. }
  536. }
  537. }// class VMenuBar