1 /*
2 * Copyright 1997-2008 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26 package javax.swing;
27
28 import java.awt.Component;
29 import java.awt.Dimension;
30 import java.awt.Graphics;
31 import java.awt.Insets;
32 import java.awt.Point;
33 import java.awt.Rectangle;
34 import java.awt.event;
35 import java.beans.Transient;
36 import java.util.Vector;
37 import java.util.Enumeration;
38
39 import java.io.Serializable;
40 import java.io.ObjectOutputStream;
41 import java.io.ObjectInputStream;
42 import java.io.IOException;
43
44 import javax.swing.event;
45 import javax.swing.border.Border;
46 import javax.swing.plaf;
47 import javax.accessibility;
48
49 /**
50 * An implementation of a menu bar. You add <code>JMenu</code> objects to the
51 * menu bar to construct a menu. When the user selects a <code>JMenu</code>
52 * object, its associated <code>JPopupMenu</code> is displayed, allowing the
53 * user to select one of the <code>JMenuItems</code> on it.
54 * <p>
55 * For information and examples of using menu bars see
56 * <a
57 href="http://java.sun.com/docs/books/tutorial/uiswing/components/menu.html">How to Use Menus</a>,
58 * a section in <em>The Java Tutorial.</em>
59 * <p>
60 * <strong>Warning:</strong> Swing is not thread safe. For more
61 * information see <a
62 * href="package-summary.html#threading">Swing's Threading
63 * Policy</a>.
64 * <p>
65 * <strong>Warning:</strong>
66 * Serialized objects of this class will not be compatible with
67 * future Swing releases. The current serialization support is
68 * appropriate for short term storage or RMI between applications running
69 * the same version of Swing. As of 1.4, support for long term storage
70 * of all JavaBeans<sup><font size="-2">TM</font></sup>
71 * has been added to the <code>java.beans</code> package.
72 * Please see {@link java.beans.XMLEncoder}.
73 *
74 * @beaninfo
75 * attribute: isContainer true
76 * description: A container for holding and displaying menus.
77 *
78 * @author Georges Saab
79 * @author David Karlton
80 * @author Arnaud Weber
81 * @see JMenu
82 * @see JPopupMenu
83 * @see JMenuItem
84 */
85 public class JMenuBar extends JComponent implements Accessible,MenuElement
86 {
87 /**
88 * @see #getUIClassID
89 * @see #readObject
90 */
91 private static final String uiClassID = "MenuBarUI";
92
93 /*
94 * Model for the selected subcontrol.
95 */
96 private transient SingleSelectionModel selectionModel;
97
98 private boolean paintBorder = true;
99 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 /**
150 * Returns the name of the L&F class that renders this component.
151 *
152 * @return the string "MenuBarUI"
153 * @see JComponent#getUIClassID
154 * @see UIDefaults#getUI
155 */
156 public String getUIClassID() {
157 return uiClassID;
158 }
159
160
161 /**
162 * Returns the model object that handles single selections.
163 *
164 * @return the <code>SingleSelectionModel</code> property
165 * @see SingleSelectionModel
166 */
167 public SingleSelectionModel getSelectionModel() {
168 return selectionModel;
169 }
170
171 /**
172 * Sets the model object to handle single selections.
173 *
174 * @param model the <code>SingleSelectionModel</code> to use
175 * @see SingleSelectionModel
176 * @beaninfo
177 * bound: true
178 * description: The selection model, recording which child is selected.
179 */
180 public void setSelectionModel(SingleSelectionModel model) {
181 SingleSelectionModel oldValue = selectionModel;
182 this.selectionModel = model;
183 firePropertyChange("selectionModel", oldValue, selectionModel);
184 }
185
186
187 /**
188 * Appends the specified menu to the end of the menu bar.
189 *
190 * @param c the <code>JMenu</code> component to add
191 * @return the menu component
192 */
193 public JMenu add(JMenu c) {
194 super.add(c);
195 return c;
196 }
197
198 /**
199 * Returns the menu at the specified position in the menu bar.
200 *
201 * @param index an integer giving the position in the menu bar, where
202 * 0 is the first position
203 * @return the <code>JMenu</code> at that position, or <code>null</code> if
204 * if there is no <code>JMenu</code> at that position (ie. if
205 * it is a <code>JMenuItem</code>)
206 */
207 public JMenu getMenu(int index) {
208 Component c = getComponentAtIndex(index);
209 if (c instanceof JMenu)
210 return (JMenu) c;
211 return null;
212 }
213
214 /**
215 * Returns the number of items in the menu bar.
216 *
217 * @return the number of items in the menu bar
218 */
219 public int getMenuCount() {
220 return getComponentCount();
221 }
222
223 /**
224 * Sets the help menu that appears when the user selects the
225 * "help" option in the menu bar. This method is not yet implemented
226 * and will throw an exception.
227 *
228 * @param menu the JMenu that delivers help to the user
229 */
230 public void setHelpMenu(JMenu menu) {
231 throw new Error("setHelpMenu() not yet implemented.");
232 }
233
234 /**
235 * Gets the help menu for the menu bar. This method is not yet
236 * implemented and will throw an exception.
237 *
238 * @return the <code>JMenu</code> that delivers help to the user
239 */
240 @Transient
241 public JMenu getHelpMenu() {
242 throw new Error("getHelpMenu() not yet implemented.");
243 }
244
245 /**
246 * Returns the component at the specified index.
247 *
248 * @param i an integer specifying the position, where 0 is first
249 * @return the <code>Component</code> at the position,
250 * or <code>null</code> for an invalid index
251 * @deprecated replaced by <code>getComponent(int i)</code>
252 */
253 @Deprecated
254 public Component getComponentAtIndex(int i) {
255 if(i < 0 || i >= getComponentCount()) {
256 return null;
257 }
258 return getComponent(i);
259 }
260
261 /**
262 * Returns the index of the specified component.
263 *
264 * @param c the <code>Component</code> to find
265 * @return an integer giving the component's position, where 0 is first;
266 * or -1 if it can't be found
267 */
268 public int getComponentIndex(Component c) {
269 int ncomponents = this.getComponentCount();
270 Component[] component = this.getComponents();
271 for (int i = 0 ; i < ncomponents ; i++) {
272 Component comp = component[i];
273 if (comp == c)
274 return i;
275 }
276 return -1;
277 }
278
279 /**
280 * Sets the currently selected component, producing a
281 * a change to the selection model.
282 *
283 * @param sel the <code>Component</code> to select
284 */
285 public void setSelected(Component sel) {
286 SingleSelectionModel model = getSelectionModel();
287 int index = getComponentIndex(sel);
288 model.setSelectedIndex(index);
289 }
290
291 /**
292 * Returns true if the menu bar currently has a component selected.
293 *
294 * @return true if a selection has been made, else false
295 */
296 public boolean isSelected() {
297 return selectionModel.isSelected();
298 }
299
300 /**
301 * Returns true if the menu bars border should be painted.
302 *
303 * @return true if the border should be painted, else false
304 */
305 public boolean isBorderPainted() {
306 return paintBorder;
307 }
308
309 /**
310 * Sets whether the border should be painted.
311 *
312 * @param b if true and border property is not <code>null</code>,
313 * the border is painted.
314 * @see #isBorderPainted
315 * @beaninfo
316 * bound: true
317 * attribute: visualUpdate true
318 * description: Whether the border should be painted.
319 */
320 public void setBorderPainted(boolean b) {
321 boolean oldValue = paintBorder;
322 paintBorder = b;
323 firePropertyChange("borderPainted", oldValue, paintBorder);
324 if (b != oldValue) {
325 revalidate();
326 repaint();
327 }
328 }
329
330 /**
331 * Paints the menubar's border if <code>BorderPainted</code>
332 * property is true.
333 *
334 * @param g the <code>Graphics</code> context to use for painting
335 * @see JComponent#paint
336 * @see JComponent#setBorder
337 */
338 protected void paintBorder(Graphics g) {
339 if (isBorderPainted()) {
340 super.paintBorder(g);
341 }
342 }
343
344 /**
345 * Sets the margin between the menubar's border and
346 * its menus. Setting to <code>null</code> will cause the menubar to
347 * use the default margins.
348 *
349 * @param m an Insets object containing the margin values
350 * @see Insets
351 * @beaninfo
352 * bound: true
353 * attribute: visualUpdate true
354 * description: The space between the menubar's border and its contents
355 */
356 public void setMargin(Insets m) {
357 Insets old = margin;
358 this.margin = m;
359 firePropertyChange("margin", old, m);
360 if (old == null || !old.equals(m)) {
361 revalidate();
362 repaint();
363 }
364 }
365
366 /**
367 * Returns the margin between the menubar's border and
368 * its menus. If there is no previous margin, it will create
369 * a default margin with zero size.
370 *
371 * @return an <code>Insets</code> object containing the margin values
372 * @see Insets
373 */
374 public Insets getMargin() {
375 if(margin == null) {
376 return new Insets(0,0,0,0);
377 } else {
378 return margin;
379 }
380 }
381
382
383 /**
384 * Implemented to be a <code>MenuElement</code> -- does nothing.
385 *
386 * @see #getSubElements
387 */
388 public void processMouseEvent(MouseEvent event,MenuElement path[],MenuSelectionManager manager) {
389 }
390
391 /**
392 * Implemented to be a <code>MenuElement</code> -- does nothing.
393 *
394 * @see #getSubElements
395 */
396 public void processKeyEvent(KeyEvent e,MenuElement path[],MenuSelectionManager manager) {
397 }
398
399 /**
400 * Implemented to be a <code>MenuElement</code> -- does nothing.
401 *
402 * @see #getSubElements
403 */
404 public void menuSelectionChanged(boolean isIncluded) {
405 }
406
407 /**
408 * Implemented to be a <code>MenuElement</code> -- returns the
409 * menus in this menu bar.
410 * This is the reason for implementing the <code>MenuElement</code>
411 * interface -- so that the menu bar can be treated the same as
412 * other menu elements.
413 * @return an array of menu items in the menu bar.
414 */
415 public MenuElement[] getSubElements() {
416 MenuElement result[];
417 Vector tmp = new Vector();
418 int c = getComponentCount();
419 int i;
420 Component m;
421
422 for(i=0 ; i < c ; i++) {
423 m = getComponent(i);
424 if(m instanceof MenuElement)
425 tmp.addElement(m);
426 }
427
428 result = new MenuElement[tmp.size()];
429 for(i=0,c=tmp.size() ; i < c ; i++)
430 result[i] = (MenuElement) tmp.elementAt(i);
431 return result;
432 }
433
434 /**
435 * Implemented to be a <code>MenuElement</code>. Returns this object.
436 *
437 * @return the current <code>Component</code> (this)
438 * @see #getSubElements
439 */
440 public Component getComponent() {
441 return this;
442 }
443
444
445 /**
446 * Returns a string representation of this <code>JMenuBar</code>.
447 * This method
448 * is intended to be used only for debugging purposes, and the
449 * content and format of the returned string may vary between
450 * implementations. The returned string may be empty but may not
451 * be <code>null</code>.
452 *
453 * @return a string representation of this <code>JMenuBar</code>
454 */
455 protected String paramString() {
456 String paintBorderString = (paintBorder ?
457 "true" : "false");
458 String marginString = (margin != null ?
459 margin.toString() : "");
460
461 return super.paramString() +
462 ",margin=" + marginString +
463 ",paintBorder=" + paintBorderString;
464 }
465
466 /////////////////
467 // Accessibility support
468 ////////////////
469
470 /**
471 * Gets the AccessibleContext associated with this JMenuBar.
472 * For JMenuBars, the AccessibleContext takes the form of an
473 * AccessibleJMenuBar.
474 * A new AccessibleJMenuBar instance is created if necessary.
475 *
476 * @return an AccessibleJMenuBar that serves as the
477 * AccessibleContext of this JMenuBar
478 */
479 public AccessibleContext getAccessibleContext() {
480 if (accessibleContext == null) {
481 accessibleContext = new AccessibleJMenuBar();
482 }
483 return accessibleContext;
484 }
485
486 /**
487 * This class implements accessibility support for the
488 * <code>JMenuBar</code> class. It provides an implementation of the
489 * Java Accessibility API appropriate to menu bar user-interface
490 * elements.
491 * <p>
492 * <strong>Warning:</strong>
493 * Serialized objects of this class will not be compatible with
494 * future Swing releases. The current serialization support is
495 * appropriate for short term storage or RMI between applications running
496 * the same version of Swing. As of 1.4, support for long term storage
497 * of all JavaBeans<sup><font size="-2">TM</font></sup>
498 * has been added to the <code>java.beans</code> package.
499 * Please see {@link java.beans.XMLEncoder}.
500 */
501 protected class AccessibleJMenuBar extends AccessibleJComponent
502 implements AccessibleSelection {
503
504 /**
505 * Get the accessible state set of this object.
506 *
507 * @return an instance of AccessibleState containing the current state
508 * of the object
509 */
510 public AccessibleStateSet getAccessibleStateSet() {
511 AccessibleStateSet states = super.getAccessibleStateSet();
512 return states;
513 }
514
515 /**
516 * Get the role of this object.
517 *
518 * @return an instance of AccessibleRole describing the role of the
519 * object
520 */
521 public AccessibleRole getAccessibleRole() {
522 return AccessibleRole.MENU_BAR;
523 }
524
525 /**
526 * Get the AccessibleSelection associated with this object. In the
527 * implementation of the Java Accessibility API for this class,
528 * return this object, which is responsible for implementing the
529 * AccessibleSelection interface on behalf of itself.
530 *
531 * @return this object
532 */
533 public AccessibleSelection getAccessibleSelection() {
534 return this;
535 }
536
537 /**
538 * Returns 1 if a menu is currently selected in this menu bar.
539 *
540 * @return 1 if a menu is currently selected, else 0
541 */
542 public int getAccessibleSelectionCount() {
543 if (isSelected()) {
544 return 1;
545 } else {
546 return 0;
547 }
548 }
549
550 /**
551 * Returns the currently selected menu if one is selected,
552 * otherwise null.
553 */
554 public Accessible getAccessibleSelection(int i) {
555 if (isSelected()) {
556 if (i != 0) { // single selection model for JMenuBar
557 return null;
558 }
559 int j = getSelectionModel().getSelectedIndex();
560 if (getComponentAtIndex(j) instanceof Accessible) {
561 return (Accessible) getComponentAtIndex(j);
562 }
563 }
564 return null;
565 }
566
567 /**
568 * Returns true if the current child of this object is selected.
569 *
570 * @param i the zero-based index of the child in this Accessible
571 * object.
572 * @see AccessibleContext#getAccessibleChild
573 */
574 public boolean isAccessibleChildSelected(int i) {
575 return (i == getSelectionModel().getSelectedIndex());
576 }
577
578 /**
579 * Selects the nth menu in the menu bar, forcing it to
580 * pop up. If another menu is popped up, this will force
581 * it to close. If the nth menu is already selected, this
582 * method has no effect.
583 *
584 * @param i the zero-based index of selectable items
585 * @see #getAccessibleStateSet
586 */
587 public void addAccessibleSelection(int i) {
588 // first close up any open menu
589 int j = getSelectionModel().getSelectedIndex();
590 if (i == j) {
591 return;
592 }
593 if (j >= 0 && j < getMenuCount()) {
594 JMenu menu = getMenu(j);
595 if (menu != null) {
596 MenuSelectionManager.defaultManager().setSelectedPath(null);
597 // menu.setPopupMenuVisible(false);
598 }
599 }
600 // now popup the new menu
601 getSelectionModel().setSelectedIndex(i);
602 JMenu menu = getMenu(i);
603 if (menu != null) {
604 MenuElement me[] = new MenuElement[3];
605 me[0] = JMenuBar.this;
606 me[1] = menu;
607 me[2] = menu.getPopupMenu();
608 MenuSelectionManager.defaultManager().setSelectedPath(me);
609 // menu.setPopupMenuVisible(true);
610 }
611 }
612
613 /**
614 * Removes the nth selected item in the object from the object's
615 * selection. If the nth item isn't currently selected, this
616 * method has no effect. Otherwise, it closes the popup menu.
617 *
618 * @param i the zero-based index of selectable items
619 */
620 public void removeAccessibleSelection(int i) {
621 if (i >= 0 && i < getMenuCount()) {
622 JMenu menu = getMenu(i);
623 if (menu != null) {
624 MenuSelectionManager.defaultManager().setSelectedPath(null);
625 // menu.setPopupMenuVisible(false);
626 }
627 getSelectionModel().setSelectedIndex(-1);
628 }
629 }
630
631 /**
632 * Clears the selection in the object, so that nothing in the
633 * object is selected. This will close any open menu.
634 */
635 public void clearAccessibleSelection() {
636 int i = getSelectionModel().getSelectedIndex();
637 if (i >= 0 && i < getMenuCount()) {
638 JMenu menu = getMenu(i);
639 if (menu != null) {
640 MenuSelectionManager.defaultManager().setSelectedPath(null);
641 // menu.setPopupMenuVisible(false);
642 }
643 }
644 getSelectionModel().setSelectedIndex(-1);
645 }
646
647 /**
648 * Normally causes every selected item in the object to be selected
649 * if the object supports multiple selections. This method
650 * makes no sense in a menu bar, and so does nothing.
651 */
652 public void selectAllAccessibleSelection() {
653 }
654 } // internal class AccessibleJMenuBar
655
656
657 /**
658 * Subclassed to check all the child menus.
659 * @since 1.3
660 */
661 protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
662 int condition, boolean pressed) {
663 // See if we have a local binding.
664 boolean retValue = super.processKeyBinding(ks, e, condition, pressed);
665 if (!retValue) {
666 MenuElement[] subElements = getSubElements();
667 for (int i=0; i<subElements.length; i++) {
668 if (processBindingForKeyStrokeRecursive(
669 subElements[i], ks, e, condition, pressed)) {
670 return true;
671 }
672 }
673 }
674 return retValue;
675 }
676
677 static boolean processBindingForKeyStrokeRecursive(MenuElement elem,
678 KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
679 if (elem == null) {
680 return false;
681 }
682
683 Component c = elem.getComponent();
684
685 if ( !(c.isVisible() || (c instanceof JPopupMenu)) || !c.isEnabled() ) {
686 return false;
687 }
688
689 if (c != null && c instanceof JComponent &&
690 ((JComponent)c).processKeyBinding(ks, e, condition, pressed)) {
691
692 return true;
693 }
694
695 MenuElement[] subElements = elem.getSubElements();
696 for(int i=0; i<subElements.length; i++) {
697 if (processBindingForKeyStrokeRecursive(subElements[i], ks, e,
698 condition, pressed)) {
699 return true;
700 // We don't, pass along to children JMenu's
701 }
702 }
703 return false;
704 }
705
706 /**
707 * Overrides <code>JComponent.addNotify</code> to register this
708 * menu bar with the current keyboard manager.
709 */
710 public void addNotify() {
711 super.addNotify();
712 KeyboardManager.getCurrentManager().registerMenuBar(this);
713 }
714
715 /**
716 * Overrides <code>JComponent.removeNotify</code> to unregister this
717 * menu bar with the current keyboard manager.
718 */
719 public void removeNotify() {
720 super.removeNotify();
721 KeyboardManager.getCurrentManager().unregisterMenuBar(this);
722 }
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 /**
748 * See JComponent.readObject() for information about serialization
749 * in Swing.
750 */
751 private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException
752 {
753 s.defaultReadObject();
754 Object[] kvData = (Object[])(s.readObject());
755
756 for(int i = 0; i < kvData.length; i += 2) {
757 if (kvData[i] == null) {
758 break;
759 }
760 else if (kvData[i].equals("selectionModel")) {
761 selectionModel = (SingleSelectionModel)kvData[i + 1];
762 }
763 }
764
765 }
766 }