2 Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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)
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.
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
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
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. */
41 import java.awt.Component;
42 import java.awt.Point;
43 import java.awt.event.KeyEvent;
44 import java.awt.event.WindowAdapter;
45 import java.awt.event.WindowEvent;
46 import java.beans.PropertyChangeEvent;
47 import java.beans.PropertyChangeListener;
48 import java.io.Serializable;
49 import java.util.ArrayList;
50 import java.util.EventListener;
52 import javax.accessibility.Accessible;
53 import javax.accessibility.AccessibleContext;
54 import javax.accessibility.AccessibleRole;
55 import javax.accessibility.AccessibleSelection;
56 import javax.swing.event.MenuEvent;
57 import javax.swing.event.MenuListener;
58 import javax.swing.plaf.MenuItemUI;
61 * This class represents a menu that can be added to a menu bar or
62 * can be a submenu in some other menu. When JMenu is selected it
63 * displays JPopupMenu containing its menu items.
66 * JMenu's fires MenuEvents when this menu's selection changes. If this menu
67 * is selected, then fireMenuSelectedEvent() is invoked. In case when menu is
68 * deselected or cancelled, then fireMenuDeselectedEvent() or
69 * fireMenuCancelledEvent() is invoked, respectivelly.
72 public class JMenu extends JMenuItem implements Accessible, MenuElement
74 private static final long serialVersionUID = 4227225638931828014L;
76 /** A Popup menu associated with this menu, which pops up when menu is selected */
77 private JPopupMenu popupMenu = new JPopupMenu();
79 /** Whenever menu is selected or deselected the MenuEvent is fired to
80 menu's registered listeners. */
81 private MenuEvent menuEvent = new MenuEvent(this);
83 /*Amount of time, in milliseconds, that should pass before popupMenu
84 associated with this menu appears or disappers */
88 protected WinListener popupListener;
90 /** Location at which popup menu associated with this menu will be
92 private Point menuLocation;
95 * Creates a new JMenu object.
104 * Creates a new <code>JMenu</code> with the specified label.
106 * @param text label for this menu
108 public JMenu(String text)
111 popupMenu.setInvoker(this);
116 * Creates a new <code>JMenu</code> object.
118 * @param action Action that is used to create menu item tha will be
121 public JMenu(Action action)
124 createActionChangeListener(this);
125 popupMenu.setInvoker(this);
130 * Creates a new <code>JMenu</code> with specified label and an option
131 * for this menu to be tear-off menu.
133 * @param text label for this menu
134 * @param tearoff true if this menu should be tear-off and false otherwise
136 public JMenu(String text, boolean tearoff)
138 // FIXME: tearoff not implemented
143 * Adds specified menu item to this menu
145 * @param item Menu item to add to this menu
147 * @return Menu item that was added
149 public JMenuItem add(JMenuItem item)
151 return popupMenu.add(item);
155 * Adds specified component to this menu.
157 * @param component Component to add to this menu
159 * @return Component that was added
161 public Component add(Component component)
163 popupMenu.insert(component, -1);
168 * Adds specified component to this menu at the given index
170 * @param component Component to add
171 * @param index Position of this menu item in the menu
173 * @return Component that was added
175 public Component add(Component component, int index)
177 return popupMenu.add(component, index);
181 * Adds JMenuItem constructed with the specified label to this menu
183 * @param text label for the menu item that will be added
185 * @return Menu Item that was added to this menu
187 public JMenuItem add(String text)
189 return popupMenu.add(text);
193 * Adds JMenuItem constructed using properties from specified action.
195 * @param action action to construct the menu item with
197 * @return Menu Item that was added to this menu
199 public JMenuItem add(Action action)
201 return popupMenu.add(action);
205 * Removes given menu item from this menu. Nothing happens if
206 * this menu doesn't contain specified menu item.
208 * @param item Menu Item which needs to be removed
210 public void remove(JMenuItem item)
212 popupMenu.remove(item);
216 * Removes component at the specified index from this menu
218 * @param index Position of the component that needs to be removed in the menu
220 public void remove(int index)
222 popupMenu.remove(index);
226 * Removes given component from this menu.
228 * @param component Component to remove
230 public void remove(Component component)
232 int index = popupMenu.getComponentIndex(component);
233 popupMenu.remove(index);
237 * Removes all menu items from the menu
239 public void removeAll()
241 popupMenu.removeAll();
245 * Creates JMenuItem with the specified text and inserts it in the
246 * at the specified index
248 * @param text label for the new menu item
249 * @param index index at which to insert newly created menu item.
251 public void insert(String text, int index)
253 this.insert(new JMenuItem(text), index);
257 * Creates JMenuItem with the specified text and inserts it in the
258 * at the specified index. IllegalArgumentException is thrown
259 * if index is less than 0
261 * @param item menu item to insert
262 * @param index index at which to insert menu item.
263 * @return Menu item that was added to the menu
265 public JMenuItem insert(JMenuItem item, int index)
268 throw new IllegalArgumentException("index less than zero");
270 popupMenu.insert(item, index);
275 * Creates JMenuItem with the associated action and inserts it to the menu
276 * at the specified index. IllegalArgumentException is thrown
277 * if index is less than 0
279 * @param action Action for the new menu item
280 * @param index index at which to insert newly created menu item.
281 * @return Menu item that was added to the menu
283 public JMenuItem insert(Action action, int index)
285 JMenuItem item = new JMenuItem(action);
286 this.insert(item, index);
292 * This method sets this menuItem's UI to the UIManager's default for the
293 * current look and feel.
295 public void updateUI()
297 setUI((MenuItemUI) UIManager.getUI(this));
301 * This method returns a name to identify which look and feel class will be
302 * the UI delegate for the menu.
304 * @return The Look and Feel classID. "MenuUI"
306 public String getUIClassID()
312 * Sets model for this menu.
314 * @param model model to set
316 public void setModel(ButtonModel model)
318 super.setModel(model);
322 * Returns true if the menu is selected and false otherwise
324 * @return true if the menu is selected and false otherwise
326 public boolean isSelected()
328 return super.isSelected();
332 * A helper method to handle setSelected calls from both mouse events and
333 * direct calls to setSelected. Direct calls shouldn't expand the popup
334 * menu and should select the JMenu even if it is disabled. Mouse events
335 * only select the JMenu if it is enabled and should expand the popup menu
336 * associated with this JMenu.
337 * @param selected whether or not the JMenu was selected
338 * @param menuEnabled whether or not selecting the menu is "enabled". This
339 * is always true for direct calls, and is set to isEnabled() for mouse
341 * @param showMenu whether or not to show the popup menu
343 private void setSelectedHelper(boolean selected, boolean menuEnabled, boolean showMenu)
345 // If menu is selected and enabled, activates the menu and
346 // displays associated popup.
347 if (selected && menuEnabled)
349 super.setArmed(true);
350 super.setSelected(true);
352 // FIXME: The popup menu should be shown on the screen after certain
353 // number of seconds pass. The 'delay' property of this menu indicates
354 // this amount of seconds. 'delay' property is 0 by default.
362 if (menuLocation == null)
364 // Calculate correct position of the popup. Note that location of the popup
365 // passed to show() should be relative to the popup's invoker
366 if (isTopLevelMenu())
367 y = this.getHeight();
370 getPopupMenu().show(this, x, y);
374 getPopupMenu().show(this, menuLocation.x, menuLocation.y);
381 super.setSelected(false);
382 super.setArmed(false);
383 fireMenuDeselected();
384 popupMenu.setVisible(false);
389 * Changes this menu selected state if selected is true and false otherwise
390 * This method fires menuEvents to menu's registered listeners.
392 * @param selected true if the menu should be selected and false otherwise
394 public void setSelected(boolean selected)
396 setSelectedHelper(selected, true, false);
400 * Checks if PopupMenu associated with this menu is visible
402 * @return true if the popup associated with this menu is currently visible
403 * on the screen and false otherwise.
405 public boolean isPopupMenuVisible()
407 return popupMenu.isVisible();
411 * Sets popup menu visibility
413 * @param popup true if popup should be visible and false otherwise
415 public void setPopupMenuVisible(boolean popup)
417 if (getModel().isEnabled())
418 popupMenu.setVisible(popup);
422 * Returns origin point of the popup menu
424 * @return Point containing
426 protected Point getPopupMenuOrigin()
428 // if menu in the menu bar
429 if (isTopLevelMenu())
430 return new Point(0, this.getHeight());
433 return new Point(this.getWidth(), 0);
437 * Returns delay property.
439 * @return delay property, indicating number of milliseconds before
440 * popup menu associated with the menu appears or disappears after
441 * menu was selected or deselected respectively
443 public int getDelay()
449 * Sets delay property for this menu. If given time for the delay
450 * property is negative, then IllegalArgumentException is thrown
452 * @param delay number of milliseconds before
453 * popup menu associated with the menu appears or disappears after
454 * menu was selected or deselected respectively
456 public void setDelay(int delay)
459 throw new IllegalArgumentException("delay less than 0");
464 * Sets location at which popup menu should be displayed
465 * The location given is relative to this menu item
467 * @param x x-coordinate of the menu location
468 * @param y y-coordinate of the menu location
470 public void setMenuLocation(int x, int y)
472 menuLocation = new Point(x, y);
476 * Creates and returns JMenuItem associated with the given action
478 * @param action Action to use for creation of JMenuItem
480 * @return JMenuItem that was creted with given action
482 protected JMenuItem createActionComponent(Action action)
484 return new JMenuItem(action);
488 * Creates ActionChangeListener to listen for PropertyChangeEvents occuring
489 * in the action that is associated with this menu
491 * @param item menu that contains action to listen to
493 * @return The PropertyChangeListener
495 protected PropertyChangeListener createActionChangeListener(JMenuItem item)
497 return new ActionChangedListener(item);
501 * Adds separator to the end of the menu items in the menu.
503 public void addSeparator()
505 getPopupMenu().addSeparator();
509 * Inserts separator in the menu at the specified index.
511 * @param index Index at which separator should be inserted
513 public void insertSeparator(int index)
516 throw new IllegalArgumentException("index less than 0");
518 getPopupMenu().insert(new JPopupMenu.Separator(), index);
522 * Returns menu item located at the specified index in the menu
524 * @param index Index at which to look for the menu item
526 * @return menu item located at the specified index in the menu
528 public JMenuItem getItem(int index)
531 throw new IllegalArgumentException("index less than 0");
533 Component c = popupMenu.getComponentAtIndex(index);
535 if (c instanceof JMenuItem)
536 return (JMenuItem) c;
542 * Returns number of items in the menu including separators.
544 * @return number of items in the menu
546 * @see #getMenuComponentCount()
548 public int getItemCount()
550 return getMenuComponentCount();
554 * Checks if this menu is a tear-off menu.
556 * @return true if this menu is a tear-off menu and false otherwise
558 public boolean isTearOff()
560 // NOT YET IMPLEMENTED
565 * Returns number of menu components in this menu
567 * @return number of menu components in this menu
569 public int getMenuComponentCount()
571 return popupMenu.getComponentCount();
575 * Returns menu component located at the givent index
578 * @param index index at which to get the menu component in the menu
580 * @return Menu Component located in the menu at the specified index
582 public Component getMenuComponent(int index)
584 return (Component) popupMenu.getComponentAtIndex(index);
588 * Return components belonging to this menu
590 * @return components belonging to this menu
592 public Component[] getMenuComponents()
594 return popupMenu.getComponents();
598 * Checks if this menu is a top level menu. The menu is top
599 * level menu if it is inside the menu bar. While if the menu
600 * inside some other menu, it is considered to be a pull-right menu.
602 * @return true if this menu is top level menu, and false otherwise
604 public boolean isTopLevelMenu()
606 return getParent() instanceof JMenuBar;
610 * Checks if given component exists in this menu. The submenus of
611 * this menu are checked as well
613 * @param component Component to look for
615 * @return true if the given component exists in this menu, and false otherwise
617 public boolean isMenuComponent(Component component)
623 * Returns popup menu associated with the menu.
625 * @return popup menu associated with the menu.
627 public JPopupMenu getPopupMenu()
633 * Adds MenuListener to the menu
635 * @param listener MenuListener to add
637 public void addMenuListener(MenuListener listener)
639 listenerList.add(MenuListener.class, listener);
643 * Removes MenuListener from the menu
645 * @param listener MenuListener to remove
647 public void removeMenuListener(MenuListener listener)
649 listenerList.remove(MenuListener.class, listener);
653 * Returns all registered <code>MenuListener</code> objects.
655 * @return an array of listeners
659 public MenuListener[] getMenuListeners()
661 return (MenuListener[]) listenerList.getListeners(MenuListener.class);
665 * This method fires MenuEvents to all menu's MenuListeners. In this case
666 * menuSelected() method of MenuListeners is called to indicated that the menu
669 protected void fireMenuSelected()
671 MenuListener[] listeners = getMenuListeners();
673 for (int index = 0; index < listeners.length; ++index)
674 listeners[index].menuSelected(menuEvent);
678 * This method fires MenuEvents to all menu's MenuListeners. In this case
679 * menuDeselected() method of MenuListeners is called to indicated that the menu
682 protected void fireMenuDeselected()
684 EventListener[] ll = listenerList.getListeners(MenuListener.class);
686 for (int i = 0; i < ll.length; i++)
687 ((MenuListener) ll[i]).menuDeselected(menuEvent);
691 * This method fires MenuEvents to all menu's MenuListeners. In this case
692 * menuSelected() method of MenuListeners is called to indicated that the menu
693 * was cancelled. The menu is cancelled when it's popup menu is close without selection.
695 protected void fireMenuCanceled()
697 EventListener[] ll = listenerList.getListeners(MenuListener.class);
699 for (int i = 0; i < ll.length; i++)
700 ((MenuListener) ll[i]).menuCanceled(menuEvent);
704 * Creates WinListener that listens to the menu;s popup menu.
706 * @param popup JPopupMenu to listen to
708 * @return The WinListener
710 protected WinListener createWinListener(JPopupMenu popup)
712 return new WinListener(popup);
716 * Method of the MenuElementInterface. It reacts to the selection
717 * changes in the menu. If this menu was selected, then it
718 * displayes popup menu associated with it and if this menu was
719 * deselected it hides the popup menu.
721 * @param changed true if the menu was selected and false otherwise
723 public void menuSelectionChanged(boolean changed)
725 // if this menu selection is true, then activate this menu and
726 // display popup associated with this menu
727 setSelectedHelper(changed, isEnabled(), true);
731 * Method of MenuElement interface. Returns sub components of
734 * @return array containing popupMenu that is associated with this menu
736 public MenuElement[] getSubElements()
738 return new MenuElement[] { popupMenu };
742 * @return Returns reference to itself
744 public Component getComponent()
750 * This method is overriden with empty implementation, s.t the
751 * accelerator couldn't be set for the menu. The mnemonic should
752 * be used for the menu instead.
754 * @param keystroke accelerator for this menu
756 public void setAccelerator(KeyStroke keystroke)
758 throw new Error("setAccelerator() is not defined for JMenu. Use setMnemonic() instead.");
762 * This method process KeyEvent occuring when the menu is visible
764 * @param event The KeyEvent
766 protected void processKeyEvent(KeyEvent event)
768 MenuSelectionManager.defaultManager().processKeyEvent(event);
772 * Programatically performs click
774 * @param time Number of milliseconds for which this menu stays pressed
776 public void doClick(int time)
778 getModel().setArmed(true);
779 getModel().setPressed(true);
782 java.lang.Thread.sleep(time);
784 catch (java.lang.InterruptedException e)
789 getModel().setPressed(false);
790 getModel().setArmed(false);
791 popupMenu.show(this, this.getWidth(), 0);
795 * A string that describes this JMenu. Normally only used
798 * @return A string describing this JMenu
800 protected String paramString()
802 return super.paramString();
805 public AccessibleContext getAccessibleContext()
807 if (accessibleContext == null)
808 accessibleContext = new AccessibleJMenu();
810 return accessibleContext;
814 * Implements support for assisitive technologies for <code>JMenu</code>.
816 protected class AccessibleJMenu extends AccessibleJMenuItem
817 implements AccessibleSelection
819 private static final long serialVersionUID = -8131864021059524309L;
821 protected AccessibleJMenu()
823 // Nothing to do here.
827 * Returns the number of accessible children of this object.
829 * @return the number of accessible children of this object
831 public int getAccessibleChildrenCount()
833 Component[] children = getMenuComponents();
835 for (int i = 0; i < children.length; i++)
837 if (children[i] instanceof Accessible)
844 * Returns the accessible child with the specified <code>index</code>.
846 * @param index the index of the child to fetch
848 * @return the accessible child with the specified <code>index</code>
850 public Accessible getAccessibleChild(int index)
852 Component[] children = getMenuComponents();
854 Accessible found = null;
855 for (int i = 0; i < children.length; i++)
857 if (children[i] instanceof Accessible)
861 found = (Accessible) children[i];
871 * Returns the accessible selection of this object. AccessibleJMenus handle
872 * their selection themselves, so we always return <code>this</code> here.
874 * @return the accessible selection of this object
876 public AccessibleSelection getAccessibleSelection()
882 * Returns the selected accessible child with the specified
883 * <code>index</code>.
885 * @param index the index of the accessible selected child to return
887 * @return the selected accessible child with the specified
890 public Accessible getAccessibleSelection(int index)
892 Accessible selected = null;
893 // Only one item can be selected, which must therefore have index == 0.
896 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
897 MenuElement[] me = msm.getSelectedPath();
900 for (int i = 0; i < me.length; i++)
902 if (me[i] == JMenu.this)
904 // This JMenu is selected, find and return the next
905 // JMenuItem in the path.
908 if (me[i] instanceof Accessible)
910 selected = (Accessible) me[i];
914 } while (i < me.length);
916 if (selected != null)
925 * Returns <code>true</code> if the accessible child with the specified
926 * index is selected, <code>false</code> otherwise.
928 * @param index the index of the accessible child to check
930 * @return <code>true</code> if the accessible child with the specified
931 * index is selected, <code>false</code> otherwise
933 public boolean isAccessibleChildSelected(int index)
935 boolean selected = false;
936 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
937 MenuElement[] me = msm.getSelectedPath();
940 Accessible toBeFound = getAccessibleChild(index);
941 for (int i = 0; i < me.length; i++)
943 if (me[i] == toBeFound)
954 * Returns the accessible role of this object, which is
955 * {@link AccessibleRole#MENU} for the AccessibleJMenu.
957 * @return the accessible role of this object
959 public AccessibleRole getAccessibleRole()
961 return AccessibleRole.MENU;
965 * Returns the number of selected accessible children. This will be
966 * <code>0</code> if no item is selected, or <code>1</code> if an item
967 * is selected. AccessibleJMenu can have maximum 1 selected item.
969 * @return the number of selected accessible children
971 public int getAccessibleSelectionCount()
974 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
975 MenuElement[] me = msm.getSelectedPath();
978 for (int i = 0; i < me.length; i++)
980 if (me[i] == JMenu.this)
982 if (i + 1 < me.length)
994 * Selects the accessible child with the specified index.
996 * @param index the index of the accessible child to select
998 public void addAccessibleSelection(int index)
1000 Accessible child = getAccessibleChild(index);
1001 if (child != null && child instanceof JMenuItem)
1003 JMenuItem mi = (JMenuItem) child;
1004 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1005 msm.setSelectedPath(createPath(JMenu.this));
1010 * Removes the item with the specified index from the selection.
1012 * @param index the index of the selected item to remove from the selection
1014 public void removeAccessibleSelection(int index)
1016 Accessible child = getAccessibleChild(index);
1019 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1020 MenuElement[] oldSelection = msm.getSelectedPath();
1021 for (int i = 0; i < oldSelection.length; i++)
1023 if (oldSelection[i] == child)
1025 // Found the specified child in the selection. Remove it
1026 // from the selection.
1027 MenuElement[] newSel = new MenuElement[i - 1];
1028 System.arraycopy(oldSelection, 0, newSel, 0, i - 1);
1029 msm.setSelectedPath(newSel);
1037 * Removes all possibly selected accessible children of this object from
1040 public void clearAccessibleSelection()
1042 MenuSelectionManager msm = MenuSelectionManager.defaultManager();
1043 MenuElement[] oldSelection = msm.getSelectedPath();
1044 for (int i = 0; i < oldSelection.length; i++)
1046 if (oldSelection[i] == JMenu.this)
1048 // Found this menu in the selection. Remove all children from
1050 MenuElement[] newSel = new MenuElement[i];
1051 System.arraycopy(oldSelection, 0, newSel, 0, i);
1052 msm.setSelectedPath(newSel);
1059 * AccessibleJMenu don't support multiple selection, so this method
1062 public void selectAllAccessibleSelection()
1064 // Nothing to do here.
1068 protected class WinListener extends WindowAdapter implements Serializable
1070 private static final long serialVersionUID = -6415815570638474823L;
1073 * Creates a new <code>WinListener</code>.
1075 * @param popup the popup menu which is observed
1077 public WinListener(JPopupMenu popup)
1079 // TODO: What should we do with the popup argument?
1083 * Receives notification when the popup menu is closing and deselects
1086 * @param event the window event
1088 public void windowClosing(WindowEvent event)
1095 * This class listens to PropertyChangeEvents occuring in menu's action
1097 private class ActionChangedListener implements PropertyChangeListener
1099 /** menu item associated with the action */
1100 private JMenuItem menuItem;
1102 /** Creates new ActionChangedListener and adds it to menuItem's action */
1103 public ActionChangedListener(JMenuItem menuItem)
1105 this.menuItem = menuItem;
1107 Action a = menuItem.getAction();
1109 a.addPropertyChangeListener(this);
1112 /**This method is invoked when some change occures in menuItem's action*/
1113 public void propertyChange(PropertyChangeEvent evt)
1115 // FIXME: Need to implement
1120 * Creates an array to be feeded as argument to
1121 * {@link MenuSelectionManager#setSelectedPath(MenuElement[])} for the
1122 * specified element. This has the effect of selecting the specified element
1123 * and all its parents.
1125 * @param leaf the leaf element for which to create the selected path
1127 * @return the selected path array
1129 MenuElement[] createPath(JMenu leaf)
1131 ArrayList path = new ArrayList();
1132 MenuElement[] array = null;
1133 Component current = leaf.getPopupMenu();
1136 if (current instanceof JPopupMenu)
1138 JPopupMenu popupMenu = (JPopupMenu) current;
1139 path.add(0, popupMenu);
1140 current = popupMenu.getInvoker();
1142 else if (current instanceof JMenu)
1144 JMenu menu = (JMenu) current;
1146 current = menu.getParent();
1148 else if (current instanceof JMenuBar)
1150 JMenuBar menuBar = (JMenuBar) current;
1151 path.add(0, menuBar);
1152 array = new MenuElement[path.size()];
1153 array = (MenuElement[]) path.toArray(array);