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.

IMenuBar.java 18KB

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