001/* JMenu.java -- 002 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing; 040 041import java.awt.Component; 042import java.awt.Dimension; 043import java.awt.GraphicsConfiguration; 044import java.awt.GraphicsDevice; 045import java.awt.GraphicsEnvironment; 046import java.awt.Insets; 047import java.awt.Point; 048import java.awt.Rectangle; 049import java.awt.Toolkit; 050import java.awt.event.KeyEvent; 051import java.awt.event.WindowAdapter; 052import java.awt.event.WindowEvent; 053import java.beans.PropertyChangeEvent; 054import java.beans.PropertyChangeListener; 055import java.io.Serializable; 056import java.util.ArrayList; 057import java.util.EventListener; 058 059import javax.accessibility.Accessible; 060import javax.accessibility.AccessibleContext; 061import javax.accessibility.AccessibleRole; 062import javax.accessibility.AccessibleSelection; 063import javax.swing.event.ChangeEvent; 064import javax.swing.event.ChangeListener; 065import javax.swing.event.MenuEvent; 066import javax.swing.event.MenuListener; 067import javax.swing.plaf.MenuItemUI; 068 069/** 070 * This class represents a menu that can be added to a menu bar or 071 * can be a submenu in some other menu. When JMenu is selected it 072 * displays JPopupMenu containing its menu items. 073 * 074 * <p> 075 * JMenu's fires MenuEvents when this menu's selection changes. If this menu 076 * is selected, then fireMenuSelectedEvent() is invoked. In case when menu is 077 * deselected or cancelled, then fireMenuDeselectedEvent() or 078 * fireMenuCancelledEvent() is invoked, respectivelly. 079 * </p> 080 */ 081public class JMenu extends JMenuItem implements Accessible, MenuElement 082{ 083 /** 084 * Receives notifications when the JMenu's ButtonModel is changed and 085 * fires menuSelected or menuDeselected events when appropriate. 086 */ 087 private class MenuChangeListener 088 implements ChangeListener 089 { 090 /** 091 * Indicates the last selected state. 092 */ 093 private boolean selected; 094 095 /** 096 * Receives notification when the JMenu's ButtonModel changes. 097 */ 098 public void stateChanged(ChangeEvent ev) 099 { 100 ButtonModel m = (ButtonModel) ev.getSource(); 101 boolean s = m.isSelected(); 102 if (s != selected) 103 { 104 if (s) 105 fireMenuSelected(); 106 else 107 fireMenuDeselected(); 108 selected = s; 109 } 110 } 111 } 112 113 private static final long serialVersionUID = 4227225638931828014L; 114 115 /** A Popup menu associated with this menu, which pops up when menu is selected */ 116 private JPopupMenu popupMenu = null; 117 118 /** Whenever menu is selected or deselected the MenuEvent is fired to 119 menu's registered listeners. */ 120 private MenuEvent menuEvent = new MenuEvent(this); 121 122 /*Amount of time, in milliseconds, that should pass before popupMenu 123 associated with this menu appears or disappers */ 124 private int delay; 125 126 /* PopupListener */ 127 protected WinListener popupListener; 128 129 /** 130 * Location at which popup menu associated with this menu will be 131 * displayed 132 */ 133 private Point menuLocation; 134 135 /** 136 * The ChangeListener for the ButtonModel. 137 * 138 * @see MenuChangeListener 139 */ 140 private ChangeListener menuChangeListener; 141 142 /** 143 * Creates a new JMenu object. 144 */ 145 public JMenu() 146 { 147 super(); 148 setOpaque(false); 149 } 150 151 /** 152 * Creates a new <code>JMenu</code> with the specified label. 153 * 154 * @param text label for this menu 155 */ 156 public JMenu(String text) 157 { 158 super(text); 159 popupMenu = new JPopupMenu(); 160 popupMenu.setInvoker(this); 161 setOpaque(false); 162 } 163 164 /** 165 * Creates a new <code>JMenu</code> object. 166 * 167 * @param action Action that is used to create menu item tha will be 168 * added to the menu. 169 */ 170 public JMenu(Action action) 171 { 172 super(action); 173 createActionChangeListener(this); 174 popupMenu = new JPopupMenu(); 175 popupMenu.setInvoker(this); 176 setOpaque(false); 177 } 178 179 /** 180 * Creates a new <code>JMenu</code> with specified label and an option 181 * for this menu to be tear-off menu. 182 * 183 * @param text label for this menu 184 * @param tearoff true if this menu should be tear-off and false otherwise 185 */ 186 public JMenu(String text, boolean tearoff) 187 { 188 // FIXME: tearoff not implemented 189 this(text); 190 } 191 192 /** 193 * Adds specified menu item to this menu 194 * 195 * @param item Menu item to add to this menu 196 * 197 * @return Menu item that was added 198 */ 199 public JMenuItem add(JMenuItem item) 200 { 201 return getPopupMenu().add(item); 202 } 203 204 /** 205 * Adds specified component to this menu. 206 * 207 * @param component Component to add to this menu 208 * 209 * @return Component that was added 210 */ 211 public Component add(Component component) 212 { 213 getPopupMenu().insert(component, -1); 214 return component; 215 } 216 217 /** 218 * Adds specified component to this menu at the given index 219 * 220 * @param component Component to add 221 * @param index Position of this menu item in the menu 222 * 223 * @return Component that was added 224 */ 225 public Component add(Component component, int index) 226 { 227 return getPopupMenu().add(component, index); 228 } 229 230 /** 231 * Adds JMenuItem constructed with the specified label to this menu 232 * 233 * @param text label for the menu item that will be added 234 * 235 * @return Menu Item that was added to this menu 236 */ 237 public JMenuItem add(String text) 238 { 239 return add(new JMenuItem(text)); 240 } 241 242 /** 243 * Adds JMenuItem constructed using properties from specified action. 244 * 245 * @param action action to construct the menu item with 246 * 247 * @return Menu Item that was added to this menu 248 */ 249 public JMenuItem add(Action action) 250 { 251 JMenuItem i = createActionComponent(action); 252 i.setAction(action); 253 add(i); 254 return i; 255 } 256 257 /** 258 * Removes given menu item from this menu. Nothing happens if 259 * this menu doesn't contain specified menu item. 260 * 261 * @param item Menu Item which needs to be removed 262 */ 263 public void remove(JMenuItem item) 264 { 265 getPopupMenu().remove(item); 266 } 267 268 /** 269 * Removes component at the specified index from this menu 270 * 271 * @param index Position of the component that needs to be removed in the menu 272 */ 273 public void remove(int index) 274 { 275 if (index < 0 || (index > 0 && getMenuComponentCount() == 0)) 276 throw new IllegalArgumentException(); 277 278 if (getMenuComponentCount() > 0) 279 popupMenu.remove(index); 280 } 281 282 /** 283 * Removes given component from this menu. 284 * 285 * @param component Component to remove 286 */ 287 public void remove(Component component) 288 { 289 int index = getPopupMenu().getComponentIndex(component); 290 if (index >= 0) 291 getPopupMenu().remove(index); 292 } 293 294 /** 295 * Removes all menu items from the menu 296 */ 297 public void removeAll() 298 { 299 if (popupMenu != null) 300 popupMenu.removeAll(); 301 } 302 303 /** 304 * Creates JMenuItem with the specified text and inserts it in the 305 * at the specified index 306 * 307 * @param text label for the new menu item 308 * @param index index at which to insert newly created menu item. 309 */ 310 public void insert(String text, int index) 311 { 312 this.insert(new JMenuItem(text), index); 313 } 314 315 /** 316 * Creates JMenuItem with the specified text and inserts it in the 317 * at the specified index. IllegalArgumentException is thrown 318 * if index is less than 0 319 * 320 * @param item menu item to insert 321 * @param index index at which to insert menu item. 322 * @return Menu item that was added to the menu 323 */ 324 public JMenuItem insert(JMenuItem item, int index) 325 { 326 if (index < 0) 327 throw new IllegalArgumentException("index less than zero"); 328 329 getPopupMenu().insert(item, index); 330 return item; 331 } 332 333 /** 334 * Creates JMenuItem with the associated action and inserts it to the menu 335 * at the specified index. IllegalArgumentException is thrown 336 * if index is less than 0 337 * 338 * @param action Action for the new menu item 339 * @param index index at which to insert newly created menu item. 340 * @return Menu item that was added to the menu 341 */ 342 public JMenuItem insert(Action action, int index) 343 { 344 JMenuItem item = new JMenuItem(action); 345 this.insert(item, index); 346 347 return item; 348 } 349 350 /** 351 * This method sets this menuItem's UI to the UIManager's default for the 352 * current look and feel. 353 */ 354 public void updateUI() 355 { 356 setUI((MenuItemUI) UIManager.getUI(this)); 357 } 358 359 /** 360 * This method returns a name to identify which look and feel class will be 361 * the UI delegate for the menu. 362 * 363 * @return The Look and Feel classID. "MenuUI" 364 */ 365 public String getUIClassID() 366 { 367 return "MenuUI"; 368 } 369 370 /** 371 * Sets model for this menu. 372 * 373 * @param model model to set 374 */ 375 public void setModel(ButtonModel model) 376 { 377 ButtonModel oldModel = getModel(); 378 if (oldModel != null && menuChangeListener != null) 379 oldModel.removeChangeListener(menuChangeListener); 380 381 super.setModel(model); 382 383 if (model != null) 384 { 385 if (menuChangeListener == null) 386 menuChangeListener = new MenuChangeListener(); 387 model.addChangeListener(menuChangeListener); 388 } 389 } 390 391 /** 392 * Returns true if the menu is selected and false otherwise 393 * 394 * @return true if the menu is selected and false otherwise 395 */ 396 public boolean isSelected() 397 { 398 return super.isSelected(); 399 } 400 401 /** 402 * Changes this menu selected state if selected is true and false otherwise 403 * This method fires menuEvents to menu's registered listeners. 404 * 405 * @param selected true if the menu should be selected and false otherwise 406 */ 407 public void setSelected(boolean selected) 408 { 409 ButtonModel m = getModel(); 410 if (selected != m.isSelected()) 411 m.setSelected(selected); 412 } 413 414 /** 415 * Checks if PopupMenu associated with this menu is visible 416 * 417 * @return true if the popup associated with this menu is currently visible 418 * on the screen and false otherwise. 419 */ 420 public boolean isPopupMenuVisible() 421 { 422 return getPopupMenu().isVisible(); 423 } 424 425 /** 426 * Sets popup menu visibility 427 * 428 * @param popup true if popup should be visible and false otherwise 429 */ 430 public void setPopupMenuVisible(boolean popup) 431 { 432 if (popup != isPopupMenuVisible() && (isEnabled() || ! popup)) 433 { 434 if (popup && isShowing()) 435 { 436 // Set location as determined by getPopupLocation(). 437 Point loc = menuLocation == null ? getPopupMenuOrigin() 438 : menuLocation; 439 getPopupMenu().show(this, loc.x, loc.y); 440 } 441 else 442 getPopupMenu().setVisible(false); 443 } 444 } 445 446 /** 447 * Returns origin point of the popup menu. This takes the screen bounds 448 * into account and places the popup where it fits best. 449 * 450 * @return the origin of the popup menu 451 */ 452 protected Point getPopupMenuOrigin() 453 { 454 // The menu's screen location and size. 455 Point screenLoc = getLocationOnScreen(); 456 Dimension size = getSize(); 457 458 // Determine the popup's size. 459 JPopupMenu popup = getPopupMenu(); 460 Dimension popupSize = popup.getSize(); 461 if (popupSize.width == 0 || popupSize.height == 0) 462 popupSize = popup.getPreferredSize(); 463 464 // Determine screen bounds. 465 Toolkit tk = Toolkit.getDefaultToolkit(); 466 Rectangle screenBounds = new Rectangle(tk.getScreenSize()); 467 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 468 GraphicsDevice gd = ge.getDefaultScreenDevice(); 469 GraphicsConfiguration gc = gd.getDefaultConfiguration(); 470 Insets screenInsets = tk.getScreenInsets(gc); 471 screenBounds.x -= screenInsets.left; 472 screenBounds.width -= screenInsets.left + screenInsets.right; 473 screenBounds.y -= screenInsets.top; 474 screenBounds.height -= screenInsets.top + screenInsets.bottom; 475 screenLoc.x -= screenInsets.left; 476 screenLoc.y -= screenInsets.top; 477 478 Point point = new Point(); 479 if (isTopLevelMenu()) 480 { 481 // If menu in the menu bar. 482 int xOffset = UIManager.getInt("Menu.menuPopupOffsetX"); 483 int yOffset = UIManager.getInt("Menu.menuPopupOffsetY"); 484 // Determine X location. 485 if (getComponentOrientation().isLeftToRight()) 486 { 487 // Prefer popup to the right. 488 point.x = xOffset; 489 // Check if it fits, otherwise place popup wherever it fits. 490 if (screenLoc.x + point.x + popupSize.width 491 > screenBounds.width + screenBounds.width 492 && screenBounds.width - size.width 493 < 2 * (screenLoc.x - screenBounds.x)) 494 // Popup to the right if there's not enough room. 495 point.x = size.width - xOffset - popupSize.width; 496 } 497 else 498 { 499 // Prefer popup to the left. 500 point.x = size.width - xOffset - popupSize.width; 501 if (screenLoc.x + point.x < screenBounds.x 502 && screenBounds.width - size.width 503 > 2 * (screenLoc.x - screenBounds.x)) 504 // Popup to the left if there's not enough room. 505 point.x = xOffset; 506 } 507 // Determine Y location. Prefer popping down. 508 point.y = size.height + yOffset; 509 if (screenLoc.y + point.y + popupSize.height >= screenBounds.height 510 && screenBounds.height - size.height 511 < 2 * (screenLoc.y - screenBounds.y)) 512 // Position above if there's not enough room below. 513 point.y = - yOffset - popupSize.height; 514 } 515 else 516 { 517 // If submenu. 518 int xOffset = UIManager.getInt("Menu.submenuPopupOffsetX"); 519 int yOffset = UIManager.getInt("Menu.submenuPopupOffsetY"); 520 // Determine X location. 521 if (getComponentOrientation().isLeftToRight()) 522 { 523 // Prefer popup to the right. 524 point.x = size.width + xOffset; 525 if (screenLoc.x + point.x + popupSize.width 526 >= screenBounds.x + screenBounds.width 527 && screenBounds.width - size.width 528 < 2 * (screenLoc.x - screenBounds.x)) 529 // Position to the left if there's not enough room on the right. 530 point.x = - xOffset - popupSize.width; 531 } 532 else 533 { 534 // Prefer popup on the left side. 535 point.x = - xOffset - popupSize.width; 536 if (screenLoc.x + point.x < screenBounds.x 537 && screenBounds.width - size.width 538 > 2 * (screenLoc.x - screenBounds.x)) 539 // Popup to the right if there's not enough room. 540 point.x = size.width + xOffset; 541 } 542 // Determine Y location. Prefer popping down. 543 point.y = yOffset; 544 if (screenLoc.y + point.y + popupSize.height 545 >= screenBounds.y + screenBounds.height 546 && screenBounds.height - size.height 547 < 2 * (screenLoc.y - screenBounds.y)) 548 // Pop up if there's not enough room below. 549 point.y = size.height - yOffset - popupSize.height; 550 } 551 return point; 552 } 553 554 /** 555 * Returns delay property. 556 * 557 * @return delay property, indicating number of milliseconds before 558 * popup menu associated with the menu appears or disappears after 559 * menu was selected or deselected respectively 560 */ 561 public int getDelay() 562 { 563 return delay; 564 } 565 566 /** 567 * Sets delay property for this menu. If given time for the delay 568 * property is negative, then IllegalArgumentException is thrown 569 * 570 * @param delay number of milliseconds before 571 * popup menu associated with the menu appears or disappears after 572 * menu was selected or deselected respectively 573 */ 574 public void setDelay(int delay) 575 { 576 if (delay < 0) 577 throw new IllegalArgumentException("delay less than 0"); 578 this.delay = delay; 579 } 580 581 /** 582 * Sets location at which popup menu should be displayed 583 * The location given is relative to this menu item 584 * 585 * @param x x-coordinate of the menu location 586 * @param y y-coordinate of the menu location 587 */ 588 public void setMenuLocation(int x, int y) 589 { 590 menuLocation = new Point(x, y); 591 if (popupMenu != null) 592 popupMenu.setLocation(x, y); 593 } 594 595 /** 596 * Creates and returns JMenuItem associated with the given action 597 * 598 * @param action Action to use for creation of JMenuItem 599 * 600 * @return JMenuItem that was creted with given action 601 */ 602 protected JMenuItem createActionComponent(Action action) 603 { 604 return new JMenuItem(action); 605 } 606 607 /** 608 * Creates ActionChangeListener to listen for PropertyChangeEvents occuring 609 * in the action that is associated with this menu 610 * 611 * @param item menu that contains action to listen to 612 * 613 * @return The PropertyChangeListener 614 */ 615 protected PropertyChangeListener createActionChangeListener(JMenuItem item) 616 { 617 return new ActionChangedListener(item); 618 } 619 620 /** 621 * Adds separator to the end of the menu items in the menu. 622 */ 623 public void addSeparator() 624 { 625 getPopupMenu().addSeparator(); 626 } 627 628 /** 629 * Inserts separator in the menu at the specified index. 630 * 631 * @param index Index at which separator should be inserted 632 */ 633 public void insertSeparator(int index) 634 { 635 if (index < 0) 636 throw new IllegalArgumentException("index less than 0"); 637 638 getPopupMenu().insert(new JPopupMenu.Separator(), index); 639 } 640 641 /** 642 * Returns menu item located at the specified index in the menu 643 * 644 * @param index Index at which to look for the menu item 645 * 646 * @return menu item located at the specified index in the menu 647 */ 648 public JMenuItem getItem(int index) 649 { 650 if (index < 0) 651 throw new IllegalArgumentException("index less than 0"); 652 653 if (getItemCount() == 0) 654 return null; 655 656 Component c = popupMenu.getComponentAtIndex(index); 657 658 if (c instanceof JMenuItem) 659 return (JMenuItem) c; 660 else 661 return null; 662 } 663 664 /** 665 * Returns number of items in the menu including separators. 666 * 667 * @return number of items in the menu 668 * 669 * @see #getMenuComponentCount() 670 */ 671 public int getItemCount() 672 { 673 return getMenuComponentCount(); 674 } 675 676 /** 677 * Checks if this menu is a tear-off menu. 678 * 679 * @return true if this menu is a tear-off menu and false otherwise 680 */ 681 public boolean isTearOff() 682 { 683 // NOT YET IMPLEMENTED 684 throw new Error("The method isTearOff() has not yet been implemented."); 685 } 686 687 /** 688 * Returns number of menu components in this menu 689 * 690 * @return number of menu components in this menu 691 */ 692 public int getMenuComponentCount() 693 { 694 return getPopupMenu().getComponentCount(); 695 } 696 697 /** 698 * Returns menu component located at the givent index 699 * in the menu 700 * 701 * @param index index at which to get the menu component in the menu 702 * 703 * @return Menu Component located in the menu at the specified index 704 */ 705 public Component getMenuComponent(int index) 706 { 707 if (getPopupMenu() == null || getMenuComponentCount() == 0) 708 return null; 709 710 return popupMenu.getComponentAtIndex(index); 711 } 712 713 /** 714 * Return components belonging to this menu 715 * 716 * @return components belonging to this menu 717 */ 718 public Component[] getMenuComponents() 719 { 720 return getPopupMenu().getComponents(); 721 } 722 723 /** 724 * Checks if this menu is a top level menu. The menu is top 725 * level menu if it is inside the menu bar. While if the menu 726 * inside some other menu, it is considered to be a pull-right menu. 727 * 728 * @return true if this menu is top level menu, and false otherwise 729 */ 730 public boolean isTopLevelMenu() 731 { 732 return getParent() instanceof JMenuBar; 733 } 734 735 /** 736 * Checks if given component exists in this menu. The submenus of 737 * this menu are checked as well 738 * 739 * @param component Component to look for 740 * 741 * @return true if the given component exists in this menu, and false otherwise 742 */ 743 public boolean isMenuComponent(Component component) 744 { 745 return false; 746 } 747 748 /** 749 * Returns popup menu associated with the menu. 750 * 751 * @return popup menu associated with the menu. 752 */ 753 public JPopupMenu getPopupMenu() 754 { 755 if (popupMenu == null) 756 { 757 popupMenu = new JPopupMenu(); 758 popupMenu.setInvoker(this); 759 } 760 return popupMenu; 761 } 762 763 /** 764 * Adds MenuListener to the menu 765 * 766 * @param listener MenuListener to add 767 */ 768 public void addMenuListener(MenuListener listener) 769 { 770 listenerList.add(MenuListener.class, listener); 771 } 772 773 /** 774 * Removes MenuListener from the menu 775 * 776 * @param listener MenuListener to remove 777 */ 778 public void removeMenuListener(MenuListener listener) 779 { 780 listenerList.remove(MenuListener.class, listener); 781 } 782 783 /** 784 * Returns all registered <code>MenuListener</code> objects. 785 * 786 * @return an array of listeners 787 * 788 * @since 1.4 789 */ 790 public MenuListener[] getMenuListeners() 791 { 792 return (MenuListener[]) listenerList.getListeners(MenuListener.class); 793 } 794 795 /** 796 * This method fires MenuEvents to all menu's MenuListeners. In this case 797 * menuSelected() method of MenuListeners is called to indicated that the menu 798 * was selected. 799 */ 800 protected void fireMenuSelected() 801 { 802 MenuListener[] listeners = getMenuListeners(); 803 804 for (int index = 0; index < listeners.length; ++index) 805 listeners[index].menuSelected(menuEvent); 806 } 807 808 /** 809 * This method fires MenuEvents to all menu's MenuListeners. In this case 810 * menuDeselected() method of MenuListeners is called to indicated that the menu 811 * was deselected. 812 */ 813 protected void fireMenuDeselected() 814 { 815 EventListener[] ll = listenerList.getListeners(MenuListener.class); 816 817 for (int i = 0; i < ll.length; i++) 818 ((MenuListener) ll[i]).menuDeselected(menuEvent); 819 } 820 821 /** 822 * This method fires MenuEvents to all menu's MenuListeners. In this case 823 * menuSelected() method of MenuListeners is called to indicated that the menu 824 * was cancelled. The menu is cancelled when it's popup menu is close without selection. 825 */ 826 protected void fireMenuCanceled() 827 { 828 EventListener[] ll = listenerList.getListeners(MenuListener.class); 829 830 for (int i = 0; i < ll.length; i++) 831 ((MenuListener) ll[i]).menuCanceled(menuEvent); 832 } 833 834 /** 835 * Creates WinListener that listens to the menu;s popup menu. 836 * 837 * @param popup JPopupMenu to listen to 838 * 839 * @return The WinListener 840 */ 841 protected WinListener createWinListener(JPopupMenu popup) 842 { 843 return new WinListener(popup); 844 } 845 846 /** 847 * Method of the MenuElementInterface. It reacts to the selection 848 * changes in the menu. If this menu was selected, then it 849 * displayes popup menu associated with it and if this menu was 850 * deselected it hides the popup menu. 851 * 852 * @param changed true if the menu was selected and false otherwise 853 */ 854 public void menuSelectionChanged(boolean changed) 855 { 856 // if this menu selection is true, then activate this menu and 857 // display popup associated with this menu 858 setSelected(changed); 859 } 860 861 /** 862 * Method of MenuElement interface. Returns sub components of 863 * this menu. 864 * 865 * @return array containing popupMenu that is associated with this menu 866 */ 867 public MenuElement[] getSubElements() 868 { 869 return new MenuElement[] { popupMenu }; 870 } 871 872 /** 873 * @return Returns reference to itself 874 */ 875 public Component getComponent() 876 { 877 return this; 878 } 879 880 /** 881 * This method is overriden with empty implementation, s.t the 882 * accelerator couldn't be set for the menu. The mnemonic should 883 * be used for the menu instead. 884 * 885 * @param keystroke accelerator for this menu 886 */ 887 public void setAccelerator(KeyStroke keystroke) 888 { 889 throw new Error("setAccelerator() is not defined for JMenu. Use setMnemonic() instead."); 890 } 891 892 /** 893 * This method process KeyEvent occuring when the menu is visible 894 * 895 * @param event The KeyEvent 896 */ 897 protected void processKeyEvent(KeyEvent event) 898 { 899 MenuSelectionManager.defaultManager().processKeyEvent(event); 900 } 901 902 /** 903 * Programatically performs click 904 * 905 * @param time Number of milliseconds for which this menu stays pressed 906 */ 907 public void doClick(int time) 908 { 909 getModel().setArmed(true); 910 getModel().setPressed(true); 911 try 912 { 913 java.lang.Thread.sleep(time); 914 } 915 catch (java.lang.InterruptedException e) 916 { 917 // probably harmless 918 } 919 920 getModel().setPressed(false); 921 getModel().setArmed(false); 922 popupMenu.show(this, this.getWidth(), 0); 923 } 924 925 /** 926 * A string that describes this JMenu. Normally only used 927 * for debugging. 928 * 929 * @return A string describing this JMenu 930 */ 931 protected String paramString() 932 { 933 return super.paramString(); 934 } 935 936 public AccessibleContext getAccessibleContext() 937 { 938 if (accessibleContext == null) 939 accessibleContext = new AccessibleJMenu(); 940 941 return accessibleContext; 942 } 943 944 /** 945 * Implements support for assisitive technologies for <code>JMenu</code>. 946 */ 947 protected class AccessibleJMenu extends AccessibleJMenuItem 948 implements AccessibleSelection 949 { 950 private static final long serialVersionUID = -8131864021059524309L; 951 952 protected AccessibleJMenu() 953 { 954 // Nothing to do here. 955 } 956 957 /** 958 * Returns the number of accessible children of this object. 959 * 960 * @return the number of accessible children of this object 961 */ 962 public int getAccessibleChildrenCount() 963 { 964 Component[] children = getMenuComponents(); 965 int count = 0; 966 for (int i = 0; i < children.length; i++) 967 { 968 if (children[i] instanceof Accessible) 969 count++; 970 } 971 return count; 972 } 973 974 /** 975 * Returns the accessible child with the specified <code>index</code>. 976 * 977 * @param index the index of the child to fetch 978 * 979 * @return the accessible child with the specified <code>index</code> 980 */ 981 public Accessible getAccessibleChild(int index) 982 { 983 Component[] children = getMenuComponents(); 984 int count = 0; 985 Accessible found = null; 986 for (int i = 0; i < children.length; i++) 987 { 988 if (children[i] instanceof Accessible) 989 { 990 if (count == index) 991 { 992 found = (Accessible) children[i]; 993 break; 994 } 995 count++; 996 } 997 } 998 return found; 999 } 1000 1001 /** 1002 * Returns the accessible selection of this object. AccessibleJMenus handle 1003 * their selection themselves, so we always return <code>this</code> here. 1004 * 1005 * @return the accessible selection of this object 1006 */ 1007 public AccessibleSelection getAccessibleSelection() 1008 { 1009 return this; 1010 } 1011 1012 /** 1013 * Returns the selected accessible child with the specified 1014 * <code>index</code>. 1015 * 1016 * @param index the index of the accessible selected child to return 1017 * 1018 * @return the selected accessible child with the specified 1019 * <code>index</code> 1020 */ 1021 public Accessible getAccessibleSelection(int index) 1022 { 1023 Accessible selected = null; 1024 // Only one item can be selected, which must therefore have index == 0. 1025 if (index == 0) 1026 { 1027 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 1028 MenuElement[] me = msm.getSelectedPath(); 1029 if (me != null) 1030 { 1031 for (int i = 0; i < me.length; i++) 1032 { 1033 if (me[i] == JMenu.this) 1034 { 1035 // This JMenu is selected, find and return the next 1036 // JMenuItem in the path. 1037 do 1038 { 1039 if (me[i] instanceof Accessible) 1040 { 1041 selected = (Accessible) me[i]; 1042 break; 1043 } 1044 i++; 1045 } while (i < me.length); 1046 } 1047 if (selected != null) 1048 break; 1049 } 1050 } 1051 } 1052 return selected; 1053 } 1054 1055 /** 1056 * Returns <code>true</code> if the accessible child with the specified 1057 * index is selected, <code>false</code> otherwise. 1058 * 1059 * @param index the index of the accessible child to check 1060 * 1061 * @return <code>true</code> if the accessible child with the specified 1062 * index is selected, <code>false</code> otherwise 1063 */ 1064 public boolean isAccessibleChildSelected(int index) 1065 { 1066 boolean selected = false; 1067 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 1068 MenuElement[] me = msm.getSelectedPath(); 1069 if (me != null) 1070 { 1071 Accessible toBeFound = getAccessibleChild(index); 1072 for (int i = 0; i < me.length; i++) 1073 { 1074 if (me[i] == toBeFound) 1075 { 1076 selected = true; 1077 break; 1078 } 1079 } 1080 } 1081 return selected; 1082 } 1083 1084 /** 1085 * Returns the accessible role of this object, which is 1086 * {@link AccessibleRole#MENU} for the AccessibleJMenu. 1087 * 1088 * @return the accessible role of this object 1089 */ 1090 public AccessibleRole getAccessibleRole() 1091 { 1092 return AccessibleRole.MENU; 1093 } 1094 1095 /** 1096 * Returns the number of selected accessible children. This will be 1097 * <code>0</code> if no item is selected, or <code>1</code> if an item 1098 * is selected. AccessibleJMenu can have maximum 1 selected item. 1099 * 1100 * @return the number of selected accessible children 1101 */ 1102 public int getAccessibleSelectionCount() 1103 { 1104 int count = 0; 1105 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 1106 MenuElement[] me = msm.getSelectedPath(); 1107 if (me != null) 1108 { 1109 for (int i = 0; i < me.length; i++) 1110 { 1111 if (me[i] == JMenu.this) 1112 { 1113 if (i + 1 < me.length) 1114 { 1115 count = 1; 1116 break; 1117 } 1118 } 1119 } 1120 } 1121 return count; 1122 } 1123 1124 /** 1125 * Selects the accessible child with the specified index. 1126 * 1127 * @param index the index of the accessible child to select 1128 */ 1129 public void addAccessibleSelection(int index) 1130 { 1131 Accessible child = getAccessibleChild(index); 1132 if (child != null && child instanceof JMenuItem) 1133 { 1134 JMenuItem mi = (JMenuItem) child; 1135 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 1136 msm.setSelectedPath(createPath(JMenu.this)); 1137 } 1138 } 1139 1140 /** 1141 * Removes the item with the specified index from the selection. 1142 * 1143 * @param index the index of the selected item to remove from the selection 1144 */ 1145 public void removeAccessibleSelection(int index) 1146 { 1147 Accessible child = getAccessibleChild(index); 1148 if (child != null) 1149 { 1150 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 1151 MenuElement[] oldSelection = msm.getSelectedPath(); 1152 for (int i = 0; i < oldSelection.length; i++) 1153 { 1154 if (oldSelection[i] == child) 1155 { 1156 // Found the specified child in the selection. Remove it 1157 // from the selection. 1158 MenuElement[] newSel = new MenuElement[i - 1]; 1159 System.arraycopy(oldSelection, 0, newSel, 0, i - 1); 1160 msm.setSelectedPath(newSel); 1161 break; 1162 } 1163 } 1164 } 1165 } 1166 1167 /** 1168 * Removes all possibly selected accessible children of this object from 1169 * the selection. 1170 */ 1171 public void clearAccessibleSelection() 1172 { 1173 MenuSelectionManager msm = MenuSelectionManager.defaultManager(); 1174 MenuElement[] oldSelection = msm.getSelectedPath(); 1175 for (int i = 0; i < oldSelection.length; i++) 1176 { 1177 if (oldSelection[i] == JMenu.this) 1178 { 1179 // Found this menu in the selection. Remove all children from 1180 // the selection. 1181 MenuElement[] newSel = new MenuElement[i]; 1182 System.arraycopy(oldSelection, 0, newSel, 0, i); 1183 msm.setSelectedPath(newSel); 1184 break; 1185 } 1186 } 1187 } 1188 1189 /** 1190 * AccessibleJMenu don't support multiple selection, so this method 1191 * does nothing. 1192 */ 1193 public void selectAllAccessibleSelection() 1194 { 1195 // Nothing to do here. 1196 } 1197 } 1198 1199 protected class WinListener extends WindowAdapter implements Serializable 1200 { 1201 private static final long serialVersionUID = -6415815570638474823L; 1202 1203 /** 1204 * Creates a new <code>WinListener</code>. 1205 * 1206 * @param popup the popup menu which is observed 1207 */ 1208 public WinListener(JPopupMenu popup) 1209 { 1210 // TODO: What should we do with the popup argument? 1211 } 1212 1213 /** 1214 * Receives notification when the popup menu is closing and deselects 1215 * the menu. 1216 * 1217 * @param event the window event 1218 */ 1219 public void windowClosing(WindowEvent event) 1220 { 1221 setSelected(false); 1222 } 1223 } 1224 1225 /** 1226 * This class listens to PropertyChangeEvents occuring in menu's action 1227 */ 1228 private class ActionChangedListener implements PropertyChangeListener 1229 { 1230 /** menu item associated with the action */ 1231 private JMenuItem menuItem; 1232 1233 /** Creates new ActionChangedListener and adds it to menuItem's action */ 1234 public ActionChangedListener(JMenuItem menuItem) 1235 { 1236 this.menuItem = menuItem; 1237 1238 Action a = menuItem.getAction(); 1239 if (a != null) 1240 a.addPropertyChangeListener(this); 1241 } 1242 1243 /**This method is invoked when some change occures in menuItem's action*/ 1244 public void propertyChange(PropertyChangeEvent evt) 1245 { 1246 // FIXME: Need to implement 1247 } 1248 } 1249 1250 /** 1251 * Creates an array to be feeded as argument to 1252 * {@link MenuSelectionManager#setSelectedPath(MenuElement[])} for the 1253 * specified element. This has the effect of selecting the specified element 1254 * and all its parents. 1255 * 1256 * @param leaf the leaf element for which to create the selected path 1257 * 1258 * @return the selected path array 1259 */ 1260 MenuElement[] createPath(JMenu leaf) 1261 { 1262 ArrayList path = new ArrayList(); 1263 MenuElement[] array = null; 1264 Component current = leaf.getPopupMenu(); 1265 while (true) 1266 { 1267 if (current instanceof JPopupMenu) 1268 { 1269 JPopupMenu popupMenu = (JPopupMenu) current; 1270 path.add(0, popupMenu); 1271 current = popupMenu.getInvoker(); 1272 } 1273 else if (current instanceof JMenu) 1274 { 1275 JMenu menu = (JMenu) current; 1276 path.add(0, menu); 1277 current = menu.getParent(); 1278 } 1279 else if (current instanceof JMenuBar) 1280 { 1281 JMenuBar menuBar = (JMenuBar) current; 1282 path.add(0, menuBar); 1283 array = new MenuElement[path.size()]; 1284 array = (MenuElement[]) path.toArray(array); 1285 break; 1286 } 1287 } 1288 return array; 1289 } 1290}