001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.user.client.ui;
017:
018: import com.google.gwt.user.client.Command;
019: import com.google.gwt.user.client.DOM;
020: import com.google.gwt.user.client.DeferredCommand;
021: import com.google.gwt.user.client.Element;
022: import com.google.gwt.user.client.Event;
023:
024: import java.util.ArrayList;
025: import java.util.List;
026:
027: /**
028: * A standard menu bar widget. A menu bar can contain any number of menu items,
029: * each of which can either fire a {@link com.google.gwt.user.client.Command} or
030: * open a cascaded menu bar.
031: *
032: * <p>
033: * <img class='gallery' src='MenuBar.png'/>
034: * </p>
035: *
036: * <h3>CSS Style Rules</h3>
037: * <ul class='css'>
038: * <li>.gwt-MenuBar { the menu bar itself }</li>
039: * <li>.gwt-MenuBar-horizontal { dependent style applied to horizontal menu
040: * bars }</li>
041: * <li>.gwt-MenuBar-vertical { dependent style applied to vertical menu bars }</li>
042: * <li>.gwt-MenuBar .gwt-MenuItem { menu items }</li>
043: * <li>.gwt-MenuBar .gwt-MenuItem-selected { selected menu items }</li>
044: * </ul>
045: *
046: * <p>
047: * <h3>Example</h3>
048: * {@example com.google.gwt.examples.MenuBarExample}
049: * </p>
050: */
051: public class MenuBar extends Widget implements PopupListener {
052:
053: private Element body;
054: private ArrayList<MenuItem> items = new ArrayList<MenuItem>();
055: private MenuBar parentMenu;
056: private PopupPanel popup;
057: private MenuItem selectedItem;
058: private MenuBar shownChildMenu;
059: private boolean vertical, autoOpen;
060:
061: /**
062: * Creates an empty horizontal menu bar.
063: */
064: public MenuBar() {
065: this (false);
066: }
067:
068: /**
069: * Creates an empty menu bar.
070: *
071: * @param vertical <code>true</code> to orient the menu bar vertically
072: */
073: public MenuBar(boolean vertical) {
074: super ();
075:
076: Element table = DOM.createTable();
077: body = DOM.createTBody();
078: DOM.appendChild(table, body);
079:
080: if (!vertical) {
081: Element tr = DOM.createTR();
082: DOM.appendChild(body, tr);
083: }
084:
085: this .vertical = vertical;
086:
087: Element outer = DOM.createDiv();
088: DOM.appendChild(outer, table);
089: setElement(outer);
090:
091: sinkEvents(Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT);
092: setStyleName("gwt-MenuBar");
093: if (vertical) {
094: addStyleDependentName("vertical");
095: } else {
096: addStyleDependentName("horizontal");
097: }
098: }
099:
100: /**
101: * Adds a menu item to the bar.
102: *
103: * @param item the item to be added
104: */
105: public void addItem(MenuItem item) {
106: Element tr;
107: if (vertical) {
108: tr = DOM.createTR();
109: DOM.appendChild(body, tr);
110: } else {
111: tr = DOM.getChild(body, 0);
112: }
113:
114: DOM.appendChild(tr, item.getElement());
115:
116: item.setParentMenu(this );
117: item.setSelectionStyle(false);
118: items.add(item);
119: }
120:
121: /**
122: * Adds a menu item to the bar, that will fire the given command when it is
123: * selected.
124: *
125: * @param text the item's text
126: * @param asHTML <code>true</code> to treat the specified text as html
127: * @param cmd the command to be fired
128: * @return the {@link MenuItem} object created
129: */
130: public MenuItem addItem(String text, boolean asHTML, Command cmd) {
131: MenuItem item = new MenuItem(text, asHTML, cmd);
132: addItem(item);
133: return item;
134: }
135:
136: /**
137: * Adds a menu item to the bar, that will open the specified menu when it is
138: * selected.
139: *
140: * @param text the item's text
141: * @param asHTML <code>true</code> to treat the specified text as html
142: * @param popup the menu to be cascaded from it
143: * @return the {@link MenuItem} object created
144: */
145: public MenuItem addItem(String text, boolean asHTML, MenuBar popup) {
146: MenuItem item = new MenuItem(text, asHTML, popup);
147: addItem(item);
148: return item;
149: }
150:
151: /**
152: * Adds a menu item to the bar, that will fire the given command when it is
153: * selected.
154: *
155: * @param text the item's text
156: * @param cmd the command to be fired
157: * @return the {@link MenuItem} object created
158: */
159: public MenuItem addItem(String text, Command cmd) {
160: MenuItem item = new MenuItem(text, cmd);
161: addItem(item);
162: return item;
163: }
164:
165: /**
166: * Adds a menu item to the bar, that will open the specified menu when it is
167: * selected.
168: *
169: * @param text the item's text
170: * @param popup the menu to be cascaded from it
171: * @return the {@link MenuItem} object created
172: */
173: public MenuItem addItem(String text, MenuBar popup) {
174: MenuItem item = new MenuItem(text, popup);
175: addItem(item);
176: return item;
177: }
178:
179: /**
180: * Removes all menu items from this menu bar.
181: */
182: public void clearItems() {
183: Element container = getItemContainerElement();
184: while (DOM.getChildCount(container) > 0) {
185: DOM.removeChild(container, DOM.getChild(container, 0));
186: }
187: items.clear();
188: }
189:
190: /**
191: * Gets whether this menu bar's child menus will open when the mouse is moved
192: * over it.
193: *
194: * @return <code>true</code> if child menus will auto-open
195: */
196: public boolean getAutoOpen() {
197: return autoOpen;
198: }
199:
200: @Override
201: public void onBrowserEvent(Event event) {
202: super .onBrowserEvent(event);
203:
204: MenuItem item = findItem(DOM.eventGetTarget(event));
205: switch (DOM.eventGetType(event)) {
206: case Event.ONCLICK: {
207: // Fire an item's command when the user clicks on it.
208: if (item != null) {
209: doItemAction(item, true);
210: }
211: break;
212: }
213:
214: case Event.ONMOUSEOVER: {
215: if (item != null) {
216: itemOver(item);
217: }
218: break;
219: }
220:
221: case Event.ONMOUSEOUT: {
222: if (item != null) {
223: itemOver(null);
224: }
225: break;
226: }
227: }
228: }
229:
230: public void onPopupClosed(PopupPanel sender, boolean autoClosed) {
231: // If the menu popup was auto-closed, close all of its parents as well.
232: if (autoClosed) {
233: closeAllParents();
234: }
235:
236: // When the menu popup closes, remember that no item is
237: // currently showing a popup menu.
238: onHide();
239: shownChildMenu = null;
240: popup = null;
241: }
242:
243: /**
244: * Removes the specified menu item from the bar.
245: *
246: * @param item the item to be removed
247: */
248: public void removeItem(MenuItem item) {
249: int idx = items.indexOf(item);
250: if (idx == -1) {
251: return;
252: }
253:
254: Element container = getItemContainerElement();
255: DOM.removeChild(container, DOM.getChild(container, idx));
256: items.remove(idx);
257: }
258:
259: /**
260: * Sets whether this menu bar's child menus will open when the mouse is moved
261: * over it.
262: *
263: * @param autoOpen <code>true</code> to cause child menus to auto-open
264: */
265: public void setAutoOpen(boolean autoOpen) {
266: this .autoOpen = autoOpen;
267: }
268:
269: /**
270: * Returns a list containing the <code>MenuItem</code> objects in the menu
271: * bar. If there are no items in the menu bar, then an empty <code>List</code>
272: * object will be returned.
273: *
274: * @return a list containing the <code>MenuItem</code> objects in the menu
275: * bar
276: */
277: protected List<MenuItem> getItems() {
278: return this .items;
279: }
280:
281: /**
282: * Returns the <code>MenuItem</code> that is currently selected
283: * (highlighted) by the user. If none of the items in the menu are currently
284: * selected, then <code>null</code> will be returned.
285: *
286: * @return the <code>MenuItem</code> that is currently selected, or
287: * <code>null</code> if no items are currently selected
288: */
289: protected MenuItem getSelectedItem() {
290: return this .selectedItem;
291: }
292:
293: @Override
294: protected void onDetach() {
295: // When the menu is detached, make sure to close all of its children.
296: if (popup != null) {
297: popup.hide();
298: }
299:
300: super .onDetach();
301: }
302:
303: /*
304: * Closes all parent menu popups.
305: */
306: void closeAllParents() {
307: MenuBar curMenu = this ;
308: while (curMenu != null) {
309: curMenu.close();
310:
311: if ((curMenu.parentMenu == null)
312: && (curMenu.selectedItem != null)) {
313: curMenu.selectedItem.setSelectionStyle(false);
314: curMenu.selectedItem = null;
315: }
316:
317: curMenu = curMenu.parentMenu;
318: }
319: }
320:
321: /*
322: * Performs the action associated with the given menu item. If the item has a
323: * popup associated with it, the popup will be shown. If it has a command
324: * associated with it, and 'fireCommand' is true, then the command will be
325: * fired. Popups associated with other items will be hidden.
326: *
327: * @param item the item whose popup is to be shown. @param fireCommand <code>true</code>
328: * if the item's command should be fired, <code>false</code> otherwise.
329: */
330: void doItemAction(final MenuItem item, boolean fireCommand) {
331: // If the given item is already showing its menu, we're done.
332: if ((shownChildMenu != null)
333: && (item.getSubMenu() == shownChildMenu)) {
334: return;
335: }
336:
337: // If another item is showing its menu, then hide it.
338: if (shownChildMenu != null) {
339: shownChildMenu.onHide();
340: popup.hide();
341: }
342:
343: // If the item has no popup, optionally fire its command.
344: if (item.getSubMenu() == null) {
345: if (fireCommand) {
346: // Close this menu and all of its parents.
347: closeAllParents();
348:
349: // Fire the item's command.
350: Command cmd = item.getCommand();
351: if (cmd != null) {
352: DeferredCommand.addCommand(cmd);
353: }
354: }
355: return;
356: }
357:
358: // Ensure that the item is selected.
359: selectItem(item);
360:
361: // Create a new popup for this item, and position it next to
362: // the item (below if this is a horizontal menu bar, to the
363: // right if it's a vertical bar).
364: popup = new PopupPanel(true) {
365: {
366: setWidget(item.getSubMenu());
367: item.getSubMenu().onShow();
368: }
369:
370: @Override
371: public boolean onEventPreview(Event event) {
372: // Hook the popup panel's event preview. We use this to keep it from
373: // auto-hiding when the parent menu is clicked.
374: switch (DOM.eventGetType(event)) {
375: case Event.ONCLICK:
376: // If the event target is part of the parent menu, suppress the
377: // event altogether.
378: Element target = DOM.eventGetTarget(event);
379: Element parentMenuElement = item.getParentMenu()
380: .getElement();
381: if (DOM.isOrHasChild(parentMenuElement, target)) {
382: return false;
383: }
384: break;
385: }
386:
387: return super .onEventPreview(event);
388: }
389: };
390: popup.addPopupListener(this );
391:
392: if (vertical) {
393: popup.setPopupPosition(this .getAbsoluteLeft()
394: + this .getOffsetWidth() - 1, item.getAbsoluteTop());
395: } else {
396: popup.setPopupPosition(item.getAbsoluteLeft(), this
397: .getAbsoluteTop()
398: + this .getOffsetHeight() - 1);
399: }
400:
401: shownChildMenu = item.getSubMenu();
402: item.getSubMenu().parentMenu = this ;
403:
404: // Show the popup, ensuring that the menubar's event preview remains on top
405: // of the popup's.
406: popup.show();
407: }
408:
409: void itemOver(MenuItem item) {
410: if (item == null) {
411: // Don't clear selection if the currently selected item's menu is showing.
412: if ((selectedItem != null)
413: && (shownChildMenu == selectedItem.getSubMenu())) {
414: return;
415: }
416: }
417:
418: // Style the item selected when the mouse enters.
419: selectItem(item);
420:
421: // If child menus are being shown, or this menu is itself
422: // a child menu, automatically show an item's child menu
423: // when the mouse enters.
424: if (item != null) {
425: if ((shownChildMenu != null) || (parentMenu != null)
426: || autoOpen) {
427: doItemAction(item, false);
428: }
429: }
430: }
431:
432: void selectItem(MenuItem item) {
433: if (item == selectedItem) {
434: return;
435: }
436:
437: if (selectedItem != null) {
438: selectedItem.setSelectionStyle(false);
439: }
440:
441: if (item != null) {
442: item.setSelectionStyle(true);
443: }
444:
445: selectedItem = item;
446: }
447:
448: /**
449: * Closes this menu (if it is a popup).
450: */
451: private void close() {
452: if (parentMenu != null) {
453: parentMenu.popup.hide();
454: }
455: }
456:
457: private MenuItem findItem(Element hItem) {
458: for (int i = 0; i < items.size(); ++i) {
459: MenuItem item = items.get(i);
460: if (DOM.isOrHasChild(item.getElement(), hItem)) {
461: return item;
462: }
463: }
464:
465: return null;
466: }
467:
468: private Element getItemContainerElement() {
469: if (vertical) {
470: return body;
471: } else {
472: return DOM.getChild(body, 0);
473: }
474: }
475:
476: /*
477: * This method is called when a menu bar is hidden, so that it can hide any
478: * child popups that are currently being shown.
479: */
480: private void onHide() {
481: if (shownChildMenu != null) {
482: shownChildMenu.onHide();
483: popup.hide();
484: }
485: }
486:
487: /*
488: * This method is called when a menu bar is shown.
489: */
490: private void onShow() {
491: // Select the first item when a menu is shown.
492: if (items.size() > 0) {
493: selectItem(items.get(0));
494: }
495: }
496: }
|