I implemented the keyboard actions for BasicPopupMenuUI. Now you can navigate through menus using the arrow keys and activate an item using ENTER, or cancel using ESC. (with the latest fixes you can also activate the menubar with F10, so you can use menus compeletely without mouse now :-D)

It has been quite tricky to implement this. The keyboard actions have to be installed as WHEN_IN_FOCUSED_WINDOW. However, since more than one JPopupMenu can be opened in one window, the mappings would mess up, because the mappings are stored in a window->keystroke mapping. I implemented it so that the keyboard actions for a popup are installed when a JPopupMenu opens, and are deinstalled when it is closed. This way, only the currently active PopupMenu can receive keyboard actions.

2006-07-25  Roman Kennke  <[EMAIL PROTECTED]>

        * javax/swing/plaf/basic/BasicPopupMenuUI.java
        (NavigateAction): New inner class. This is responsible for
        keyboard navigation through menus.
        (KeyboardHelper): New inner class. This manages the
        keyboard mappings and focus when a popup opens or closes.
        (keyboardHelper): New static field.
        (numPopups): New static field.
        (installUI): Create KeyboardHelper for first popup.
        Call installKeyboardActions().
        (installKeyboardActions): Removed NotImplementedException.
        This method is a no-op.
        (installKeyboardActionsImpl): New method. Installs keyboard
        mapping when a popup is opened.
        (getActionMap): New helper method.
        (createDefaultActions): New helper method.
        (uninstallUI): Uninstall KeyboardHelper when last Popup is
        uninstalled. Call uninstallKeyboardActions().
        (uninstallKeyboardActions): Removed NotImplementedException.
        This method is a no-op.
        (uninstallKeyboardActionsImpl): New method. Uninstalls keyboard
        mapping when a popup is closed.

/Roman
Index: javax/swing/plaf/basic/BasicPopupMenuUI.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/plaf/basic/BasicPopupMenuUI.java,v
retrieving revision 1.19
diff -u -1 -2 -r1.19 BasicPopupMenuUI.java
--- javax/swing/plaf/basic/BasicPopupMenuUI.java	21 Jun 2006 16:10:20 -0000	1.19
+++ javax/swing/plaf/basic/BasicPopupMenuUI.java	25 Jul 2006 22:52:15 -0000
@@ -28,62 +28,616 @@
 executable, regardless of the license terms of these independent
 modules, and to copy and distribute the resulting executable under
 terms of your choice, provided that you also meet, for each linked
 independent module, the terms and conditions of the license of that
 module.  An independent module is a module which is not derived from
 or based on this library.  If you modify this library, you may extend
 this exception to your version of the library, but you are not
 obligated to do so.  If you do not wish to do so, delete this
 exception statement from your version. */
 
 package javax.swing.plaf.basic;
 
-import gnu.classpath.NotImplementedException;
-
 import java.awt.Component;
 import java.awt.Dimension;
+import java.awt.KeyboardFocusManager;
+import java.awt.event.ActionEvent;
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
 import java.awt.event.MouseEvent;
+import java.util.EventListener;
 
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
 import javax.swing.BoxLayout;
+import javax.swing.InputMap;
+import javax.swing.JApplet;
 import javax.swing.JComponent;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
 import javax.swing.JMenuItem;
 import javax.swing.JPopupMenu;
+import javax.swing.JRootPane;
 import javax.swing.LookAndFeel;
 import javax.swing.MenuElement;
 import javax.swing.MenuSelectionManager;
 import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
 import javax.swing.event.PopupMenuEvent;
 import javax.swing.event.PopupMenuListener;
+import javax.swing.plaf.ActionMapUIResource;
 import javax.swing.plaf.ComponentUI;
 import javax.swing.plaf.PopupMenuUI;
 
