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