Index: javax/swing/ActionMap.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/ActionMap.java,v retrieving revision 1.12 diff -u -r1.12 ActionMap.java --- javax/swing/ActionMap.java 19 Oct 2005 15:45:03 -0000 1.12 +++ javax/swing/ActionMap.java 14 Nov 2005 20:10:43 -0000 @@ -171,7 +171,9 @@ */ public Object[] keys() { - return actionMap.keySet().toArray(); + if (size() != 0) + return actionMap.keySet().toArray(); + return null; } /** @@ -188,7 +190,9 @@ set.addAll(Arrays.asList(parent.allKeys())); set.addAll(actionMap.keySet()); - return set.toArray(); + if (set.size() != 0) + return set.toArray(); + return null; } /** Index: javax/swing/InputMap.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/InputMap.java,v retrieving revision 1.12 diff -u -r1.12 InputMap.java --- javax/swing/InputMap.java 27 Jul 2005 12:41:33 -0000 1.12 +++ javax/swing/InputMap.java 14 Nov 2005 20:10:43 -0000 @@ -171,8 +171,12 @@ */ public KeyStroke[] keys() { - KeyStroke[] array = new KeyStroke[size()]; - return (KeyStroke[]) inputMap.keySet().toArray(array); + if (size() != 0) + { + KeyStroke[] array = new KeyStroke[size()]; + return (KeyStroke[]) inputMap.keySet().toArray(array); + } + return null; } /** @@ -189,7 +193,9 @@ set.addAll(Arrays.asList(parent.allKeys())); set.addAll(inputMap.keySet()); - KeyStroke[] array = new KeyStroke[size()]; + if (set.size() == 0) + return null; + KeyStroke[] array = new KeyStroke[set.size()]; return (KeyStroke[]) set.toArray(array); } Index: javax/swing/JMenuBar.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/JMenuBar.java,v retrieving revision 1.18 diff -u -r1.18 JMenuBar.java --- javax/swing/JMenuBar.java 19 Oct 2005 15:45:04 -0000 1.18 +++ javax/swing/JMenuBar.java 14 Nov 2005 20:10:43 -0000 @@ -234,8 +234,8 @@ */ public void addNotify() { - // FIXME: Should register this menu bar with the keyboard manager super.addNotify(); + KeyboardManager.getManager().registerJMenuBar(this); } public AccessibleContext getAccessibleContext() @@ -473,6 +473,63 @@ // Do nothing - needed for implementation of MenuElement interface } + /** + * This method overrides JComponent.processKeyBinding to allow the + * JMenuBar to check all the child components (recursiveley) to see + * if they'll consume the event. + * + * @param ks the KeyStroke for the event + * @param e the KeyEvent for the event + * @param condition the focus condition for the binding + * @param pressed true if the key is pressed + */ + protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, + boolean pressed) + { + // See if the regular JComponent behavior consumes the event + if (super.processKeyBinding(ks, e, condition, pressed)) + return true; + + // If not, have to recursively check all the child menu elements to see + // if they want it + MenuElement[] children = getSubElements(); + for (int i = 0; i < children.length; i++) + if (processKeyBindingHelper(children[i], ks, e, condition, pressed)) + return true; + return false; + } + + /** + * This is a helper method to recursively check the children of this + * JMenuBar to see if they will consume a key event via key bindings. + * This is used for menu accelerators. + * @param menuElement the menuElement to check (and check all its children) + * @param ks the KeyStroke for the event + * @param e the KeyEvent that may be consumed + * @param condition the focus condition for the binding + * @param pressed true if the key was pressed + * @return true menuElement or one of its children consume + * the event (processKeyBinding returns true for menuElement or one of + * its children). + */ + static boolean processKeyBindingHelper(MenuElement menuElement, KeyStroke ks, + KeyEvent e, int condition, + boolean pressed) + { + // First check the menuElement itself, if it's a JComponent + if (menuElement instanceof JComponent + && ((JComponent) menuElement).processKeyBinding(ks, e, condition, + pressed)) + return true; + + // If that didn't consume it, check all the children recursively + MenuElement[] children = menuElement.getSubElements(); + for (int i = 0; i < children.length; i++) + if (processKeyBindingHelper(children[i], ks, e, condition, pressed)) + return true; + return false; + } + /** * Process mouse events forwarded from MenuSelectionManager. This method * doesn't do anything. It is here to conform to the MenuElement interface. Index: javax/swing/JMenuItem.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/JMenuItem.java,v retrieving revision 1.22 diff -u -r1.22 JMenuItem.java --- javax/swing/JMenuItem.java 19 Oct 2005 15:45:04 -0000 1.22 +++ javax/swing/JMenuItem.java 14 Nov 2005 20:10:43 -0000 @@ -254,7 +254,9 @@ */ public void setAccelerator(KeyStroke keystroke) { + KeyStroke old = this.accelerator; this.accelerator = keystroke; + firePropertyChange ("accelerator", old, keystroke); } /** Index: javax/swing/KeyboardManager.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/KeyboardManager.java,v retrieving revision 1.2 diff -u -r1.2 KeyboardManager.java --- javax/swing/KeyboardManager.java 10 Nov 2005 20:03:56 -0000 1.2 +++ javax/swing/KeyboardManager.java 14 Nov 2005 20:10:43 -0000 @@ -45,6 +45,7 @@ import java.awt.event.KeyEvent; import java.util.Enumeration; import java.util.Hashtable; +import java.util.Vector; /** * This class maintains a mapping from top-level containers to a @@ -67,6 +68,12 @@ Hashtable topLevelLookup = new Hashtable(); /** + * A mapping between top level containers and Vectors of JMenuBars + * used to allow all the JMenuBars within a top level container + * a chance to consume key events. + */ + Hashtable menuBarLookup = new Hashtable(); + /** * Returns the shared instance of KeyboardManager. * @return the shared instance of KeybaordManager. */ @@ -101,7 +108,7 @@ * @return the Hashtable mapping KeyStrokes to Components for the * specified top-level container */ - public Hashtable getHashtableForTopLevel (Container c) + Hashtable getHashtableForTopLevel (Container c) { Hashtable keyToComponent = (Hashtable)topLevelLookup.get(c); if (keyToComponent == null) @@ -185,9 +192,12 @@ */ public void registerEntireMap (ComponentInputMap map) { + if (map == null) + return; JComponent comp = map.getComponent(); KeyStroke[] keys = map.keys(); - + if (keys == null) + return; // Find the top-level container associated with this ComponentInputMap Container topLevel = findTopLevel(comp); if (topLevel == null) @@ -201,18 +211,56 @@ public boolean processKeyStroke (Component comp, KeyStroke key, KeyEvent e) { + boolean pressed = e.getID() == KeyEvent.KEY_PRESSED; + // Look for the top-level ancestor Container topLevel = findTopLevel(comp); if (topLevel == null) - return false; + return false; // Now get the Hashtable for that top-level container Hashtable keyToComponent = getHashtableForTopLevel(topLevel); Enumeration keys = keyToComponent.keys(); - JComponent target = (JComponent)keyToComponent.get(key); - if (target == null) - return false; - return target.processKeyBinding - (key, e, JComponent.WHEN_IN_FOCUSED_WINDOW, - e.getID() == KeyEvent.KEY_PRESSED); + JComponent target = (JComponent)keyToComponent.get(key); + if (target != null && target.processKeyBinding + (key, e, JComponent.WHEN_IN_FOCUSED_WINDOW, pressed)) + return true; + + // Have to give all the JMenuBars a chance to consume the event + Vector menuBars = getVectorForTopLevel(topLevel); + for (int i = 0; i < menuBars.size(); i++) + if (((JMenuBar)menuBars.elementAt(i)).processKeyBinding(key, e, JComponent.WHEN_IN_FOCUSED_WINDOW, pressed)) + return true; + return false; + } + + /** + * Returns the Vector of JMenuBars associated with the top-level + * @param c the top-level container whose JMenuBar Vector we want + * @return the Vector of JMenuBars for this top level container + */ + Vector getVectorForTopLevel(Container c) + { + Vector result = (Vector) menuBarLookup.get(c); + if (result == null) + { + result = new Vector(); + menuBarLookup.put (c, result); + } + return result; + } + + /** + * In processKeyStroke, KeyManager must give all JMenuBars in the + * focused top-level container a chance to process the event. So, + * JMenuBars must be registered in KeyManager and associated with a + * top-level container. That's what this method is for. + * @param menuBar the JMenuBar to register + */ + public void registerJMenuBar (JMenuBar menuBar) + { + Container topLevel = findTopLevel(menuBar); + Vector menuBars = getVectorForTopLevel(topLevel); + if (!menuBars.contains(menuBar)) + menuBars.add(menuBar); } } Index: javax/swing/plaf/basic/BasicMenuItemUI.java =================================================================== RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicMenuItemUI.java,v retrieving revision 1.37 diff -u -r1.37 BasicMenuItemUI.java --- javax/swing/plaf/basic/BasicMenuItemUI.java 7 Nov 2005 18:26:50 -0000 1.37 +++ javax/swing/plaf/basic/BasicMenuItemUI.java 14 Nov 2005 20:10:44 -0000 @@ -46,14 +46,20 @@ import java.awt.Graphics; import java.awt.Insets; import java.awt.Rectangle; -import java.awt.event.KeyEvent; -import java.awt.event.MouseEvent; +import java.awt.event.ActionEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; import java.util.ArrayList; +import javax.swing.AbstractAction; +import javax.swing.ActionMap; import javax.swing.ButtonModel; import javax.swing.Icon; +import javax.swing.InputMap; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JMenu; @@ -72,6 +78,8 @@ import javax.swing.event.MenuKeyEvent; import javax.swing.event.MenuKeyListener; import javax.swing.event.MouseInputListener; +import javax.swing.plaf.ActionMapUIResource; +import javax.swing.plaf.ComponentInputMapUIResource; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.MenuItemUI; @@ -173,6 +181,54 @@ */ private int MenuGap = 10; + /** A PropertyChangeListener to make UI updates after property changes **/ + PropertyChangeHandler propertyChangeListener; + + /** + * A class to handle PropertChangeEvents for the JMenuItem + * @author Anthony Balkissoon abalkiss at redhat dot com. + */ + class PropertyChangeHandler implements PropertyChangeListener + { + /** + * This method is called when a property of the menuItem is changed. + * Currently it is only used to update the accelerator key bindings. + * + * @param e + * the PropertyChangeEvent + */ + public void propertyChange(PropertyChangeEvent e) + { + if (e.getPropertyName() == "accelerator") + { + InputMap map = SwingUtilities.getUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW); + if (map != null) + map.remove((KeyStroke)e.getOldValue()); + else + map = new ComponentInputMapUIResource(menuItem); + map.put((KeyStroke)e.getNewValue(), "doClick"); + } + } + } + + /** + * A class to handle accelerator keys. This is the Action we will + * perform when the accelerator key for this JMenuItem is pressed. + * @author Anthony Balkissoon abalkiss at redhat dot com + * + */ + class ClickAction extends AbstractAction + { + /** + * This is what is done when the accelerator key for the JMenuItem is + * pressed. + */ + public void actionPerformed(ActionEvent event) + { + doClick(MenuSelectionManager.defaultManager()); + } + } + /** * Creates a new BasicMenuItemUI object. */ @@ -182,6 +238,7 @@ menuDragMouseListener = createMenuDragMouseListener(menuItem); menuKeyListener = createMenuKeyListener(menuItem); itemListener = new ItemHandler(); + propertyChangeListener = new PropertyChangeHandler(); } /** @@ -426,7 +483,17 @@ */ protected void installKeyboardActions() { - // FIXME: Need to implement + InputMap focusedWindowMap = SwingUtilities.getUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW); + if (focusedWindowMap == null) + focusedWindowMap = new ComponentInputMapUIResource(menuItem); + focusedWindowMap.put(menuItem.getAccelerator(), "doClick"); + SwingUtilities.replaceUIInputMap(menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW, focusedWindowMap); + + ActionMap UIActionMap = SwingUtilities.getUIActionMap(menuItem); + if (UIActionMap == null) + UIActionMap = new ActionMapUIResource(); + UIActionMap.put("doClick", new ClickAction()); + SwingUtilities.replaceUIActionMap(menuItem, UIActionMap); } /** @@ -439,6 +506,7 @@ menuItem.addMenuDragMouseListener(menuDragMouseListener); menuItem.addMenuKeyListener(menuKeyListener); menuItem.addItemListener(itemListener); + menuItem.addPropertyChangeListener(propertyChangeListener); } /** @@ -456,6 +524,7 @@ installDefaults(); installComponents(menuItem); installListeners(); + installKeyboardActions(); } /** @@ -714,8 +783,9 @@ * Uninstalls any keyboard actions. */ protected void uninstallKeyboardActions() - { - // FIXME: need to implement + { + SwingUtilities.replaceUIInputMap(menuItem, + JComponent.WHEN_IN_FOCUSED_WINDOW, null); } /** @@ -727,6 +797,7 @@ menuItem.removeMenuDragMouseListener(menuDragMouseListener); menuItem.removeMenuKeyListener(menuKeyListener); menuItem.removeItemListener(itemListener); + menuItem.removePropertyChangeListener(propertyChangeListener); } /**