-
 /**
  * UI Delegate for JPopupMenu
  */
 public class BasicPopupMenuUI extends PopupMenuUI
 {
+  /**
+   * Handles keyboard navigation through menus.
+   */
+  private static class NavigateAction
+    extends AbstractAction
+  {
+
+    /**
+     * Creates a new NavigateAction instance.
+     *
+     * @param name the name of the action
+     */
+    NavigateAction(String name)
+    {
+      super(name);
+    }
+
+    /**
+     * Actually performs the action.
+     */
+    public void actionPerformed(ActionEvent event)
+    {
+      String name = (String) getValue(Action.NAME);
+      if (name.equals("selectNext"))
+        navigateNextPrevious(true);
+      else if (name.equals("selectPrevious"))
+        navigateNextPrevious(false);
+      else if (name.equals("selectChild"))
+        navigateParentChild(true);
+      else if (name.equals("selectParent"))
+        navigateParentChild(false);
+      else if (name.equals("cancel"))
+        cancel();
+      else if (name.equals("return"))
+        doReturn();
+      else
+        assert false : "Must not reach here";
+    }
+
+    /**
+     * Navigates to the next or previous menu item.
+     *
+     * @param dir <code>true</code>: navigate to next, <code>false</code>:
+     *        navigate to previous
+     */
+    private void navigateNextPrevious(boolean dir)
+    {
+      MenuSelectionManager msm = MenuSelectionManager.defaultManager();
+      MenuElement path[] = msm.getSelectedPath();
+      int len = path.length;
+      if (len >= 2)
+        {
+
+          if (path[0] instanceof JMenuBar &&
+              path[1] instanceof JMenu && len == 2)
+            {
+
+              // A toplevel menu is selected, but its popup not yet shown.
+              // Show the popup and select the first item
+              JPopupMenu popup = ((JMenu)path[1]).getPopupMenu();
+              MenuElement next =
+                findEnabledChild(popup.getSubElements(), -1, true);
+              MenuElement[] newPath;
+
+              if (next != null)
+                {
+                  newPath = new MenuElement[4];
+                  newPath[3] = next;
+                }
+              else
+                {
+                  // Menu has no enabled items, show the popup anyway.
+                  newPath = new MenuElement[3];
+                }
+              System.arraycopy(path, 0, newPath, 0, 2);
+              newPath[2] = popup;
+              msm.setSelectedPath(newPath);
+            }
+          else if (path[len - 1] instanceof JPopupMenu &&
+                   path[len - 2] instanceof JMenu)
+            {
+              // Select next item in already shown popup menu.
+              JMenu menu = (JMenu) path[len - 2];
+              JPopupMenu popup = menu.getPopupMenu();
+              MenuElement next =
+                findEnabledChild(popup.getSubElements(), -1, dir);
+
+              if (next != null)
+                {
+                  MenuElement[] newPath = new MenuElement[len + 1];
+                  System.arraycopy(path, 0, newPath, 0, len);
+                  newPath[len] = next;
+                  msm.setSelectedPath(newPath);
+                }
+              else
+                {
+                  // All items in the popup are disabled.
+                  // Find the parent popup menu and select
+                  // its next item. If there's no parent popup menu , do nothing.
+                  if (len > 2 && path[len - 3] instanceof JPopupMenu)
+                    {
+                      popup = ((JPopupMenu) path[len - 3]);
+                      next = findEnabledChild(popup.getSubElements(),
+                                              menu, dir);
+                      if (next != null && next != menu)
+                        {
+                          MenuElement[] newPath = new MenuElement[len - 1];
+                          System.arraycopy(path, 0, newPath, 0, len - 2);
+                          newPath[len - 2] = next;
+                          msm.setSelectedPath(newPath);
+                        }
+                    }
+                }
+            }
+          else
+            {
+              // Only select the next item.
+              MenuElement subs[] = path[len - 2].getSubElements();
+              MenuElement nextChild =
+                findEnabledChild(subs, path[len - 1], dir);
+              if (nextChild == null)
+                {
+                  nextChild = findEnabledChild(subs, -1, dir);
+                }
+              if (nextChild != null)
+                {
+                  path[len-1] = nextChild;
+                  msm.setSelectedPath(path);
+                }
+            }
+        }
+    }
+
+    private MenuElement findEnabledChild(MenuElement[] children,
+                                         MenuElement start, boolean dir)
+    {
+      MenuElement found = null;
+      for (int i = 0; i < children.length && found == null; i++)
+        {
+          if (children[i] == start)
+            {
+              found = findEnabledChild(children, i, dir);
+            }
+        }
+      return found;
+    }
+
+    /**
+     * Searches the next or previous enabled child menu element.
+     *
+     * @param children the children to search through
+     * @param start the index at which to start
+     * @param dir the direction (true == forward, false == backward)
+     *
+     * @return the found element or null
+     */
+    private MenuElement findEnabledChild(MenuElement[] children,
+                                         int start, boolean dir)
+    {
+      MenuElement result = null;
+      if (dir)
+        {
+          result = findNextEnabledChild(children, start + 1, children.length-1);
+          if (result == null)
+            result = findNextEnabledChild(children, 0, start - 1);
+        }
+      else
+        {
+          result = findPreviousEnabledChild(children, start - 1, 0);
+          if (result == null)
+            result = findPreviousEnabledChild(children, children.length-1,
+                                          start + 1);
+        }
+      return result;
+    }
+
+    /**
+     * Finds the next child element that is enabled and visible.
+     * 
+     * @param children the children to search through
+     * @param start the start index
+     * @param end the end index
+     *
+     * @return the found child, or null
+     */
+    private MenuElement findNextEnabledChild(MenuElement[] children, int start,
+                                             int end)
+    {
+      MenuElement found = null;
+      for (int i = start; i <= end && found == null; i++)
+        {
+          if (children[i] != null)
+            {
+              Component comp = children[i].getComponent();
+              if (comp != null && comp.isEnabled() && comp.isVisible())
+                {
+                  found = children[i];
+                }
+            }
+        }
+      return found;
+    }
+
+    /**
+     * Finds the previous child element that is enabled and visible.
+     * 
+     * @param children the children to search through
+     * @param start the start index
+     * @param end the end index
+     *
+     * @return the found child, or null
+     */
+    private MenuElement findPreviousEnabledChild(MenuElement[] children,
+                                                 int start, int end)
+    {
+      MenuElement found = null;
+      for (int i = start; i >= end && found == null; i--)
+        {
+          if (children[i] != null)
+            {
+              Component comp = children[i].getComponent();
+              if (comp != null && comp.isEnabled() && comp.isVisible())
+                {
+                  found = children[i];
+                }
+            }
+        }
+      return found;
+    }
+
+    /**
+     * Navigates to the parent or child menu item.
+     *
+     * @param selectChild <code>true</code>: navigate to child,
+     *        <code>false</code>: navigate to parent
+     */
+    private void navigateParentChild(boolean selectChild)
+    {
+      MenuSelectionManager msm = MenuSelectionManager.defaultManager();
+      MenuElement path[] = msm.getSelectedPath();
+      int len = path.length;
+
+      if (selectChild)
+        {
+          if (len > 0 && path[len - 1] instanceof JMenu
+              && ! ((JMenu) path[len-1]).isTopLevelMenu())
+            {
+              // We have a submenu, open it.
+              JMenu menu = (JMenu) path[len - 1];
+              JPopupMenu popup = menu.getPopupMenu();
+              MenuElement[] subs = popup.getSubElements();
+              MenuElement item = findEnabledChild(subs, -1, true);
+              MenuElement[] newPath;
+
+              if (item == null)
+                {
+                  newPath = new MenuElement[len + 1];
+                }
+              else
+                {
+                  newPath = new MenuElement[len + 2];
+                  newPath[len + 1] = item;
+                }
+              System.arraycopy(path, 0, newPath, 0, len);
+              newPath[len] = popup;
+              msm.setSelectedPath(newPath);
+              return;
+            }
+        }
+      else
+        {
+          int popupIndex = len-1;
+          if (len > 2 
+              && (path[popupIndex] instanceof JPopupMenu
+                  || path[--popupIndex] instanceof JPopupMenu)
+                  && ! ((JMenu) path[popupIndex - 1]).isTopLevelMenu())
+            {
+              // We have a submenu, close it.
+              MenuElement newPath[] = new MenuElement[popupIndex];
+              System.arraycopy(path, 0, newPath, 0, popupIndex);
+              msm.setSelectedPath(newPath);
+              return;
+            }
+        }
+
+      // If we got here, we have not selected a child or parent.
+      // Check if we have a toplevel menu selected. If so, then select
+      // another one.
+      if (len > 1 && path[0] instanceof JMenuBar)
+        {
+          MenuElement currentMenu = path[1];
+          MenuElement nextMenu = findEnabledChild(path[0].getSubElements(),
+                                                  currentMenu, selectChild);
+
+          if (nextMenu != null && nextMenu != currentMenu)
+            {
+              MenuElement newSelection[];
+              if (len == 2)
+                {
+                  // Menu is selected but its popup not shown.
+                  newSelection = new MenuElement[2];
+                  newSelection[0] = path[0];
+                  newSelection[1] = nextMenu;
+                }
+              else
+                {
+                  // Menu is selected and its popup is shown.
+                  newSelection = new MenuElement[3];
+                  newSelection[0] = path[0];
+                  newSelection[1] = nextMenu;
+                  newSelection[2] = ((JMenu) nextMenu).getPopupMenu();
+                }
+              msm.setSelectedPath(newSelection);
+            }
+        }
+    }
+
+    /**
+     * Handles cancel requests (ESC key).
+     */
+    private void cancel()
+    {
+      // Fire popup menu cancelled event. Unfortunately the
+      // firePopupMenuCancelled() is protected in JPopupMenu so we work
+      // around this limitation by fetching the listeners and notifying them
+      // directly.
+      JPopupMenu lastPopup = (JPopupMenu) getLastPopup();
+      EventListener[] ll = lastPopup.getListeners(PopupMenuListener.class);
+      for (int i = 0; i < ll.length; i++)
+        {
+          PopupMenuEvent ev = new PopupMenuEvent(lastPopup);
+          ((PopupMenuListener) ll[i]).popupMenuCanceled(ev);
+        }
+
+      // Close the last popup or the whole selection if there's only one
+      // popup left.
+      MenuSelectionManager msm = MenuSelectionManager.defaultManager();
+      MenuElement path[] = msm.getSelectedPath();
+      if(path.length > 4)
+        {
+          MenuElement newPath[] = new MenuElement[path.length - 2];
+          System.arraycopy(path,0,newPath,0,path.length-2);
+          MenuSelectionManager.defaultManager().setSelectedPath(newPath);
+        }
+      else
+          msm.clearSelectedPath();
+    }
+
+    /**
+     * Returns the last popup menu in the current selection or null.
+     *
+     * @return the last popup menu in the current selection or null
+     */
+    private JPopupMenu getLastPopup()
+    {
+      MenuSelectionManager msm = MenuSelectionManager.defaultManager();
+      MenuElement[] p = msm.getSelectedPath();
+      JPopupMenu popup = null;
+      for(int i = p.length - 1; popup == null && i >= 0; i--)
+        {
+          if (p[i] instanceof JPopupMenu)
+            popup = (JPopupMenu) p[i];
+        }
+      return popup;
+    }
+
+    /**
+     * Handles ENTER key requests. This normally opens submenus on JMenu
+     * items, or activates the menu item as if it's been clicked on it.
+     */
+    private void doReturn()
+    {
+      KeyboardFocusManager fmgr =
+        KeyboardFocusManager.getCurrentKeyboardFocusManager();
+      Component focusOwner = fmgr.getFocusOwner();
+      if((focusOwner == null || (focusOwner instanceof JRootPane)))
+        {
+          MenuSelectionManager msm = MenuSelectionManager.defaultManager();
+          MenuElement path[] = msm.getSelectedPath();
+          MenuElement lastElement;
+          if(path.length > 0)
+            {
+              lastElement = path[path.length - 1];
+              if(lastElement instanceof JMenu)
+                {
+                  MenuElement newPath[] = new MenuElement[path.length + 1];
+                  System.arraycopy(path,0,newPath,0,path.length);
+                  newPath[path.length] = ((JMenu) lastElement).getPopupMenu();
+                  msm.setSelectedPath(newPath);
+                }
+              else if(lastElement instanceof JMenuItem)
+                {
+                  JMenuItem mi = (JMenuItem)lastElement;
+                  if (mi.getUI() instanceof BasicMenuItemUI)
+                    {
+                      ((BasicMenuItemUI)mi.getUI()).doClick(msm);
+                    }
+                  else
+                    {
+                      msm.clearSelectedPath();
+                      mi.doClick(0);
+                    }
+                }
+            }
+        }
+    }
+  }
+
+  /**
+   * Installs keyboard actions when a popup is opened, and uninstalls the
+   * keyboard actions when closed. This listens on the default
+   * MenuSelectionManager.
+   */
+  private class KeyboardHelper
+    implements ChangeListener
+  {
+    private MenuElement[] lastSelectedPath = new MenuElement[0];
+    private Component lastFocused;
+    private JRootPane invokerRootPane;
+
+    public void stateChanged(ChangeEvent event)
+    {
+      MenuSelectionManager msm = (MenuSelectionManager) event.getSource();
+      MenuElement[] p = msm.getSelectedPath();
+      JPopupMenu popup = getActivePopup(p);
+      if (popup == null || popup.isFocusable())
+        {
+          if (lastSelectedPath.length != 0 && p.length != 0 )
+            {
+              if (! invokerEquals(p[0], lastSelectedPath[0]))
+                {
+                  uninstallKeyboardActionsImpl();
+                  lastSelectedPath = new MenuElement[0];
+                }
+            }
+
+          if (lastSelectedPath.length == 0 && p.length > 0)
+            {
+              JComponent invoker;
+              if (popup == null)
+                {
+                  if (p.length == 2 && p[0] instanceof JMenuBar
+                      && p[1] instanceof JMenu)
+                    {
+                      // A menu has been selected but not opened.
+                      invoker = (JComponent)p[1];
+                      popup = ((JMenu)invoker).getPopupMenu();
+                    }
+                  else
+                    {
+                      return;
+                    }
+                }
+              else
+                {
+                Component c = popup.getInvoker();
+                if(c instanceof JFrame)
+                  {
+                    invoker = ((JFrame) c).getRootPane();
+                  }
+                else if(c instanceof JApplet)
+                  {
+                    invoker = ((JApplet) c).getRootPane();
+                  }
+                else
+                  {
+                    while (!(c instanceof JComponent))
+                      {
+                        if (c == null)
+                          {
+                            return;
+                          }
+                        c = c.getParent();
+                      }
+                    invoker = (JComponent)c;
+                  }
+                }
+
+              // Remember current focus owner.
+              lastFocused = KeyboardFocusManager.
+                             getCurrentKeyboardFocusManager().getFocusOwner();
+
+              // Install keybindings used for menu navigation.
+              invokerRootPane = SwingUtilities.getRootPane(invoker);
+              if (invokerRootPane != null)
+                {
+                  invokerRootPane.requestFocus(true);
+                  installKeyboardActionsImpl();
+                }
+            }
+          else if (lastSelectedPath.length != 0 && p.length == 0)
+            {
+              // menu hidden -- return focus to where it had been before
+              // and uninstall menu keybindings
+              uninstallKeyboardActionsImpl();
+            }
+        }
+
+      // Remember the last path selected
+      lastSelectedPath = p;
+    }
+
+    private JPopupMenu getActivePopup(MenuElement[] path)
+    {
+      JPopupMenu active = null;
+      for (int i = path.length - 1; i >= 0 && active == null; i--)
+        {
+          MenuElement elem = path[i];
+          if (elem instanceof JPopupMenu)
+            {
+              active = (JPopupMenu) elem;
+            }
+        }
+      return active;
+    }
+
+    private boolean invokerEquals(MenuElement el1, MenuElement el2)
+    {
+      Component invoker1 = el1.getComponent();
+      Component invoker2 = el2.getComponent();
+      if (invoker1 instanceof JPopupMenu)
+        invoker1 = ((JPopupMenu) invoker1).getInvoker();
+      if (invoker2 instanceof JPopupMenu)
+        invoker2 = ((JPopupMenu) invoker2).getInvoker();
+      return invoker1 == invoker2;
+    }
+  }
+
   /* popupMenu for which this UI delegate is for*/
   protected JPopupMenu popupMenu;
 
   /* PopupMenuListener listens to popup menu events fired by JPopupMenu*/
   private transient PopupMenuListener popupMenuListener;
 
   /* ComponentListener listening to popupMenu's invoker.
    * This is package-private to avoid an accessor method.  */
   TopWindowListener topWindowListener;
 
   /**
+   * Counts how many popup menus are handled by this UI or a subclass.
+   * This is used to install a KeyboardHelper on the MenuSelectionManager
+   * for the first popup, and uninstall this same KeyboardHelper when the
+   * last popup is uninstalled.
+   */
+  private static int numPopups;
+
+  /**
+   * This is the KeyboardHelper that listens on the MenuSelectionManager.
+   */
+  private static KeyboardHelper keyboardHelper;
+
+  /**
    * Creates a new BasicPopupMenuUI object.
    */
   public BasicPopupMenuUI()
   {
     popupMenuListener = new PopupMenuHandler();
     topWindowListener = new TopWindowListener();
   }
 
   /**
    * Factory method to create a BasicPopupMenuUI for the given [EMAIL PROTECTED]
    * JComponent}, which should be a [EMAIL PROTECTED] JMenuItem}.
    *
@@ -97,31 +651,42 @@
   }
 
   /**
    * Installs and initializes all fields for this UI delegate. Any properties
    * of the UI that need to be initialized and/or set to defaults will be
    * done now. It will also install any listeners necessary.
    *
    * @param c The [EMAIL PROTECTED] JComponent} that is having this UI installed.
    */
   public void installUI(JComponent c)
   {
     super.installUI(c);
+
+    // Install KeyboardHelper when the first popup is initialized.
+    if (numPopups == 0)
+      {
+        keyboardHelper = new KeyboardHelper();
+        MenuSelectionManager msm = MenuSelectionManager.defaultManager();
+        msm.addChangeListener(keyboardHelper);
+      }
+    numPopups++;
+
     popupMenu = (JPopupMenu) c;
     popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS));
     popupMenu.setBorderPainted(true);
     JPopupMenu.setDefaultLightWeightPopupEnabled(true);
 
     installDefaults();
     installListeners();
