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}