001    /* JMenu.java --
002       Copyright (C) 2002, 2004, 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing;
040    
041    import java.awt.Component;
042    import java.awt.Dimension;
043    import java.awt.GraphicsConfiguration;
044    import java.awt.GraphicsDevice;
045    import java.awt.GraphicsEnvironment;
046    import java.awt.Insets;
047    import java.awt.Point;
048    import java.awt.Rectangle;
049    import java.awt.Toolkit;
050    import java.awt.event.KeyEvent;
051    import java.awt.event.WindowAdapter;
052    import java.awt.event.WindowEvent;
053    import java.beans.PropertyChangeEvent;
054    import java.beans.PropertyChangeListener;
055    import java.io.Serializable;
056    import java.util.ArrayList;
057    import java.util.EventListener;
058    
059    import javax.accessibility.Accessible;
060    import javax.accessibility.AccessibleContext;
061    import javax.accessibility.AccessibleRole;
062    import javax.accessibility.AccessibleSelection;
063    import javax.swing.event.ChangeEvent;
064    import javax.swing.event.ChangeListener;
065    import javax.swing.event.MenuEvent;
066    import javax.swing.event.MenuListener;
067    import 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     */
081    public 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    }