001 /*
002 * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.swing;
027
028 import java.awt.Component;
029 import java.awt.Dimension;
030 import java.awt.Graphics;
031 import java.awt.Insets;
032 import java.awt.Point;
033 import java.awt.Rectangle;
034 import java.awt.event.*;
035 import java.util.Vector;
036 import java.util.Enumeration;
037
038 import java.io.Serializable;
039 import java.io.ObjectOutputStream;
040 import java.io.ObjectInputStream;
041 import java.io.IOException;
042
043 import javax.swing.event.*;
044 import javax.swing.border.Border;
045 import javax.swing.plaf.*;
046 import javax.accessibility.*;
047
048 /**
049 * An implementation of a menu bar. You add <code>JMenu</code> objects to the
050 * menu bar to construct a menu. When the user selects a <code>JMenu</code>
051 * object, its associated <code>JPopupMenu</code> is displayed, allowing the
052 * user to select one of the <code>JMenuItems</code> on it.
053 * <p>
054 * For information and examples of using menu bars see
055 * <a
056 href="http://java.sun.com/docs/books/tutorial/uiswing/components/menu.html">How to Use Menus</a>,
057 * a section in <em>The Java Tutorial.</em>
058 * <p>
059 * <strong>Warning:</strong> Swing is not thread safe. For more
060 * information see <a
061 * href="package-summary.html#threading">Swing's Threading
062 * Policy</a>.
063 * <p>
064 * <strong>Warning:</strong>
065 * Serialized objects of this class will not be compatible with
066 * future Swing releases. The current serialization support is
067 * appropriate for short term storage or RMI between applications running
068 * the same version of Swing. As of 1.4, support for long term storage
069 * of all JavaBeans<sup><font size="-2">TM</font></sup>
070 * has been added to the <code>java.beans</code> package.
071 * Please see {@link java.beans.XMLEncoder}.
072 *
073 * @beaninfo
074 * attribute: isContainer true
075 * description: A container for holding and displaying menus.
076 *
077 * @version 1.112 05/05/07
078 * @author Georges Saab
079 * @author David Karlton
080 * @author Arnaud Weber
081 * @see JMenu
082 * @see JPopupMenu
083 * @see JMenuItem
084 */
085 public class JMenuBar extends JComponent implements Accessible,
086 MenuElement {
087 /**
088 * @see #getUIClassID
089 * @see #readObject
090 */
091 private static final String uiClassID = "MenuBarUI";
092
093 /*
094 * Model for the selected subcontrol.
095 */
096 private transient SingleSelectionModel selectionModel;
097
098 private boolean paintBorder = true;
099 private Insets margin = null;
100
101 /* diagnostic aids -- should be false for production builds. */
102 private static final boolean TRACE = false; // trace creates and disposes
103 private static final boolean VERBOSE = false; // show reuse hits/misses
104 private static final boolean DEBUG = false; // show bad params, misc.
105
106 /**
107 * Creates a new menu bar.
108 */
109 public JMenuBar() {
110 super ();
111 setFocusTraversalKeysEnabled(false);
112 setSelectionModel(new DefaultSingleSelectionModel());
113 updateUI();
114 }
115
116 /**
117 * Returns the menubar's current UI.
118 * @see #setUI
119 */
120 public MenuBarUI getUI() {
121 return (MenuBarUI) ui;
122 }
123
124 /**
125 * Sets the L&F object that renders this component.
126 *
127 * @param ui the new MenuBarUI L&F object
128 * @see UIDefaults#getUI
129 * @beaninfo
130 * bound: true
131 * hidden: true
132 * attribute: visualUpdate true
133 * description: The UI object that implements the Component's LookAndFeel.
134 */
135 public void setUI(MenuBarUI ui) {
136 super .setUI(ui);
137 }
138
139 /**
140 * Resets the UI property with a value from the current look and feel.
141 *
142 * @see JComponent#updateUI
143 */
144 public void updateUI() {
145 setUI((MenuBarUI) UIManager.getUI(this ));
146 }
147
148 /**
149 * Returns the name of the L&F class that renders this component.
150 *
151 * @return the string "MenuBarUI"
152 * @see JComponent#getUIClassID
153 * @see UIDefaults#getUI
154 */
155 public String getUIClassID() {
156 return uiClassID;
157 }
158
159 /**
160 * Returns the model object that handles single selections.
161 *
162 * @return the <code>SingleSelectionModel</code> property
163 * @see SingleSelectionModel
164 */
165 public SingleSelectionModel getSelectionModel() {
166 return selectionModel;
167 }
168
169 /**
170 * Sets the model object to handle single selections.
171 *
172 * @param model the <code>SingleSelectionModel</code> to use
173 * @see SingleSelectionModel
174 * @beaninfo
175 * bound: true
176 * description: The selection model, recording which child is selected.
177 */
178 public void setSelectionModel(SingleSelectionModel model) {
179 SingleSelectionModel oldValue = selectionModel;
180 this .selectionModel = model;
181 firePropertyChange("selectionModel", oldValue, selectionModel);
182 }
183
184 /**
185 * Appends the specified menu to the end of the menu bar.
186 *
187 * @param c the <code>JMenu</code> component to add
188 * @return the menu component
189 */
190 public JMenu add(JMenu c) {
191 super .add(c);
192 return c;
193 }
194
195 /**
196 * Returns the menu at the specified position in the menu bar.
197 *
198 * @param index an integer giving the position in the menu bar, where
199 * 0 is the first position
200 * @return the <code>JMenu</code> at that position, or <code>null</code> if
201 * if there is no <code>JMenu</code> at that position (ie. if
202 * it is a <code>JMenuItem</code>)
203 */
204 public JMenu getMenu(int index) {
205 Component c = getComponentAtIndex(index);
206 if (c instanceof JMenu)
207 return (JMenu) c;
208 return null;
209 }
210
211 /**
212 * Returns the number of items in the menu bar.
213 *
214 * @return the number of items in the menu bar
215 */
216 public int getMenuCount() {
217 return getComponentCount();
218 }
219
220 /**
221 * Sets the help menu that appears when the user selects the
222 * "help" option in the menu bar. This method is not yet implemented
223 * and will throw an exception.
224 *
225 * @param menu the JMenu that delivers help to the user
226 */
227 public void setHelpMenu(JMenu menu) {
228 throw new Error("setHelpMenu() not yet implemented.");
229 }
230
231 /**
232 * Gets the help menu for the menu bar. This method is not yet
233 * implemented and will throw an exception.
234 *
235 * @return the <code>JMenu</code> that delivers help to the user
236 */
237 public JMenu getHelpMenu() {
238 throw new Error("getHelpMenu() not yet implemented.");
239 }
240
241 /**
242 * Returns the component at the specified index.
243 *
244 * @param i an integer specifying the position, where 0 is first
245 * @return the <code>Component</code> at the position,
246 * or <code>null</code> for an invalid index
247 * @deprecated replaced by <code>getComponent(int i)</code>
248 */
249 @Deprecated
250 public Component getComponentAtIndex(int i) {
251 if (i < 0 || i >= getComponentCount()) {
252 return null;
253 }
254 return getComponent(i);
255 }
256
257 /**
258 * Returns the index of the specified component.
259 *
260 * @param c the <code>Component</code> to find
261 * @return an integer giving the component's position, where 0 is first;
262 * or -1 if it can't be found
263 */
264 public int getComponentIndex(Component c) {
265 int ncomponents = this .getComponentCount();
266 Component[] component = this .getComponents();
267 for (int i = 0; i < ncomponents; i++) {
268 Component comp = component[i];
269 if (comp == c)
270 return i;
271 }
272 return -1;
273 }
274
275 /**
276 * Sets the currently selected component, producing a
277 * a change to the selection model.
278 *
279 * @param sel the <code>Component</code> to select
280 */
281 public void setSelected(Component sel) {
282 SingleSelectionModel model = getSelectionModel();
283 int index = getComponentIndex(sel);
284 model.setSelectedIndex(index);
285 }
286
287 /**
288 * Returns true if the menu bar currently has a component selected.
289 *
290 * @return true if a selection has been made, else false
291 */
292 public boolean isSelected() {
293 return selectionModel.isSelected();
294 }
295
296 /**
297 * Returns true if the menu bars border should be painted.
298 *
299 * @return true if the border should be painted, else false
300 */
301 public boolean isBorderPainted() {
302 return paintBorder;
303 }
304
305 /**
306 * Sets whether the border should be painted.
307 *
308 * @param b if true and border property is not <code>null</code>,
309 * the border is painted.
310 * @see #isBorderPainted
311 * @beaninfo
312 * bound: true
313 * attribute: visualUpdate true
314 * description: Whether the border should be painted.
315 */
316 public void setBorderPainted(boolean b) {
317 boolean oldValue = paintBorder;
318 paintBorder = b;
319 firePropertyChange("borderPainted", oldValue, paintBorder);
320 if (b != oldValue) {
321 revalidate();
322 repaint();
323 }
324 }
325
326 /**
327 * Paints the menubar's border if <code>BorderPainted</code>
328 * property is true.
329 *
330 * @param g the <code>Graphics</code> context to use for painting
331 * @see JComponent#paint
332 * @see JComponent#setBorder
333 */
334 protected void paintBorder(Graphics g) {
335 if (isBorderPainted()) {
336 super .paintBorder(g);
337 }
338 }
339
340 /**
341 * Sets the margin between the menubar's border and
342 * its menus. Setting to <code>null</code> will cause the menubar to
343 * use the default margins.
344 *
345 * @param m an Insets object containing the margin values
346 * @see Insets
347 * @beaninfo
348 * bound: true
349 * attribute: visualUpdate true
350 * description: The space between the menubar's border and its contents
351 */
352 public void setMargin(Insets m) {
353 Insets old = margin;
354 this .margin = m;
355 firePropertyChange("margin", old, m);
356 if (old == null || !old.equals(m)) {
357 revalidate();
358 repaint();
359 }
360 }
361
362 /**
363 * Returns the margin between the menubar's border and
364 * its menus. If there is no previous margin, it will create
365 * a default margin with zero size.
366 *
367 * @return an <code>Insets</code> object containing the margin values
368 * @see Insets
369 */
370 public Insets getMargin() {
371 if (margin == null) {
372 return new Insets(0, 0, 0, 0);
373 } else {
374 return margin;
375 }
376 }
377
378 /**
379 * Implemented to be a <code>MenuElement</code> -- does nothing.
380 *
381 * @see #getSubElements
382 */
383 public void processMouseEvent(MouseEvent event, MenuElement path[],
384 MenuSelectionManager manager) {
385 }
386
387 /**
388 * Implemented to be a <code>MenuElement</code> -- does nothing.
389 *
390 * @see #getSubElements
391 */
392 public void processKeyEvent(KeyEvent e, MenuElement path[],
393 MenuSelectionManager manager) {
394 }
395
396 /**
397 * Implemented to be a <code>MenuElement</code> -- does nothing.
398 *
399 * @see #getSubElements
400 */
401 public void menuSelectionChanged(boolean isIncluded) {
402 }
403
404 /**
405 * Implemented to be a <code>MenuElement</code> -- returns the
406 * menus in this menu bar.
407 * This is the reason for implementing the <code>MenuElement</code>
408 * interface -- so that the menu bar can be treated the same as
409 * other menu elements.
410 * @return an array of menu items in the menu bar.
411 */
412 public MenuElement[] getSubElements() {
413 MenuElement result[];
414 Vector tmp = new Vector();
415 int c = getComponentCount();
416 int i;
417 Component m;
418
419 for (i = 0; i < c; i++) {
420 m = getComponent(i);
421 if (m instanceof MenuElement)
422 tmp.addElement(m);
423 }
424
425 result = new MenuElement[tmp.size()];
426 for (i = 0, c = tmp.size(); i < c; i++)
427 result[i] = (MenuElement) tmp.elementAt(i);
428 return result;
429 }
430
431 /**
432 * Implemented to be a <code>MenuElement</code>. Returns this object.
433 *
434 * @return the current <code>Component</code> (this)
435 * @see #getSubElements
436 */
437 public Component getComponent() {
438 return this ;
439 }
440
441 /**
442 * Returns a string representation of this <code>JMenuBar</code>.
443 * This method
444 * is intended to be used only for debugging purposes, and the
445 * content and format of the returned string may vary between
446 * implementations. The returned string may be empty but may not
447 * be <code>null</code>.
448 *
449 * @return a string representation of this <code>JMenuBar</code>
450 */
451 protected String paramString() {
452 String paintBorderString = (paintBorder ? "true" : "false");
453 String marginString = (margin != null ? margin.toString() : "");
454
455 return super .paramString() + ",margin=" + marginString
456 + ",paintBorder=" + paintBorderString;
457 }
458
459 /////////////////
460 // Accessibility support
461 ////////////////
462
463 /**
464 * Gets the AccessibleContext associated with this JMenuBar.
465 * For JMenuBars, the AccessibleContext takes the form of an
466 * AccessibleJMenuBar.
467 * A new AccessibleJMenuBar instance is created if necessary.
468 *
469 * @return an AccessibleJMenuBar that serves as the
470 * AccessibleContext of this JMenuBar
471 */
472 public AccessibleContext getAccessibleContext() {
473 if (accessibleContext == null) {
474 accessibleContext = new AccessibleJMenuBar();
475 }
476 return accessibleContext;
477 }
478
479 /**
480 * This class implements accessibility support for the
481 * <code>JMenuBar</code> class. It provides an implementation of the
482 * Java Accessibility API appropriate to menu bar user-interface
483 * elements.
484 * <p>
485 * <strong>Warning:</strong>
486 * Serialized objects of this class will not be compatible with
487 * future Swing releases. The current serialization support is
488 * appropriate for short term storage or RMI between applications running
489 * the same version of Swing. As of 1.4, support for long term storage
490 * of all JavaBeans<sup><font size="-2">TM</font></sup>
491 * has been added to the <code>java.beans</code> package.
492 * Please see {@link java.beans.XMLEncoder}.
493 */
494 protected class AccessibleJMenuBar extends AccessibleJComponent
495 implements AccessibleSelection {
496
497 /**
498 * Get the accessible state set of this object.
499 *
500 * @return an instance of AccessibleState containing the current state
501 * of the object
502 */
503 public AccessibleStateSet getAccessibleStateSet() {
504 AccessibleStateSet states = super .getAccessibleStateSet();
505 return states;
506 }
507
508 /**
509 * Get the role of this object.
510 *
511 * @return an instance of AccessibleRole describing the role of the
512 * object
513 */
514 public AccessibleRole getAccessibleRole() {
515 return AccessibleRole.MENU_BAR;
516 }
517
518 /**
519 * Get the AccessibleSelection associated with this object. In the
520 * implementation of the Java Accessibility API for this class,
521 * return this object, which is responsible for implementing the
522 * AccessibleSelection interface on behalf of itself.
523 *
524 * @return this object
525 */
526 public AccessibleSelection getAccessibleSelection() {
527 return this ;
528 }
529
530 /**
531 * Returns 1 if a menu is currently selected in this menu bar.
532 *
533 * @return 1 if a menu is currently selected, else 0
534 */
535 public int getAccessibleSelectionCount() {
536 if (isSelected()) {
537 return 1;
538 } else {
539 return 0;
540 }
541 }
542
543 /**
544 * Returns the currently selected menu if one is selected,
545 * otherwise null.
546 */
547 public Accessible getAccessibleSelection(int i) {
548 if (isSelected()) {
549 if (i != 0) { // single selection model for JMenuBar
550 return null;
551 }
552 int j = getSelectionModel().getSelectedIndex();
553 if (getComponentAtIndex(j) instanceof Accessible) {
554 return (Accessible) getComponentAtIndex(j);
555 }
556 }
557 return null;
558 }
559
560 /**
561 * Returns true if the current child of this object is selected.
562 *
563 * @param i the zero-based index of the child in this Accessible
564 * object.
565 * @see AccessibleContext#getAccessibleChild
566 */
567 public boolean isAccessibleChildSelected(int i) {
568 return (i == getSelectionModel().getSelectedIndex());
569 }
570
571 /**
572 * Selects the nth menu in the menu bar, forcing it to
573 * pop up. If another menu is popped up, this will force
574 * it to close. If the nth menu is already selected, this
575 * method has no effect.
576 *
577 * @param i the zero-based index of selectable items
578 * @see #getAccessibleStateSet
579 */
580 public void addAccessibleSelection(int i) {
581 // first close up any open menu
582 int j = getSelectionModel().getSelectedIndex();
583 if (i == j) {
584 return;
585 }
586 if (j >= 0 && j < getMenuCount()) {
587 JMenu menu = getMenu(j);
588 if (menu != null) {
589 MenuSelectionManager.defaultManager()
590 .setSelectedPath(null);
591 // menu.setPopupMenuVisible(false);
592 }
593 }
594 // now popup the new menu
595 getSelectionModel().setSelectedIndex(i);
596 JMenu menu = getMenu(i);
597 if (menu != null) {
598 MenuElement me[] = new MenuElement[3];
599 me[0] = JMenuBar.this ;
600 me[1] = menu;
601 me[2] = menu.getPopupMenu();
602 MenuSelectionManager.defaultManager().setSelectedPath(
603 me);
604 // menu.setPopupMenuVisible(true);
605 }
606 }
607
608 /**
609 * Removes the nth selected item in the object from the object's
610 * selection. If the nth item isn't currently selected, this
611 * method has no effect. Otherwise, it closes the popup menu.
612 *
613 * @param i the zero-based index of selectable items
614 */
615 public void removeAccessibleSelection(int i) {
616 if (i >= 0 && i < getMenuCount()) {
617 JMenu menu = getMenu(i);
618 if (menu != null) {
619 MenuSelectionManager.defaultManager()
620 .setSelectedPath(null);
621 // menu.setPopupMenuVisible(false);
622 }
623 getSelectionModel().setSelectedIndex(-1);
624 }
625 }
626
627 /**
628 * Clears the selection in the object, so that nothing in the
629 * object is selected. This will close any open menu.
630 */
631 public void clearAccessibleSelection() {
632 int i = getSelectionModel().getSelectedIndex();
633 if (i >= 0 && i < getMenuCount()) {
634 JMenu menu = getMenu(i);
635 if (menu != null) {
636 MenuSelectionManager.defaultManager()
637 .setSelectedPath(null);
638 // menu.setPopupMenuVisible(false);
639 }
640 }
641 getSelectionModel().setSelectedIndex(-1);
642 }
643
644 /**
645 * Normally causes every selected item in the object to be selected
646 * if the object supports multiple selections. This method
647 * makes no sense in a menu bar, and so does nothing.
648 */
649 public void selectAllAccessibleSelection() {
650 }
651 } // internal class AccessibleJMenuBar
652
653 /**
654 * Subclassed to check all the child menus.
655 * @since 1.3
656 */
657 protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
658 int condition, boolean pressed) {
659 // See if we have a local binding.
660 boolean retValue = super .processKeyBinding(ks, e, condition,
661 pressed);
662 if (!retValue) {
663 MenuElement[] subElements = getSubElements();
664 for (int i = 0; i < subElements.length; i++) {
665 if (processBindingForKeyStrokeRecursive(subElements[i],
666 ks, e, condition, pressed)) {
667 return true;
668 }
669 }
670 }
671 return retValue;
672 }
673
674 static boolean processBindingForKeyStrokeRecursive(
675 MenuElement elem, KeyStroke ks, KeyEvent e, int condition,
676 boolean pressed) {
677 if (elem == null) {
678 return false;
679 }
680
681 Component c = elem.getComponent();
682
683 if (!(c.isVisible() || (c instanceof JPopupMenu))
684 || !c.isEnabled()) {
685 return false;
686 }
687
688 if (c != null
689 && c instanceof JComponent
690 && ((JComponent) c).processKeyBinding(ks, e, condition,
691 pressed)) {
692
693 return true;
694 }
695
696 MenuElement[] subElements = elem.getSubElements();
697 for (int i = 0; i < subElements.length; i++) {
698 if (processBindingForKeyStrokeRecursive(subElements[i], ks,
699 e, condition, pressed)) {
700 return true;
701 // We don't, pass along to children JMenu's
702 }
703 }
704 return false;
705 }
706
707 /**
708 * Overrides <code>JComponent.addNotify</code> to register this
709 * menu bar with the current keyboard manager.
710 */
711 public void addNotify() {
712 super .addNotify();
713 KeyboardManager.getCurrentManager().registerMenuBar(this );
714 }
715
716 /**
717 * Overrides <code>JComponent.removeNotify</code> to unregister this
718 * menu bar with the current keyboard manager.
719 */
720 public void removeNotify() {
721 super .removeNotify();
722 KeyboardManager.getCurrentManager().unregisterMenuBar(this );
723 }
724
725 private void writeObject(ObjectOutputStream s) throws IOException {
726 s.defaultWriteObject();
727 if (getUIClassID().equals(uiClassID)) {
728 byte count = JComponent.getWriteObjCounter(this );
729 JComponent.setWriteObjCounter(this , --count);
730 if (count == 0 && ui != null) {
731 ui.installUI(this );
732 }
733 }
734
735 Object[] kvData = new Object[4];
736 int n = 0;
737
738 if (selectionModel instanceof Serializable) {
739 kvData[n++] = "selectionModel";
740 kvData[n++] = selectionModel;
741 }
742
743 s.writeObject(kvData);
744 }
745
746 /**
747 * See JComponent.readObject() for information about serialization
748 * in Swing.
749 */
750 private void readObject(ObjectInputStream s) throws IOException,
751 ClassNotFoundException {
752 s.defaultReadObject();
753 Object[] kvData = (Object[]) (s.readObject());
754
755 for (int i = 0; i < kvData.length; i += 2) {
756 if (kvData[i] == null) {
757 break;
758 } else if (kvData[i].equals("selectionModel")) {
759 selectionModel = (SingleSelectionModel) kvData[i + 1];
760 }
761 }
762
763 }
764 }
|