001 /* JMenuBar.java -- 002 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 039 package javax.swing; 040 041 import java.awt.Component; 042 import java.awt.Graphics; 043 import java.awt.Insets; 044 import java.awt.event.KeyEvent; 045 import java.awt.event.MouseEvent; 046 047 import javax.accessibility.Accessible; 048 import javax.accessibility.AccessibleContext; 049 import javax.accessibility.AccessibleRole; 050 import javax.accessibility.AccessibleSelection; 051 import javax.accessibility.AccessibleStateSet; 052 import javax.swing.plaf.MenuBarUI; 053 054 import javax.swing.border.Border; 055 056 /** 057 * JMenuBar is a container for menu's. For a menu bar to be seen on the 058 * screen, at least one menu should be added to it. Just like adding 059 * components to container, one can use add() to add menu's to the menu bar. 060 * Menu's will be displayed in the menu bar in the order they were added. 061 * The JMenuBar uses selectionModel to keep track of selected menu index. 062 * JMenuBar's selectionModel will fire ChangeEvents to its registered 063 * listeners when the selected index changes. 064 */ 065 public class JMenuBar extends JComponent implements Accessible, MenuElement 066 { 067 /** 068 * Provides accessibility support for <code>JMenuBar</code>. 069 * 070 * @author Roman Kennke (kennke@aicas.com) 071 */ 072 protected class AccessibleJMenuBar extends AccessibleJComponent 073 implements AccessibleSelection 074 { 075 076 /** 077 * Returns the number of selected items in the menu bar. Possible values 078 * are <code>0</code> if nothing is selected, or <code>1</code> if one 079 * item is selected. 080 * 081 * @return the number of selected items in the menu bar 082 */ 083 public int getAccessibleSelectionCount() 084 { 085 int count = 0; 086 if (getSelectionModel().getSelectedIndex() != -1) 087 count = 1; 088 return count; 089 } 090 091 /** 092 * Returns the selected with index <code>i</code> menu, or 093 * <code>null</code> if the specified menu is not selected. 094 * 095 * @param i the index of the menu to return 096 * 097 * @return the selected with index <code>i</code> menu, or 098 * <code>null</code> if the specified menu is not selected 099 */ 100 public Accessible getAccessibleSelection(int i) 101 { 102 if (getSelectionModel().getSelectedIndex() != i) 103 return null; 104 return getMenu(i); 105 } 106 107 /** 108 * Returns <code>true</code> if the specified menu is selected, 109 * <code>false</code> otherwise. 110 * 111 * @param i the index of the menu to check 112 * 113 *@return <code>true</code> if the specified menu is selected, 114 * <code>false</code> otherwise 115 */ 116 public boolean isAccessibleChildSelected(int i) 117 { 118 return getSelectionModel().getSelectedIndex() == i; 119 } 120 121 /** 122 * Selects the menu with index <code>i</code>. If another menu is already 123 * selected, this will be deselected. 124 * 125 * @param i the menu to be selected 126 */ 127 public void addAccessibleSelection(int i) 128 { 129 getSelectionModel().setSelectedIndex(i); 130 } 131 132 /** 133 * Deselects the menu with index <code>i</code>. 134 * 135 * @param i the menu index to be deselected 136 */ 137 public void removeAccessibleSelection(int i) 138 { 139 if (getSelectionModel().getSelectedIndex() == i) 140 getSelectionModel().clearSelection(); 141 } 142 143 /** 144 * Deselects all possibly selected menus. 145 */ 146 public void clearAccessibleSelection() 147 { 148 getSelectionModel().clearSelection(); 149 } 150 151 /** 152 * In menu bars it is not possible to select all items, so this method 153 * does nothing. 154 */ 155 public void selectAllAccessibleSelection() 156 { 157 // In menu bars it is not possible to select all items, so this method 158 // does nothing. 159 } 160 161 /** 162 * Returns the accessible role of <code>JMenuBar</code>, which is 163 * {@link AccessibleRole#MENU_BAR}. 164 * 165 * @return the accessible role of <code>JMenuBar</code>, which is 166 * {@link AccessibleRole#MENU_BAR} 167 */ 168 public AccessibleRole getAccessibleRole() 169 { 170 return AccessibleRole.MENU_BAR; 171 } 172 173 /** 174 * Returns the <code>AccessibleSelection</code> for this object. This 175 * method returns <code>this</code>, since the 176 * <code>AccessibleJMenuBar</code> manages its selection itself. 177 * 178 * @return the <code>AccessibleSelection</code> for this object 179 */ 180 public AccessibleSelection getAccessibleSelection() 181 { 182 return this; 183 } 184 185 /** 186 * Returns the state of this <code>AccessibleJMenuBar</code>. 187 * 188 * @return the state of this <code>AccessibleJMenuBar</code>. 189 */ 190 public AccessibleStateSet getAccessibleStateSet() 191 { 192 AccessibleStateSet stateSet = super.getAccessibleStateSet(); 193 // TODO: Figure out what state must be added to the super state set. 194 return stateSet; 195 } 196 } 197 198 private static final long serialVersionUID = -8191026883931977036L; 199 200 /** JMenuBar's model. It keeps track of selected menu's index */ 201 private transient SingleSelectionModel selectionModel; 202 203 /* borderPainted property indicating if the menuBar's border will be painted*/ 204 private boolean borderPainted; 205 206 /* margin between menu bar's border and its menues*/ 207 private Insets margin; 208 209 /** 210 * Creates a new JMenuBar object. 211 */ 212 public JMenuBar() 213 { 214 selectionModel = new DefaultSingleSelectionModel(); 215 borderPainted = true; 216 updateUI(); 217 } 218 219 /** 220 * Adds menu to the menu bar 221 * 222 * @param c menu to add 223 * 224 * @return reference to the added menu 225 */ 226 public JMenu add(JMenu c) 227 { 228 c.setAlignmentX(Component.LEFT_ALIGNMENT); 229 super.add(c); 230 return c; 231 } 232 233 /** 234 * This method overrides addNotify() in the Container to register 235 * this menu bar with the current keyboard manager. 236 */ 237 public void addNotify() 238 { 239 super.addNotify(); 240 KeyboardManager.getManager().registerJMenuBar(this); 241 } 242 243 public AccessibleContext getAccessibleContext() 244 { 245 if (accessibleContext == null) 246 accessibleContext = new AccessibleJMenuBar(); 247 return accessibleContext; 248 } 249 250 /** 251 * Returns reference to this menu bar 252 * 253 * @return reference to this menu bar 254 */ 255 public Component getComponent() 256 { 257 return this; 258 } 259 260 /** 261 * Returns component at the specified index. 262 * 263 * @param i index of the component to get 264 * 265 * @return component at the specified index. Null is returned if 266 * component at the specified index doesn't exist. 267 * @deprecated Replaced by getComponent(int) 268 */ 269 public Component getComponentAtIndex(int i) 270 { 271 return getComponent(i); 272 } 273 274 /** 275 * Returns index of the specified component 276 * 277 * @param c Component to search for 278 * 279 * @return index of the specified component. -1 is returned if 280 * specified component doesnt' exist in the menu bar. 281 */ 282 public int getComponentIndex(Component c) 283 { 284 Component[] comps = getComponents(); 285 286 int index = -1; 287 288 for (int i = 0; i < comps.length; i++) 289 { 290 if (comps[i].equals(c)) 291 { 292 index = i; 293 break; 294 } 295 } 296 297 return index; 298 } 299 300 /** 301 * This method is not implemented and will throw an {@link Error} if called. 302 * 303 * @return This method never returns anything, it throws an exception. 304 */ 305 public JMenu getHelpMenu() 306 { 307 // the following error matches the behaviour of the reference 308 // implementation... 309 throw new Error("getHelpMenu() is not implemented"); 310 } 311 312 /** 313 * Returns the margin between the menu bar's border and its menus. If the 314 * margin is <code>null</code>, this method returns 315 * <code>new Insets(0, 0, 0, 0)</code>. 316 * 317 * @return The margin (never <code>null</code>). 318 * 319 * @see #setMargin(Insets) 320 */ 321 public Insets getMargin() 322 { 323 if (margin == null) 324 return new Insets(0, 0, 0, 0); 325 else 326 return margin; 327 } 328 329 /** 330 * Return menu at the specified index. If component at the 331 * specified index is not a menu, then null is returned. 332 * 333 * @param index index to look for the menu 334 * 335 * @return menu at specified index, or null if menu doesn't exist 336 * at the specified index. 337 */ 338 public JMenu getMenu(int index) 339 { 340 if (getComponentAtIndex(index) instanceof JMenu) 341 return (JMenu) getComponentAtIndex(index); 342 else 343 return null; 344 } 345 346 /** 347 * Returns number of menu's in this menu bar 348 * 349 * @return number of menu's in this menu bar 350 */ 351 public int getMenuCount() 352 { 353 return getComponentCount(); 354 } 355 356 /** 357 * Returns selection model for this menu bar. SelectionModel 358 * keeps track of the selected menu in the menu bar. Whenever 359 * selected property of selectionModel changes, the ChangeEvent 360 * will be fired its ChangeListeners. 361 * 362 * @return selection model for this menu bar. 363 */ 364 public SingleSelectionModel getSelectionModel() 365 { 366 return selectionModel; 367 } 368 369 /** 370 * Method of MenuElement interface. It returns subcomponents 371 * of the menu bar, which are all the menues that it contains. 372 * 373 * @return MenuElement[] array containing menues in this menu bar 374 */ 375 public MenuElement[] getSubElements() 376 { 377 MenuElement[] subElements = new MenuElement[getComponentCount()]; 378 379 int j = 0; 380 boolean doResize = false; 381 MenuElement menu; 382 for (int i = 0; i < getComponentCount(); i++) 383 { 384 menu = getMenu(i); 385 if (menu != null) 386 { 387 subElements[j++] = (MenuElement) menu; 388 } 389 else 390 doResize = true; 391 } 392 393 if (! doResize) 394 return subElements; 395 else 396 { 397 MenuElement[] subElements2 = new MenuElement[j]; 398 for (int i = 0; i < j; i++) 399 subElements2[i] = subElements[i]; 400 401 return subElements2; 402 } 403 } 404 405 /** 406 * Set the "UI" property of the menu bar, which is a look and feel class 407 * responsible for handling the menuBar's input events and painting it. 408 * 409 * @return The current "UI" property 410 */ 411 public MenuBarUI getUI() 412 { 413 return (MenuBarUI) ui; 414 } 415 416 /** 417 * This method returns a name to identify which look and feel class will be 418 * the UI delegate for the menu bar. 419 * 420 * @return The Look and Feel classID. "MenuBarUI" 421 */ 422 public String getUIClassID() 423 { 424 return "MenuBarUI"; 425 } 426 427 /** 428 * Returns true if menu bar paints its border and false otherwise 429 * 430 * @return true if menu bar paints its border and false otherwise 431 */ 432 public boolean isBorderPainted() 433 { 434 return borderPainted; 435 } 436 437 /** 438 * Returns true if some menu in menu bar is selected. 439 * 440 * @return true if some menu in menu bar is selected and false otherwise 441 */ 442 public boolean isSelected() 443 { 444 return selectionModel.isSelected(); 445 } 446 447 /** 448 * This method does nothing by default. This method is need for the 449 * MenuElement interface to be implemented. 450 * 451 * @param isIncluded true if menuBar is included in the selection 452 * and false otherwise 453 */ 454 public void menuSelectionChanged(boolean isIncluded) 455 { 456 // Do nothing - needed for implementation of MenuElement interface 457 } 458 459 /** 460 * Paints border of the menu bar, if its borderPainted property is set to 461 * true. 462 * 463 * @param g The graphics context with which to paint the border 464 */ 465 protected void paintBorder(Graphics g) 466 { 467 if (borderPainted) 468 { 469 Border border = getBorder(); 470 if (border != null) 471 getBorder().paintBorder(this, g, 0, 0, getSize(null).width, 472 getSize(null).height); 473 } 474 } 475 476 /** 477 * A string that describes this JMenuBar. Normally only used 478 * for debugging. 479 * 480 * @return A string describing this JMenuBar 481 */ 482 protected String paramString() 483 { 484 StringBuffer sb = new StringBuffer(); 485 sb.append(super.paramString()); 486 sb.append(",margin="); 487 if (getMargin() != null) 488 sb.append(getMargin()); 489 sb.append(",paintBorder=").append(isBorderPainted()); 490 return sb.toString(); 491 } 492 493 /** 494 * Process key events forwarded from MenuSelectionManager. This method 495 * doesn't do anything. It is here to conform to the MenuElement interface. 496 * 497 * @param e event forwarded from MenuSelectionManager 498 * @param path path to the menu element from which event was generated 499 * @param manager MenuSelectionManager for the current menu hierarchy 500 * 501 */ 502 public void processKeyEvent(KeyEvent e, MenuElement[] path, 503 MenuSelectionManager manager) 504 { 505 // Do nothing - needed for implementation of MenuElement interface 506 } 507 508 /** 509 * This method overrides JComponent.processKeyBinding to allow the 510 * JMenuBar to check all the child components (recursiveley) to see 511 * if they'll consume the event. 512 * 513 * @param ks the KeyStroke for the event 514 * @param e the KeyEvent for the event 515 * @param condition the focus condition for the binding 516 * @param pressed true if the key is pressed 517 */ 518 protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, 519 boolean pressed) 520 { 521 // See if the regular JComponent behavior consumes the event 522 if (super.processKeyBinding(ks, e, condition, pressed)) 523 return true; 524 525 // If not, have to recursively check all the child menu elements to see 526 // if they want it 527 MenuElement[] children = getSubElements(); 528 for (int i = 0; i < children.length; i++) 529 if (processKeyBindingHelper(children[i], ks, e, condition, pressed)) 530 return true; 531 return false; 532 } 533 534 /** 535 * This is a helper method to recursively check the children of this 536 * JMenuBar to see if they will consume a key event via key bindings. 537 * This is used for menu accelerators. 538 * @param menuElement the menuElement to check (and check all its children) 539 * @param ks the KeyStroke for the event 540 * @param e the KeyEvent that may be consumed 541 * @param condition the focus condition for the binding 542 * @param pressed true if the key was pressed 543 * @return true <code>menuElement</code> or one of its children consume 544 * the event (processKeyBinding returns true for menuElement or one of 545 * its children). 546 */ 547 static boolean processKeyBindingHelper(MenuElement menuElement, KeyStroke ks, 548 KeyEvent e, int condition, 549 boolean pressed) 550 { 551 if (menuElement == null) 552 return false; 553 554 // First check the menuElement itself, if it's a JComponent 555 if (menuElement instanceof JComponent 556 && ((JComponent) menuElement).processKeyBinding(ks, e, condition, 557 pressed)) 558 return true; 559 560 // If that didn't consume it, check all the children recursively 561 MenuElement[] children = menuElement.getSubElements(); 562 for (int i = 0; i < children.length; i++) 563 if (processKeyBindingHelper(children[i], ks, e, condition, pressed)) 564 return true; 565 return false; 566 } 567 568 /** 569 * Process mouse events forwarded from MenuSelectionManager. This method 570 * doesn't do anything. It is here to conform to the MenuElement interface. 571 * 572 * @param event event forwarded from MenuSelectionManager 573 * @param path path to the menu element from which event was generated 574 * @param manager MenuSelectionManager for the current menu hierarchy 575 * 576 */ 577 public void processMouseEvent(MouseEvent event, MenuElement[] path, 578 MenuSelectionManager manager) 579 { 580 // Do nothing - needed for implementation of MenuElement interface 581 } 582 583 /** 584 * This method overrides removeNotify() in the Container to 585 * unregister this menu bar from the current keyboard manager. 586 */ 587 public void removeNotify() 588 { 589 KeyboardManager.getManager().unregisterJMenuBar(this); 590 super.removeNotify(); 591 } 592 593 /** 594 * Sets painting status of the border. If 'b' is true then menu bar's 595 * border will be painted, and it will not be painted otherwise. 596 * 597 * @param b indicates if menu bar's border should be painted. 598 */ 599 public void setBorderPainted(boolean b) 600 { 601 if (b != borderPainted) 602 { 603 boolean old = borderPainted; 604 borderPainted = b; 605 firePropertyChange("borderPainted", old, b); 606 revalidate(); 607 repaint(); 608 } 609 } 610 611 /** 612 * Sets help menu for this menu bar 613 * 614 * @param menu help menu 615 * 616 * @specnote The specification states that this method is not yet implemented 617 * and should throw an exception. 618 */ 619 public void setHelpMenu(JMenu menu) 620 { 621 // We throw an Error here, just as Sun's JDK does. 622 throw new Error("setHelpMenu() not yet implemented."); 623 } 624 625 /** 626 * Sets the margin between the menu bar's border and its menus (this is a 627 * bound property with the name 'margin'). 628 * 629 * @param m the margin (<code>null</code> permitted). 630 * 631 * @see #getMargin() 632 */ 633 public void setMargin(Insets m) 634 { 635 if (m != margin) 636 { 637 Insets oldMargin = margin; 638 margin = m; 639 firePropertyChange("margin", oldMargin, margin); 640 } 641 } 642 643 /** 644 * Changes menu bar's selection to the specified menu. 645 * This method updates selected index of menu bar's selection model, 646 * which results in a model firing change event. 647 * 648 * @param sel menu to select 649 */ 650 public void setSelected(Component sel) 651 { 652 int index = getComponentIndex(sel); 653 selectionModel.setSelectedIndex(index); 654 } 655 656 /** 657 * Sets menuBar's selection model to the one specified 658 * 659 * @param model SingleSelectionModel that needs to be set for this menu bar 660 */ 661 public void setSelectionModel(SingleSelectionModel model) 662 { 663 if (selectionModel != model) 664 { 665 SingleSelectionModel oldModel = selectionModel; 666 selectionModel = model; 667 firePropertyChange("model", oldModel, selectionModel); 668 } 669 } 670 671 /** 672 * Set the "UI" property of the menu bar, which is a look and feel class 673 * responsible for handling menuBar's input events and painting it. 674 * 675 * @param ui The new "UI" property 676 */ 677 public void setUI(MenuBarUI ui) 678 { 679 super.setUI(ui); 680 } 681 682 /** 683 * Set the "UI" property to a class constructed, via the {@link 684 * UIManager}, from the current look and feel. 685 */ 686 public void updateUI() 687 { 688 setUI((MenuBarUI) UIManager.getUI(this)); 689 } 690 }