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

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558
  1. /*
  2. @VaadinApache2LicenseForJavaFiles@
  3. */
  4. package com.vaadin.terminal.gwt.client.ui;
  5. import java.util.ArrayList;
  6. import java.util.Iterator;
  7. import java.util.List;
  8. import java.util.Stack;
  9. import com.google.gwt.core.client.GWT;
  10. import com.google.gwt.core.client.Scheduler;
  11. import com.google.gwt.core.client.Scheduler.ScheduledCommand;
  12. import com.google.gwt.dom.client.Style;
  13. import com.google.gwt.dom.client.Style.Overflow;
  14. import com.google.gwt.dom.client.Style.Unit;
  15. import com.google.gwt.event.dom.client.FocusEvent;
  16. import com.google.gwt.event.dom.client.FocusHandler;
  17. import com.google.gwt.event.dom.client.KeyCodes;
  18. import com.google.gwt.event.dom.client.KeyDownEvent;
  19. import com.google.gwt.event.dom.client.KeyDownHandler;
  20. import com.google.gwt.event.dom.client.KeyPressEvent;
  21. import com.google.gwt.event.dom.client.KeyPressHandler;
  22. import com.google.gwt.event.logical.shared.CloseEvent;
  23. import com.google.gwt.event.logical.shared.CloseHandler;
  24. import com.google.gwt.user.client.Command;
  25. import com.google.gwt.user.client.DOM;
  26. import com.google.gwt.user.client.Element;
  27. import com.google.gwt.user.client.Event;
  28. import com.google.gwt.user.client.Timer;
  29. import com.google.gwt.user.client.ui.HasHTML;
  30. import com.google.gwt.user.client.ui.PopupPanel;
  31. import com.google.gwt.user.client.ui.RootPanel;
  32. import com.google.gwt.user.client.ui.Widget;
  33. import com.vaadin.terminal.gwt.client.ApplicationConnection;
  34. import com.vaadin.terminal.gwt.client.BrowserInfo;
  35. import com.vaadin.terminal.gwt.client.ContainerResizedListener;
  36. import com.vaadin.terminal.gwt.client.VPaintableMap;
  37. import com.vaadin.terminal.gwt.client.TooltipInfo;
  38. import com.vaadin.terminal.gwt.client.UIDL;
  39. import com.vaadin.terminal.gwt.client.Util;
  40. import com.vaadin.terminal.gwt.client.VPaintableWidget;
  41. import com.vaadin.terminal.gwt.client.VTooltip;
  42. public class VMenuBar extends SimpleFocusablePanel implements VPaintableWidget,
  43. CloseHandler<PopupPanel>, ContainerResizedListener, KeyPressHandler,
  44. KeyDownHandler, FocusHandler, SubPartAware {
  45. // The hierarchy of VMenuBar is a bit weird as VMenuBar is the Paintable,
  46. // used for the root menu but also used for the sub menus.
  47. /** Set the CSS class name to allow styling. */
  48. public static final String CLASSNAME = "v-menubar";
  49. /** For server connections **/
  50. protected String uidlId;
  51. protected ApplicationConnection client;
  52. protected final VMenuBar hostReference = this;
  53. protected CustomMenuItem moreItem = null;
  54. // Only used by the root menu bar
  55. protected VMenuBar collapsedRootItems;
  56. // Construct an empty command to be used when the item has no command
  57. // associated
  58. protected static final Command emptyCommand = null;
  59. public static final String OPEN_ROOT_MENU_ON_HOWER = "ormoh";
  60. public static final String ATTRIBUTE_CHECKED = "checked";
  61. public static final String HTML_CONTENT_ALLOWED = "usehtml";
  62. /** Widget fields **/
  63. protected boolean subMenu;
  64. protected ArrayList<CustomMenuItem> items;
  65. protected Element containerElement;
  66. protected VOverlay popup;
  67. protected VMenuBar visibleChildMenu;
  68. protected boolean menuVisible = false;
  69. protected VMenuBar parentMenu;
  70. protected CustomMenuItem selected;
  71. private boolean enabled = true;
  72. private String width = "notinited";
  73. private VLazyExecutor iconLoadedExecutioner = new VLazyExecutor(100,
  74. new ScheduledCommand() {
  75. public void execute() {
  76. iLayout(true);
  77. }
  78. });
  79. private boolean openRootOnHover;
  80. private boolean htmlContentAllowed;
  81. public VMenuBar() {
  82. // Create an empty horizontal menubar
  83. this(false, null);
  84. // Navigation is only handled by the root bar
  85. addFocusHandler(this);
  86. /*
  87. * Firefox auto-repeat works correctly only if we use a key press
  88. * handler, other browsers handle it correctly when using a key down
  89. * handler
  90. */
  91. if (BrowserInfo.get().isGecko()) {
  92. addKeyPressHandler(this);
  93. } else {
  94. addKeyDownHandler(this);
  95. }
  96. }
  97. public VMenuBar(boolean subMenu, VMenuBar parentMenu) {
  98. items = new ArrayList<CustomMenuItem>();
  99. popup = null;
  100. visibleChildMenu = null;
  101. containerElement = getElement();
  102. if (!subMenu) {
  103. setStyleName(CLASSNAME);
  104. } else {
  105. setStyleName(CLASSNAME + "-submenu");
  106. this.parentMenu = parentMenu;
  107. }
  108. this.subMenu = subMenu;
  109. sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT
  110. | Event.ONLOAD);
  111. sinkEvents(VTooltip.TOOLTIP_EVENTS);
  112. }
  113. @Override
  114. protected void onDetach() {
  115. super.onDetach();
  116. if (!subMenu) {
  117. setSelected(null);
  118. hideChildren();
  119. menuVisible = false;
  120. }
  121. }
  122. @Override
  123. public void setWidth(String width) {
  124. if (Util.equals(this.width, width)) {
  125. return;
  126. }
  127. this.width = width;
  128. Util.setWidthExcludingPaddingAndBorder(this, width, 0);
  129. if (!subMenu) {
  130. // Only needed for root level menu
  131. hideChildren();
  132. setSelected(null);
  133. menuVisible = false;
  134. }
  135. }
  136. /**
  137. * This method must be implemented to update the client-side component from
  138. * UIDL data received from server.
  139. *
  140. * This method is called when the page is loaded for the first time, and
  141. * every time UI changes in the component are received from the server.
  142. */
  143. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  144. // This call should be made first. Ensure correct implementation,
  145. // and let the containing layout manage caption, etc.
  146. if (client.updateComponent(this, uidl, true)) {
  147. return;
  148. }
  149. htmlContentAllowed = uidl.hasAttribute(HTML_CONTENT_ALLOWED);
  150. openRootOnHover = uidl.getBooleanAttribute(OPEN_ROOT_MENU_ON_HOWER);
  151. enabled = !uidl.getBooleanAttribute("disabled");
  152. // For future connections
  153. this.client = client;
  154. uidlId = uidl.getId();
  155. // Empty the menu every time it receives new information
  156. if (!getItems().isEmpty()) {
  157. clearItems();
  158. }
  159. UIDL options = uidl.getChildUIDL(0);
  160. if (uidl.hasAttribute("width")) {
  161. UIDL moreItemUIDL = options.getChildUIDL(0);
  162. StringBuffer itemHTML = new StringBuffer();
  163. if (moreItemUIDL.hasAttribute("icon")) {
  164. itemHTML.append("<img src=\""
  165. + Util.escapeAttribute(client
  166. .translateVaadinUri(moreItemUIDL
  167. .getStringAttribute("icon")))
  168. + "\" class=\"" + Icon.CLASSNAME + "\" alt=\"\" />");
  169. }
  170. String moreItemText = moreItemUIDL.getStringAttribute("text");
  171. if ("".equals(moreItemText)) {
  172. moreItemText = "&#x25BA;";
  173. }
  174. itemHTML.append(moreItemText);
  175. moreItem = GWT.create(CustomMenuItem.class);
  176. moreItem.setHTML(itemHTML.toString());
  177. moreItem.setCommand(emptyCommand);
  178. collapsedRootItems = new VMenuBar(true, (VMenuBar) VPaintableMap
  179. .get(client).getPaintable(uidlId));
  180. moreItem.setSubMenu(collapsedRootItems);
  181. moreItem.addStyleName(CLASSNAME + "-more-menuitem");
  182. }
  183. UIDL uidlItems = uidl.getChildUIDL(1);
  184. Iterator<Object> itr = uidlItems.getChildIterator();
  185. Stack<Iterator<Object>> iteratorStack = new Stack<Iterator<Object>>();
  186. Stack<VMenuBar> menuStack = new Stack<VMenuBar>();
  187. VMenuBar currentMenu = this;
  188. while (itr.hasNext()) {
  189. UIDL item = (UIDL) itr.next();
  190. CustomMenuItem currentItem = null;
  191. final int itemId = item.getIntAttribute("id");
  192. boolean itemHasCommand = item.hasAttribute("command");
  193. boolean itemIsCheckable = item.hasAttribute(ATTRIBUTE_CHECKED);
  194. String itemHTML = buildItemHTML(item);
  195. Command cmd = null;
  196. if (!item.hasAttribute("separator")) {
  197. if (itemHasCommand || itemIsCheckable) {
  198. // Construct a command that fires onMenuClick(int) with the
  199. // item's id-number
  200. cmd = new Command() {
  201. public void execute() {
  202. hostReference.onMenuClick(itemId);
  203. }
  204. };
  205. }
  206. }
  207. currentItem = currentMenu.addItem(itemHTML.toString(), cmd);
  208. currentItem.updateFromUIDL(item, client);
  209. if (item.getChildCount() > 0) {
  210. menuStack.push(currentMenu);
  211. iteratorStack.push(itr);
  212. itr = item.getChildIterator();
  213. currentMenu = new VMenuBar(true, currentMenu);
  214. if (uidl.hasAttribute("style")) {
  215. for (String style : uidl.getStringAttribute("style").split(
  216. " ")) {
  217. currentMenu.addStyleDependentName(style);
  218. }
  219. }
  220. currentItem.setSubMenu(currentMenu);
  221. }
  222. while (!itr.hasNext() && !iteratorStack.empty()) {
  223. boolean hasCheckableItem = false;
  224. for (CustomMenuItem menuItem : currentMenu.getItems()) {
  225. hasCheckableItem = hasCheckableItem
  226. || menuItem.isCheckable();
  227. }
  228. if (hasCheckableItem) {
  229. currentMenu.addStyleDependentName("check-column");
  230. } else {
  231. currentMenu.removeStyleDependentName("check-column");
  232. }
  233. itr = iteratorStack.pop();
  234. currentMenu = menuStack.pop();
  235. }
  236. }// while
  237. iLayout(false);
  238. }// updateFromUIDL
  239. /**
  240. * Build the HTML content for a menu item.
  241. *
  242. * @param item
  243. * @return
  244. */
  245. protected String buildItemHTML(UIDL item) {
  246. // Construct html from the text and the optional icon
  247. StringBuffer itemHTML = new StringBuffer();
  248. if (item.hasAttribute("separator")) {
  249. itemHTML.append("<span>---</span>");
  250. } else {
  251. // Add submenu indicator
  252. if (item.getChildCount() > 0) {
  253. String bgStyle = "";
  254. itemHTML.append("<span class=\"" + CLASSNAME
  255. + "-submenu-indicator\"" + bgStyle + ">&#x25BA;</span>");
  256. }
  257. itemHTML.append("<span class=\"" + CLASSNAME
  258. + "-menuitem-caption\">");
  259. if (item.hasAttribute("icon")) {
  260. itemHTML.append("<img src=\""
  261. + Util.escapeAttribute(client.translateVaadinUri(item
  262. .getStringAttribute("icon"))) + "\" class=\""
  263. + Icon.CLASSNAME + "\" alt=\"\" />");
  264. }
  265. String itemText = item.getStringAttribute("text");
  266. if (!htmlContentAllowed) {
  267. itemText = Util.escapeHTML(itemText);
  268. }
  269. itemHTML.append(itemText);
  270. itemHTML.append("</span>");
  271. }
  272. return itemHTML.toString();
  273. }
  274. /**
  275. * This is called by the items in the menu and it communicates the
  276. * information to the server
  277. *
  278. * @param clickedItemId
  279. * id of the item that was clicked
  280. */
  281. public void onMenuClick(int clickedItemId) {
  282. // Updating the state to the server can not be done before
  283. // the server connection is known, i.e., before updateFromUIDL()
  284. // has been called.
  285. if (uidlId != null && client != null) {
  286. // Communicate the user interaction parameters to server. This call
  287. // will initiate an AJAX request to the server.
  288. client.updateVariable(uidlId, "clickedId", clickedItemId, true);
  289. }
  290. }
  291. /** Widget methods **/
  292. /**
  293. * Returns a list of items in this menu
  294. */
  295. public List<CustomMenuItem> getItems() {
  296. return items;
  297. }
  298. /**
  299. * Remove all the items in this menu
  300. */
  301. public void clearItems() {
  302. Element e = getContainerElement();
  303. while (DOM.getChildCount(e) > 0) {
  304. DOM.removeChild(e, DOM.getChild(e, 0));
  305. }
  306. items.clear();
  307. }
  308. /**
  309. * Returns the containing element of the menu
  310. *
  311. * @return
  312. */
  313. @Override
  314. public Element getContainerElement() {
  315. return containerElement;
  316. }
  317. /**
  318. * Add a new item to this menu
  319. *
  320. * @param html
  321. * items text
  322. * @param cmd
  323. * items command
  324. * @return the item created
  325. */
  326. public CustomMenuItem addItem(String html, Command cmd) {
  327. CustomMenuItem item = GWT.create(CustomMenuItem.class);
  328. item.setHTML(html);
  329. item.setCommand(cmd);
  330. addItem(item);
  331. return item;
  332. }
  333. /**
  334. * Add a new item to this menu
  335. *
  336. * @param item
  337. */
  338. public void addItem(CustomMenuItem item) {
  339. if (items.contains(item)) {
  340. return;
  341. }
  342. DOM.appendChild(getContainerElement(), item.getElement());
  343. item.setParentMenu(this);
  344. item.setSelected(false);
  345. items.add(item);
  346. }
  347. public void addItem(CustomMenuItem item, int index) {
  348. if (items.contains(item)) {
  349. return;
  350. }
  351. DOM.insertChild(getContainerElement(), item.getElement(), index);
  352. item.setParentMenu(this);
  353. item.setSelected(false);
  354. items.add(index, item);
  355. }
  356. /**
  357. * Remove the given item from this menu
  358. *
  359. * @param item
  360. */
  361. public void removeItem(CustomMenuItem item) {
  362. if (items.contains(item)) {
  363. int index = items.indexOf(item);
  364. DOM.removeChild(getContainerElement(),
  365. DOM.getChild(getContainerElement(), index));
  366. items.remove(index);
  367. }
  368. }
  369. /*
  370. * @see
  371. * com.google.gwt.user.client.ui.Widget#onBrowserEvent(com.google.gwt.user
  372. * .client.Event)
  373. */
  374. @Override
  375. public void onBrowserEvent(Event e) {
  376. super.onBrowserEvent(e);
  377. // Handle onload events (icon loaded, size changes)
  378. if (DOM.eventGetType(e) == Event.ONLOAD) {
  379. VMenuBar parent = getParentMenu();
  380. if (parent != null) {
  381. // The onload event for an image in a popup should be sent to
  382. // the parent, which owns the popup
  383. parent.iconLoaded();
  384. } else {
  385. // Onload events for images in the root menu are handled by the
  386. // root menu itself
  387. iconLoaded();
  388. }
  389. return;
  390. }
  391. Element targetElement = DOM.eventGetTarget(e);
  392. CustomMenuItem targetItem = null;
  393. for (int i = 0; i < items.size(); i++) {
  394. CustomMenuItem item = items.get(i);
  395. if (DOM.isOrHasChild(item.getElement(), targetElement)) {
  396. targetItem = item;
  397. }
  398. }
  399. // Handle tooltips
  400. if (targetItem == null && client != null) {
  401. // Handle root menubar tooltips
  402. client.handleTooltipEvent(e, this);
  403. } else if (targetItem != null) {
  404. // Handle item tooltips
  405. targetItem.onBrowserEvent(e);
  406. }
  407. if (targetItem != null) {
  408. switch (DOM.eventGetType(e)) {
  409. case Event.ONCLICK:
  410. if (isEnabled() && targetItem.isEnabled()) {
  411. itemClick(targetItem);
  412. }
  413. if (subMenu) {
  414. // Prevent moving keyboard focus to child menus
  415. VMenuBar parent = parentMenu;
  416. while (parent.getParentMenu() != null) {
  417. parent = parent.getParentMenu();
  418. }
  419. parent.setFocus(true);
  420. }
  421. break;
  422. case Event.ONMOUSEOVER:
  423. LazyCloser.cancelClosing();
  424. if (isEnabled() && targetItem.isEnabled()) {
  425. itemOver(targetItem);
  426. }
  427. break;
  428. case Event.ONMOUSEOUT:
  429. itemOut(targetItem);
  430. LazyCloser.schedule();
  431. break;
  432. }
  433. } else if (subMenu && DOM.eventGetType(e) == Event.ONCLICK && subMenu) {
  434. // Prevent moving keyboard focus to child menus
  435. VMenuBar parent = parentMenu;
  436. while (parent.getParentMenu() != null) {
  437. parent = parent.getParentMenu();
  438. }
  439. parent.setFocus(true);
  440. }
  441. }
  442. private boolean isEnabled() {
  443. return enabled;
  444. }
  445. private void iconLoaded() {
  446. iconLoadedExecutioner.trigger();
  447. }
  448. /**
  449. * When an item is clicked
  450. *
  451. * @param item
  452. */
  453. public void itemClick(CustomMenuItem item) {
  454. if (item.getCommand() != null) {
  455. setSelected(null);
  456. if (visibleChildMenu != null) {
  457. visibleChildMenu.hideChildren();
  458. }
  459. hideParents(true);
  460. menuVisible = false;
  461. Scheduler.get().scheduleDeferred(item.getCommand());
  462. } else {
  463. if (item.getSubMenu() != null
  464. && item.getSubMenu() != visibleChildMenu) {
  465. setSelected(item);
  466. showChildMenu(item);
  467. menuVisible = true;
  468. } else if (!subMenu) {
  469. setSelected(null);
  470. hideChildren();
  471. menuVisible = false;
  472. }
  473. }
  474. }
  475. /**
  476. * When the user hovers the mouse over the item
  477. *
  478. * @param item
  479. */
  480. public void itemOver(CustomMenuItem item) {
  481. if ((openRootOnHover || subMenu || menuVisible) && !item.isSeparator()) {
  482. setSelected(item);
  483. if (!subMenu && openRootOnHover && !menuVisible) {
  484. menuVisible = true; // start opening menus
  485. LazyCloser.prepare(this);
  486. }
  487. }
  488. if (menuVisible && visibleChildMenu != item.getSubMenu()
  489. && popup != null) {
  490. popup.hide();
  491. }
  492. if (menuVisible && item.getSubMenu() != null
  493. && visibleChildMenu != item.getSubMenu()) {
  494. showChildMenu(item);
  495. }
  496. }
  497. /**
  498. * When the mouse is moved away from an item
  499. *
  500. * @param item
  501. */
  502. public void itemOut(CustomMenuItem item) {
  503. if (visibleChildMenu != item.getSubMenu()) {
  504. hideChildMenu(item);
  505. setSelected(null);
  506. } else if (visibleChildMenu == null) {
  507. setSelected(null);
  508. }
  509. }
  510. /**
  511. * Used to autoclose submenus when they the menu is in a mode which opens
  512. * root menus on mouse hover.
  513. */
  514. private static class LazyCloser extends Timer {
  515. static LazyCloser INSTANCE;
  516. private VMenuBar activeRoot;
  517. @Override
  518. public void run() {
  519. activeRoot.hideChildren();
  520. activeRoot.setSelected(null);
  521. activeRoot.menuVisible = false;
  522. activeRoot = null;
  523. }
  524. public static void cancelClosing() {
  525. if (INSTANCE != null) {
  526. INSTANCE.cancel();
  527. }
  528. }
  529. public static void prepare(VMenuBar vMenuBar) {
  530. if (INSTANCE == null) {
  531. INSTANCE = new LazyCloser();
  532. }
  533. if (INSTANCE.activeRoot == vMenuBar) {
  534. INSTANCE.cancel();
  535. } else if (INSTANCE.activeRoot != null) {
  536. INSTANCE.cancel();
  537. INSTANCE.run();
  538. }
  539. INSTANCE.activeRoot = vMenuBar;
  540. }
  541. public static void schedule() {
  542. if (INSTANCE != null && INSTANCE.activeRoot != null) {
  543. INSTANCE.schedule(750);
  544. }
  545. }
  546. }
  547. /**
  548. * Shows the child menu of an item. The caller must ensure that the item has
  549. * a submenu.
  550. *
  551. * @param item
  552. */
  553. public void showChildMenu(CustomMenuItem item) {
  554. int left = 0;
  555. int top = 0;
  556. if (subMenu) {
  557. left = item.getParentMenu().getAbsoluteLeft()
  558. + item.getParentMenu().getOffsetWidth();
  559. top = item.getAbsoluteTop();
  560. } else {
  561. left = item.getAbsoluteLeft();
  562. top = item.getParentMenu().getAbsoluteTop()
  563. + item.getParentMenu().getOffsetHeight();
  564. }
  565. showChildMenuAt(item, top, left);
  566. }
  567. protected void showChildMenuAt(CustomMenuItem item, int top, int left) {
  568. final int shadowSpace = 10;
  569. popup = new VOverlay(true, false, true);
  570. popup.setStyleName(CLASSNAME + "-popup");
  571. popup.setWidget(item.getSubMenu());
  572. popup.addCloseHandler(this);
  573. popup.addAutoHidePartner(item.getElement());
  574. // at 0,0 because otherwise IE7 add extra scrollbars (#5547)
  575. popup.setPopupPosition(0, 0);
  576. item.getSubMenu().onShow();
  577. visibleChildMenu = item.getSubMenu();
  578. item.getSubMenu().setParentMenu(this);
  579. popup.show();
  580. if (left + popup.getOffsetWidth() >= RootPanel.getBodyElement()
  581. .getOffsetWidth() - shadowSpace) {
  582. if (subMenu) {
  583. left = item.getParentMenu().getAbsoluteLeft()
  584. - popup.getOffsetWidth() - shadowSpace;
  585. } else {
  586. left = RootPanel.getBodyElement().getOffsetWidth()
  587. - popup.getOffsetWidth() - shadowSpace;
  588. }
  589. // Accommodate space for shadow
  590. if (left < shadowSpace) {
  591. left = shadowSpace;
  592. }
  593. }
  594. top = adjustPopupHeight(top, shadowSpace);
  595. popup.setPopupPosition(left, top);
  596. }
  597. private int adjustPopupHeight(int top, final int shadowSpace) {
  598. // Check that the popup will fit the screen
  599. int availableHeight = RootPanel.getBodyElement().getOffsetHeight()
  600. - top - shadowSpace;
  601. int missingHeight = popup.getOffsetHeight() - availableHeight;
  602. if (missingHeight > 0) {
  603. // First move the top of the popup to get more space
  604. // Don't move above top of screen, don't move more than needed
  605. int moveUpBy = Math.min(top - shadowSpace, missingHeight);
  606. // Update state
  607. top -= moveUpBy;
  608. missingHeight -= moveUpBy;
  609. availableHeight += moveUpBy;
  610. if (missingHeight > 0) {
  611. int contentWidth = visibleChildMenu.getOffsetWidth();
  612. // If there's still not enough room, limit height to fit and add
  613. // a scroll bar
  614. Style style = popup.getElement().getStyle();
  615. style.setHeight(availableHeight, Unit.PX);
  616. style.setOverflowY(Overflow.SCROLL);
  617. // Make room for the scroll bar by adjusting the width of the
  618. // popup
  619. style.setWidth(contentWidth + Util.getNativeScrollbarSize(),
  620. Unit.PX);
  621. popup.updateShadowSizeAndPosition();
  622. }
  623. }
  624. return top;
  625. }
  626. /**
  627. * Hides the submenu of an item
  628. *
  629. * @param item
  630. */
  631. public void hideChildMenu(CustomMenuItem item) {
  632. if (visibleChildMenu != null
  633. && !(visibleChildMenu == item.getSubMenu())) {
  634. popup.hide();
  635. }
  636. }
  637. /**
  638. * When the menu is shown.
  639. */
  640. public void onShow() {
  641. // remove possible previous selection
  642. if (selected != null) {
  643. selected.setSelected(false);
  644. selected = null;
  645. }
  646. menuVisible = true;
  647. }
  648. /**
  649. * Listener method, fired when this menu is closed
  650. */
  651. public void onClose(CloseEvent<PopupPanel> event) {
  652. hideChildren();
  653. if (event.isAutoClosed()) {
  654. hideParents(true);
  655. menuVisible = false;
  656. }
  657. visibleChildMenu = null;
  658. popup = null;
  659. }
  660. /**
  661. * Recursively hide all child menus
  662. */
  663. public void hideChildren() {
  664. if (visibleChildMenu != null) {
  665. visibleChildMenu.hideChildren();
  666. popup.hide();
  667. }
  668. }
  669. /**
  670. * Recursively hide all parent menus
  671. */
  672. public void hideParents(boolean autoClosed) {
  673. if (visibleChildMenu != null) {
  674. popup.hide();
  675. setSelected(null);
  676. menuVisible = !autoClosed;
  677. }
  678. if (getParentMenu() != null) {
  679. getParentMenu().hideParents(autoClosed);
  680. }
  681. }
  682. /**
  683. * Returns the parent menu of this menu, or null if this is the top-level
  684. * menu
  685. *
  686. * @return
  687. */
  688. public VMenuBar getParentMenu() {
  689. return parentMenu;
  690. }
  691. /**
  692. * Set the parent menu of this menu
  693. *
  694. * @param parent
  695. */
  696. public void setParentMenu(VMenuBar parent) {
  697. parentMenu = parent;
  698. }
  699. /**
  700. * Returns the currently selected item of this menu, or null if nothing is
  701. * selected
  702. *
  703. * @return
  704. */
  705. public CustomMenuItem getSelected() {
  706. return selected;
  707. }
  708. /**
  709. * Set the currently selected item of this menu
  710. *
  711. * @param item
  712. */
  713. public void setSelected(CustomMenuItem item) {
  714. // If we had something selected, unselect
  715. if (item != selected && selected != null) {
  716. selected.setSelected(false);
  717. }
  718. // If we have a valid selection, select it
  719. if (item != null) {
  720. item.setSelected(true);
  721. }
  722. selected = item;
  723. }
  724. /**
  725. *
  726. * A class to hold information on menu items
  727. *
  728. */
  729. protected static class CustomMenuItem extends Widget implements HasHTML {
  730. private ApplicationConnection client;
  731. protected String html = null;
  732. protected Command command = null;
  733. protected VMenuBar subMenu = null;
  734. protected VMenuBar parentMenu = null;
  735. protected boolean enabled = true;
  736. protected boolean isSeparator = false;
  737. protected boolean checkable = false;
  738. protected boolean checked = false;
  739. /**
  740. * Default menu item {@link Widget} constructor for GWT.create().
  741. *
  742. * Use {@link #setHTML(String)} and {@link #setCommand(Command)} after
  743. * constructing a menu item.
  744. */
  745. public CustomMenuItem() {
  746. this("", null);
  747. }
  748. /**
  749. * Creates a menu item {@link Widget}.
  750. *
  751. * @param html
  752. * @param cmd
  753. * @deprecated use the default constructor and {@link #setHTML(String)}
  754. * and {@link #setCommand(Command)} instead
  755. */
  756. @Deprecated
  757. public CustomMenuItem(String html, Command cmd) {
  758. // We need spans to allow inline-block in IE
  759. setElement(DOM.createSpan());
  760. setHTML(html);
  761. setCommand(cmd);
  762. setSelected(false);
  763. setStyleName(CLASSNAME + "-menuitem");
  764. sinkEvents(VTooltip.TOOLTIP_EVENTS);
  765. }
  766. public void setSelected(boolean selected) {
  767. if (selected && !isSeparator) {
  768. addStyleDependentName("selected");
  769. // needed for IE6 to have a single style name to match for an
  770. // element
  771. // TODO Can be optimized now that IE6 is not supported any more
  772. if (checkable) {
  773. if (checked) {
  774. removeStyleDependentName("selected-unchecked");
  775. addStyleDependentName("selected-checked");
  776. } else {
  777. removeStyleDependentName("selected-checked");
  778. addStyleDependentName("selected-unchecked");
  779. }
  780. }
  781. } else {
  782. removeStyleDependentName("selected");
  783. // needed for IE6 to have a single style name to match for an
  784. // element
  785. removeStyleDependentName("selected-checked");
  786. removeStyleDependentName("selected-unchecked");
  787. }
  788. }
  789. public void setChecked(boolean checked) {
  790. if (checkable && !isSeparator) {
  791. this.checked = checked;
  792. if (checked) {
  793. addStyleDependentName("checked");
  794. removeStyleDependentName("unchecked");
  795. } else {
  796. addStyleDependentName("unchecked");
  797. removeStyleDependentName("checked");
  798. }
  799. } else {
  800. this.checked = false;
  801. }
  802. }
  803. public boolean isChecked() {
  804. return checked;
  805. }
  806. public void setCheckable(boolean checkable) {
  807. if (checkable && !isSeparator) {
  808. this.checkable = true;
  809. } else {
  810. setChecked(false);
  811. this.checkable = false;
  812. }
  813. }
  814. public boolean isCheckable() {
  815. return checkable;
  816. }
  817. /*
  818. * setters and getters for the fields
  819. */
  820. public void setSubMenu(VMenuBar subMenu) {
  821. this.subMenu = subMenu;
  822. }
  823. public VMenuBar getSubMenu() {
  824. return subMenu;
  825. }
  826. public void setParentMenu(VMenuBar parentMenu) {
  827. this.parentMenu = parentMenu;
  828. }
  829. public VMenuBar getParentMenu() {
  830. return parentMenu;
  831. }
  832. public void setCommand(Command command) {
  833. this.command = command;
  834. }
  835. public Command getCommand() {
  836. return command;
  837. }
  838. public String getHTML() {
  839. return html;
  840. }
  841. public void setHTML(String html) {
  842. this.html = html;
  843. DOM.setInnerHTML(getElement(), html);
  844. // Sink the onload event for any icons. The onload
  845. // events are handled by the parent VMenuBar.
  846. Util.sinkOnloadForImages(getElement());
  847. }
  848. public String getText() {
  849. return html;
  850. }
  851. public void setText(String text) {
  852. setHTML(Util.escapeHTML(text));
  853. }
  854. public void setEnabled(boolean enabled) {
  855. this.enabled = enabled;
  856. if (enabled) {
  857. removeStyleDependentName("disabled");
  858. } else {
  859. addStyleDependentName("disabled");
  860. }
  861. }
  862. public boolean isEnabled() {
  863. return enabled;
  864. }
  865. private void setSeparator(boolean separator) {
  866. isSeparator = separator;
  867. if (separator) {
  868. setStyleName(CLASSNAME + "-separator");
  869. } else {
  870. setStyleName(CLASSNAME + "-menuitem");
  871. setEnabled(enabled);
  872. }
  873. }
  874. public boolean isSeparator() {
  875. return isSeparator;
  876. }
  877. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  878. this.client = client;
  879. setSeparator(uidl.hasAttribute("separator"));
  880. setEnabled(!uidl.hasAttribute("disabled"));
  881. if (!isSeparator() && uidl.hasAttribute(ATTRIBUTE_CHECKED)) {
  882. // if the selected attribute is present (either true or false),
  883. // the item is selectable
  884. setCheckable(true);
  885. setChecked(uidl.getBooleanAttribute(ATTRIBUTE_CHECKED));
  886. } else {
  887. setCheckable(false);
  888. }
  889. if (uidl.hasAttribute("style")) {
  890. String itemStyle = uidl.getStringAttribute("style");
  891. addStyleDependentName(itemStyle);
  892. }
  893. if (uidl.hasAttribute("description")) {
  894. String description = uidl.getStringAttribute("description");
  895. TooltipInfo info = new TooltipInfo(description);
  896. VMenuBar root = findRootMenu();
  897. client.registerTooltip(root, this, info);
  898. }
  899. }
  900. @Override
  901. public void onBrowserEvent(Event event) {
  902. super.onBrowserEvent(event);
  903. if (client != null) {
  904. client.handleTooltipEvent(event, findRootMenu(), this);
  905. }
  906. }
  907. private VMenuBar findRootMenu() {
  908. VMenuBar menubar = getParentMenu();
  909. // Traverse up until root menu is found
  910. while (menubar.getParentMenu() != null) {
  911. menubar = menubar.getParentMenu();
  912. }
  913. return menubar;
  914. }
  915. }
  916. /**
  917. * @author Jouni Koivuviita / Vaadin Ltd.
  918. */
  919. private int paddingWidth = -1;
  920. public void iLayout() {
  921. iLayout(false);
  922. }
  923. public void iLayout(boolean iconLoadEvent) {
  924. // Only collapse if there is more than one item in the root menu and the
  925. // menu has an explicit size
  926. if ((getItems().size() > 1 || (collapsedRootItems != null && collapsedRootItems
  927. .getItems().size() > 0))
  928. && getElement().getStyle().getProperty("width") != null
  929. && moreItem != null) {
  930. // Measure the width of the "more" item
  931. final boolean morePresent = getItems().contains(moreItem);
  932. addItem(moreItem);
  933. final int moreItemWidth = moreItem.getOffsetWidth();
  934. if (!morePresent) {
  935. removeItem(moreItem);
  936. }
  937. // Measure available space
  938. if (paddingWidth == -1) {
  939. int widthBefore = getElement().getClientWidth();
  940. getElement().getStyle().setProperty("padding", "0");
  941. paddingWidth = widthBefore - getElement().getClientWidth();
  942. getElement().getStyle().setProperty("padding", "");
  943. }
  944. int availableWidth = getElement().getClientWidth() - paddingWidth;
  945. // Used width includes the "more" item if present
  946. int usedWidth = getConsumedWidth();
  947. int diff = availableWidth - usedWidth;
  948. removeItem(moreItem);
  949. if (diff < 0) {
  950. // Too many items: collapse last items from root menu
  951. int widthNeeded = usedWidth - availableWidth;
  952. if (!morePresent) {
  953. widthNeeded += moreItemWidth;
  954. }
  955. int widthReduced = 0;
  956. while (widthReduced < widthNeeded && getItems().size() > 0) {
  957. // Move last root menu item to collapsed menu
  958. CustomMenuItem collapse = getItems().get(
  959. getItems().size() - 1);
  960. widthReduced += collapse.getOffsetWidth();
  961. removeItem(collapse);
  962. collapsedRootItems.addItem(collapse, 0);
  963. }
  964. } else if (collapsedRootItems.getItems().size() > 0) {
  965. // Space available for items: expand first items from collapsed
  966. // menu
  967. int widthAvailable = diff + moreItemWidth;
  968. int widthGrowth = 0;
  969. while (widthAvailable > widthGrowth
  970. && collapsedRootItems.getItems().size() > 0) {
  971. // Move first item from collapsed menu to the root menu
  972. CustomMenuItem expand = collapsedRootItems.getItems()
  973. .get(0);
  974. collapsedRootItems.removeItem(expand);
  975. addItem(expand);
  976. widthGrowth += expand.getOffsetWidth();
  977. if (collapsedRootItems.getItems().size() > 0) {
  978. widthAvailable -= moreItemWidth;
  979. }
  980. if (widthGrowth > widthAvailable) {
  981. removeItem(expand);
  982. collapsedRootItems.addItem(expand, 0);
  983. } else {
  984. widthAvailable = diff;
  985. }
  986. }
  987. }
  988. if (collapsedRootItems.getItems().size() > 0) {
  989. addItem(moreItem);
  990. }
  991. }
  992. // If a popup is open we might need to adjust the shadow as well if an
  993. // icon shown in that popup was loaded
  994. if (popup != null) {
  995. // Forces a recalculation of the shadow size
  996. popup.show();
  997. }
  998. if (iconLoadEvent) {
  999. // Size have changed if the width is undefined
  1000. Util.notifyParentOfSizeChange(this, false);
  1001. }
  1002. }
  1003. private int getConsumedWidth() {
  1004. int w = 0;
  1005. for (CustomMenuItem item : getItems()) {
  1006. if (!collapsedRootItems.getItems().contains(item)) {
  1007. w += item.getOffsetWidth();
  1008. }
  1009. }
  1010. return w;
  1011. }
  1012. /*
  1013. * (non-Javadoc)
  1014. *
  1015. * @see
  1016. * com.google.gwt.event.dom.client.KeyPressHandler#onKeyPress(com.google
  1017. * .gwt.event.dom.client.KeyPressEvent)
  1018. */
  1019. public void onKeyPress(KeyPressEvent event) {
  1020. if (handleNavigation(event.getNativeEvent().getKeyCode(),
  1021. event.isControlKeyDown() || event.isMetaKeyDown(),
  1022. event.isShiftKeyDown())) {
  1023. event.preventDefault();
  1024. }
  1025. }
  1026. /*
  1027. * (non-Javadoc)
  1028. *
  1029. * @see
  1030. * com.google.gwt.event.dom.client.KeyDownHandler#onKeyDown(com.google.gwt
  1031. * .event.dom.client.KeyDownEvent)
  1032. */
  1033. public void onKeyDown(KeyDownEvent event) {
  1034. if (handleNavigation(event.getNativeEvent().getKeyCode(),
  1035. event.isControlKeyDown() || event.isMetaKeyDown(),
  1036. event.isShiftKeyDown())) {
  1037. event.preventDefault();
  1038. }
  1039. }
  1040. /**
  1041. * Get the key that moves the selection upwards. By default it is the up
  1042. * arrow key but by overriding this you can change the key to whatever you
  1043. * want.
  1044. *
  1045. * @return The keycode of the key
  1046. */
  1047. protected int getNavigationUpKey() {
  1048. return KeyCodes.KEY_UP;
  1049. }
  1050. /**
  1051. * Get the key that moves the selection downwards. By default it is the down
  1052. * arrow key but by overriding this you can change the key to whatever you
  1053. * want.
  1054. *
  1055. * @return The keycode of the key
  1056. */
  1057. protected int getNavigationDownKey() {
  1058. return KeyCodes.KEY_DOWN;
  1059. }
  1060. /**
  1061. * Get the key that moves the selection left. By default it is the left
  1062. * arrow key but by overriding this you can change the key to whatever you
  1063. * want.
  1064. *
  1065. * @return The keycode of the key
  1066. */
  1067. protected int getNavigationLeftKey() {
  1068. return KeyCodes.KEY_LEFT;
  1069. }
  1070. /**
  1071. * Get the key that moves the selection right. By default it is the right
  1072. * arrow key but by overriding this you can change the key to whatever you
  1073. * want.
  1074. *
  1075. * @return The keycode of the key
  1076. */
  1077. protected int getNavigationRightKey() {
  1078. return KeyCodes.KEY_RIGHT;
  1079. }
  1080. /**
  1081. * Get the key that selects a menu item. By default it is the Enter key but
  1082. * by overriding this you can change the key to whatever you want.
  1083. *
  1084. * @return
  1085. */
  1086. protected int getNavigationSelectKey() {
  1087. return KeyCodes.KEY_ENTER;
  1088. }
  1089. /**
  1090. * Get the key that closes the menu. By default it is the escape key but by
  1091. * overriding this yoy can change the key to whatever you want.
  1092. *
  1093. * @return
  1094. */
  1095. protected int getCloseMenuKey() {
  1096. return KeyCodes.KEY_ESCAPE;
  1097. }
  1098. /**
  1099. * Handles the keyboard events handled by the MenuBar
  1100. *
  1101. * @param event
  1102. * The keyboard event received
  1103. * @return true iff the navigation event was handled
  1104. */
  1105. public boolean handleNavigation(int keycode, boolean ctrl, boolean shift) {
  1106. // If tab or shift+tab close menus
  1107. if (keycode == KeyCodes.KEY_TAB) {
  1108. setSelected(null);
  1109. hideChildren();
  1110. menuVisible = false;
  1111. return false;
  1112. }
  1113. if (ctrl || shift || !isEnabled()) {
  1114. // Do not handle tab key, nor ctrl keys
  1115. return false;
  1116. }
  1117. if (keycode == getNavigationLeftKey()) {
  1118. if (getSelected() == null) {
  1119. // If nothing is selected then select the last item
  1120. setSelected(items.get(items.size() - 1));
  1121. if (getSelected().isSeparator() || !getSelected().isEnabled()) {
  1122. handleNavigation(keycode, ctrl, shift);
  1123. }
  1124. } else if (visibleChildMenu == null && getParentMenu() == null) {
  1125. // If this is the root menu then move to the right
  1126. int idx = items.indexOf(getSelected());
  1127. if (idx > 0) {
  1128. setSelected(items.get(idx - 1));
  1129. } else {
  1130. setSelected(items.get(items.size() - 1));
  1131. }
  1132. if (getSelected().isSeparator() || !getSelected().isEnabled()) {
  1133. handleNavigation(keycode, ctrl, shift);
  1134. }
  1135. } else if (visibleChildMenu != null) {
  1136. // Redirect all navigation to the submenu
  1137. visibleChildMenu.handleNavigation(keycode, ctrl, shift);
  1138. } else if (getParentMenu().getParentMenu() == null) {
  1139. // Get the root menu
  1140. VMenuBar root = getParentMenu();
  1141. root.getSelected().getSubMenu().setSelected(null);
  1142. root.hideChildren();
  1143. // Get the root menus items and select the previous one
  1144. int idx = root.getItems().indexOf(root.getSelected());
  1145. idx = idx > 0 ? idx : root.getItems().size();
  1146. CustomMenuItem selected = root.getItems().get(--idx);
  1147. while (selected.isSeparator() || !selected.isEnabled()) {
  1148. idx = idx > 0 ? idx : root.getItems().size();
  1149. selected = root.getItems().get(--idx);
  1150. }
  1151. root.setSelected(selected);
  1152. root.showChildMenu(selected);
  1153. VMenuBar submenu = selected.getSubMenu();
  1154. // Select the first item in the newly open submenu
  1155. submenu.setSelected(submenu.getItems().get(0));
  1156. } else {
  1157. getParentMenu().getSelected().getSubMenu().setSelected(null);
  1158. getParentMenu().hideChildren();
  1159. }
  1160. return true;
  1161. } else if (keycode == getNavigationRightKey()) {
  1162. if (getSelected() == null) {
  1163. // If nothing is selected then select the first item
  1164. setSelected(items.get(0));
  1165. if (getSelected().isSeparator() || !getSelected().isEnabled()) {
  1166. handleNavigation(keycode, ctrl, shift);
  1167. }
  1168. } else if (visibleChildMenu == null && getParentMenu() == null) {
  1169. // If this is the root menu then move to the right
  1170. int idx = items.indexOf(getSelected());
  1171. if (idx < items.size() - 1) {
  1172. setSelected(items.get(idx + 1));
  1173. } else {
  1174. setSelected(items.get(0));
  1175. }
  1176. if (getSelected().isSeparator() || !getSelected().isEnabled()) {
  1177. handleNavigation(keycode, ctrl, shift);
  1178. }
  1179. } else if (visibleChildMenu == null
  1180. && getSelected().getSubMenu() != null) {
  1181. // If the item has a submenu then show it and move the selection
  1182. // there
  1183. showChildMenu(getSelected());
  1184. menuVisible = true;
  1185. visibleChildMenu.handleNavigation(keycode, ctrl, shift);
  1186. } else if (visibleChildMenu == null) {
  1187. // Get the root menu
  1188. VMenuBar root = getParentMenu();
  1189. while (root.getParentMenu() != null) {
  1190. root = root.getParentMenu();
  1191. }
  1192. // Hide the submenu
  1193. root.hideChildren();
  1194. // Get the root menus items and select the next one
  1195. int idx = root.getItems().indexOf(root.getSelected());
  1196. idx = idx < root.getItems().size() - 1 ? idx : -1;
  1197. CustomMenuItem selected = root.getItems().get(++idx);
  1198. while (selected.isSeparator() || !selected.isEnabled()) {
  1199. idx = idx < root.getItems().size() - 1 ? idx : -1;
  1200. selected = root.getItems().get(++idx);
  1201. }
  1202. root.setSelected(selected);
  1203. root.showChildMenu(selected);
  1204. VMenuBar submenu = selected.getSubMenu();
  1205. // Select the first item in the newly open submenu
  1206. submenu.setSelected(submenu.getItems().get(0));
  1207. } else if (visibleChildMenu != null) {
  1208. // Redirect all navigation to the submenu
  1209. visibleChildMenu.handleNavigation(keycode, ctrl, shift);
  1210. }
  1211. return true;
  1212. } else if (keycode == getNavigationUpKey()) {
  1213. if (getSelected() == null) {
  1214. // If nothing is selected then select the last item
  1215. setSelected(items.get(items.size() - 1));
  1216. if (getSelected().isSeparator() || !getSelected().isEnabled()) {
  1217. handleNavigation(keycode, ctrl, shift);
  1218. }
  1219. } else if (visibleChildMenu != null) {
  1220. // Redirect all navigation to the submenu
  1221. visibleChildMenu.handleNavigation(keycode, ctrl, shift);
  1222. } else {
  1223. // Select the previous item if possible or loop to the last item
  1224. int idx = items.indexOf(getSelected());
  1225. if (idx > 0) {
  1226. setSelected(items.get(idx - 1));
  1227. } else {
  1228. setSelected(items.get(items.size() - 1));
  1229. }
  1230. if (getSelected().isSeparator() || !getSelected().isEnabled()) {
  1231. handleNavigation(keycode, ctrl, shift);
  1232. }
  1233. }
  1234. return true;
  1235. } else if (keycode == getNavigationDownKey()) {
  1236. if (getSelected() == null) {
  1237. // If nothing is selected then select the first item
  1238. setSelected(items.get(0));
  1239. if (getSelected().isSeparator() || !getSelected().isEnabled()) {
  1240. handleNavigation(keycode, ctrl, shift);
  1241. }
  1242. } else if (visibleChildMenu == null && getParentMenu() == null) {
  1243. // If this is the root menu the show the child menu with arrow
  1244. // down
  1245. showChildMenu(getSelected());
  1246. menuVisible = true;
  1247. visibleChildMenu.handleNavigation(keycode, ctrl, shift);
  1248. } else if (visibleChildMenu != null) {
  1249. // Redirect all navigation to the submenu
  1250. visibleChildMenu.handleNavigation(keycode, ctrl, shift);
  1251. } else {
  1252. // Select the next item if possible or loop to the first item
  1253. int idx = items.indexOf(getSelected());
  1254. if (idx < items.size() - 1) {
  1255. setSelected(items.get(idx + 1));
  1256. } else {
  1257. setSelected(items.get(0));
  1258. }
  1259. if (getSelected().isSeparator() || !getSelected().isEnabled()) {
  1260. handleNavigation(keycode, ctrl, shift);
  1261. }
  1262. }
  1263. return true;
  1264. } else if (keycode == getCloseMenuKey()) {
  1265. setSelected(null);
  1266. hideChildren();
  1267. menuVisible = false;
  1268. } else if (keycode == getNavigationSelectKey()) {
  1269. if (visibleChildMenu != null) {
  1270. // Redirect all navigation to the submenu
  1271. visibleChildMenu.handleNavigation(keycode, ctrl, shift);
  1272. menuVisible = false;
  1273. } else if (visibleChildMenu == null
  1274. && getSelected().getSubMenu() != null) {
  1275. // If the item has a submenu then show it and move the selection
  1276. // there
  1277. showChildMenu(getSelected());
  1278. menuVisible = true;
  1279. visibleChildMenu.handleNavigation(keycode, ctrl, shift);
  1280. } else {
  1281. Command command = getSelected().getCommand();
  1282. if (command != null) {
  1283. command.execute();
  1284. }
  1285. setSelected(null);
  1286. hideParents(true);
  1287. }
  1288. }
  1289. return false;
  1290. }
  1291. /*
  1292. * (non-Javadoc)
  1293. *
  1294. * @see
  1295. * com.google.gwt.event.dom.client.FocusHandler#onFocus(com.google.gwt.event
  1296. * .dom.client.FocusEvent)
  1297. */
  1298. public void onFocus(FocusEvent event) {
  1299. }
  1300. private final String SUBPART_PREFIX = "item";
  1301. public Element getSubPartElement(String subPart) {
  1302. int index = Integer
  1303. .parseInt(subPart.substring(SUBPART_PREFIX.length()));
  1304. CustomMenuItem item = getItems().get(index);
  1305. return item.getElement();
  1306. }
  1307. public String getSubPartName(Element subElement) {
  1308. if (!getElement().isOrHasChild(subElement)) {
  1309. return null;
  1310. }
  1311. Element menuItemRoot = subElement;
  1312. while (menuItemRoot != null && menuItemRoot.getParentElement() != null
  1313. && menuItemRoot.getParentElement() != getElement()) {
  1314. menuItemRoot = menuItemRoot.getParentElement().cast();
  1315. }
  1316. // "menuItemRoot" is now the root of the menu item
  1317. final int itemCount = getItems().size();
  1318. for (int i = 0; i < itemCount; i++) {
  1319. if (getItems().get(i).getElement() == menuItemRoot) {
  1320. String name = SUBPART_PREFIX + i;
  1321. return name;
  1322. }
  1323. }
  1324. return null;
  1325. }
  1326. public Widget getWidgetForPaintable() {
  1327. return this;
  1328. }
  1329. }