+    installKeyboardActions();
   }
 
   /**
    * This method installs the defaults that are defined in  the Basic look
    * and feel for this [EMAIL PROTECTED] JPopupMenu}.
    */
   public void installDefaults()
   {
     LookAndFeel.installColorsAndFont(popupMenu, "PopupMenu.background",
                                      "PopupMenu.foreground", "PopupMenu.font");
     LookAndFeel.installBorder(popupMenu, "PopupMenu.border");
     popupMenu.setOpaque(true);
@@ -130,41 +695,119 @@
   /**
    * This method installs the listeners for the [EMAIL PROTECTED] JMenuItem}.
    */
   protected void installListeners()
   {
     popupMenu.addPopupMenuListener(popupMenuListener);
   }
 
   /**
    * This method installs the keyboard actions for this [EMAIL PROTECTED] JPopupMenu}.
    */
   protected void installKeyboardActions()
-    throws NotImplementedException
   {
-    // FIXME: Need to implement
+    // We can't install the keyboard actions here, because then all
+    // popup menus would have their actions registered in the KeyboardManager.
+    // So we install it when the popup menu is opened, and uninstall it
+    // when it's closed. This is done in the KeyboardHelper class.
+    // Install InputMap.
+  }
+
+  /**
+   * Called by the KeyboardHandler when a popup is made visible.
+   */
+  void installKeyboardActionsImpl()
+  {
+    Object[] bindings;
+    if (popupMenu.getComponentOrientation().isLeftToRight())
+      {
+        bindings = (Object[])
+             SharedUIDefaults.get("PopupMenu.selectedWindowInputMapBindings");
+      }
+    else
+      {
+        bindings = (Object[]) SharedUIDefaults.get
+                      ("PopupMenu.selectedWindowInputMapBindings.RightToLeft");
+      }
+    InputMap inputMap = LookAndFeel.makeComponentInputMap(popupMenu, bindings);
+    SwingUtilities.replaceUIInputMap(popupMenu,
+                                     JComponent.WHEN_IN_FOCUSED_WINDOW,
+                                     inputMap);
+
+    // Install ActionMap.
+    SwingUtilities.replaceUIActionMap(popupMenu, getActionMap());
+  }
+
+  /**
+   * Creates and returns the shared action map for JTrees.
+   *
+   * @return the shared action map for JTrees
+   */
+  private ActionMap getActionMap()
+  {
+    ActionMap am = (ActionMap) UIManager.get("PopupMenu.actionMap");
+    if (am == null)
+      {
+        am = createDefaultActions();
+        UIManager.getLookAndFeelDefaults().put("PopupMenu.actionMap", am);
+      }
+    return am;
+  }
+
+  /**
+   * Creates the default actions when there are none specified by the L&F.
+   *
+   * @return the default actions
+   */
+  private ActionMap createDefaultActions()
+  {
+    ActionMapUIResource am = new ActionMapUIResource();
+    Action action = new NavigateAction("selectNext");
+    am.put(action.getValue(Action.NAME), action);
+    action = new NavigateAction("selectPrevious");
+    am.put(action.getValue(Action.NAME), action);
+    action = new NavigateAction("selectParent");
+    am.put(action.getValue(Action.NAME), action);
+    action = new NavigateAction("selectChild");
+    am.put(action.getValue(Action.NAME), action);
+    action = new NavigateAction("return");
+    am.put(action.getValue(Action.NAME), action);
+    action = new NavigateAction("cancel");
+    am.put(action.getValue(Action.NAME), action);
+    
+    return am;
   }
 
   /**
    * Performs the opposite of installUI. Any properties or resources that need
    * to be cleaned up will be done now. It will also uninstall any listeners
    * it has. In addition, any properties of this UI will be nulled.
    *
    * @param c The [EMAIL PROTECTED] JComponent} that is having this UI uninstalled.
    */
   public void uninstallUI(JComponent c)
   {
     uninstallListeners();
     uninstallDefaults();
+    uninstallKeyboardActions();
     popupMenu = null;
+
+    // Install KeyboardHelper when the first popup is initialized.
+    numPopups--;
+    if (numPopups == 0)
+      {
+        MenuSelectionManager msm = MenuSelectionManager.defaultManager();
+        msm.removeChangeListener(keyboardHelper);
+      }
+
   }
 
   /**
    * This method uninstalls the defaults and sets any objects created during
    * install to null
    */
   protected void uninstallDefaults()
   {
     popupMenu.setBackground(null);
     popupMenu.setBorder(null);
     popupMenu.setFont(null);
     popupMenu.setForeground(null);
@@ -173,27 +816,40 @@
   /**
    * Unregisters all the listeners that this UI delegate was using.
    */
   protected void uninstallListeners()
   {
     popupMenu.removePopupMenuListener(popupMenuListener);
   }
 
   /**
    * Uninstalls any keyboard actions.
    */
   protected void uninstallKeyboardActions()
-    throws NotImplementedException
   {
-    // FIXME: Need to implement
+    // We can't install the keyboard actions here, because then all
+    // popup menus would have their actions registered in the KeyboardManager.
+    // So we install it when the popup menu is opened, and uninstall it
+    // when it's closed. This is done in the KeyboardHelper class.
+    // Install InputMap.
+  }
+
+  /**
+   * Called by the KeyboardHandler when a popup is made invisible.
+   */
+  void uninstallKeyboardActionsImpl()
+  {
+    SwingUtilities.replaceUIInputMap(popupMenu,
+                                     JComponent.WHEN_IN_FOCUSED_WINDOW, null);
+    SwingUtilities.replaceUIActionMap(popupMenu, null);
   }
 
   /**
    * This method returns the minimum size of the JPopupMenu.
    *
    * @param c The JComponent to find a size for.
    *
    * @return The minimum size.
    */
   public Dimension getMinimumSize(JComponent c)
   {
     return null;

Reply via email to