Source for javax.swing.JMenu

   1: /* JMenu.java --
   2:    Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   6: GNU Classpath is free software; you can redistribute it and/or modify
   7: it under the terms of the GNU General Public License as published by
   8: the Free Software Foundation; either version 2, or (at your option)
   9: any later version.
  10: 
  11: GNU Classpath is distributed in the hope that it will be useful, but
  12: WITHOUT ANY WARRANTY; without even the implied warranty of
  13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14: General Public License for more details.
  15: 
  16: You should have received a copy of the GNU General Public License
  17: along with GNU Classpath; see the file COPYING.  If not, write to the
  18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  19: 02110-1301 USA.
  20: 
  21: Linking this library statically or dynamically with other modules is
  22: making a combined work based on this library.  Thus, the terms and
  23: conditions of the GNU General Public License cover the whole
  24: combination.
  25: 
  26: As a special exception, the copyright holders of this library give you
  27: permission to link this library with independent modules to produce an
  28: executable, regardless of the license terms of these independent
  29: modules, and to copy and distribute the resulting executable under
  30: terms of your choice, provided that you also meet, for each linked
  31: independent module, the terms and conditions of the license of that
  32: module.  An independent module is a module which is not derived from
  33: or based on this library.  If you modify this library, you may extend
  34: this exception to your version of the library, but you are not
  35: obligated to do so.  If you do not wish to do so, delete this
  36: exception statement from your version. */
  37: 
  38: 
  39: package javax.swing;
  40: 
  41: import java.awt.Component;
  42: import java.awt.Dimension;
  43: import java.awt.GraphicsConfiguration;
  44: import java.awt.GraphicsDevice;
  45: import java.awt.GraphicsEnvironment;
  46: import java.awt.Insets;
  47: import java.awt.Point;
  48: import java.awt.Rectangle;
  49: import java.awt.Toolkit;
  50: import java.awt.event.KeyEvent;
  51: import java.awt.event.WindowAdapter;
  52: import java.awt.event.WindowEvent;
  53: import java.beans.PropertyChangeEvent;
  54: import java.beans.PropertyChangeListener;
  55: import java.io.Serializable;
  56: import java.util.ArrayList;
  57: import java.util.EventListener;
  58: 
  59: import javax.accessibility.Accessible;
  60: import javax.accessibility.AccessibleContext;
  61: import javax.accessibility.AccessibleRole;
  62: import javax.accessibility.AccessibleSelection;
  63: import javax.swing.event.ChangeEvent;
  64: import javax.swing.event.ChangeListener;
  65: import javax.swing.event.MenuEvent;
  66: import javax.swing.event.MenuListener;
  67: import javax.swing.plaf.MenuItemUI;
  68: 
  69: /**
  70:  * This class represents a menu that can be added to a menu bar or
  71:  * can be a submenu in some other menu. When JMenu is selected it
  72:  * displays JPopupMenu containing its menu items.
  73:  *
  74:  * <p>
  75:  * JMenu's fires MenuEvents when this menu's selection changes. If this menu
  76:  * is selected, then fireMenuSelectedEvent() is invoked. In case when menu is
  77:  * deselected or cancelled, then fireMenuDeselectedEvent() or 
  78:  * fireMenuCancelledEvent() is invoked, respectivelly.
  79:  * </p>
  80:  */
  81: public class JMenu extends JMenuItem implements Accessible, MenuElement
  82: {
  83:   /**
  84:    * Receives notifications when the JMenu's ButtonModel is changed and
  85:    * fires menuSelected or menuDeselected events when appropriate.
  86:    */
  87:   private class MenuChangeListener
  88:     implements ChangeListener
  89:   {
  90:     /**
  91:      * Indicates the last selected state.
  92:      */
  93:     private boolean selected;
  94: 
  95:     /**
  96:      * Receives notification when the JMenu's ButtonModel changes.
  97:      */
  98:     public void stateChanged(ChangeEvent ev)
  99:     {
 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: }