001    /* BasicPopupMenuUI.java
002       Copyright (C) 2002, 2004, 2005 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    package javax.swing.plaf.basic;
039    
040    import java.awt.Component;
041    import java.awt.Dimension;
042    import java.awt.KeyboardFocusManager;
043    import java.awt.event.ActionEvent;
044    import java.awt.event.ComponentEvent;
045    import java.awt.event.ComponentListener;
046    import java.awt.event.MouseEvent;
047    import java.util.EventListener;
048    
049    import javax.swing.AbstractAction;
050    import javax.swing.Action;
051    import javax.swing.ActionMap;
052    import javax.swing.BoxLayout;
053    import javax.swing.InputMap;
054    import javax.swing.JApplet;
055    import javax.swing.JComponent;
056    import javax.swing.JFrame;
057    import javax.swing.JMenu;
058    import javax.swing.JMenuBar;
059    import javax.swing.JMenuItem;
060    import javax.swing.JPopupMenu;
061    import javax.swing.JRootPane;
062    import javax.swing.LookAndFeel;
063    import javax.swing.MenuElement;
064    import javax.swing.MenuSelectionManager;
065    import javax.swing.SwingUtilities;
066    import javax.swing.UIManager;
067    import javax.swing.event.ChangeEvent;
068    import javax.swing.event.ChangeListener;
069    import javax.swing.event.PopupMenuEvent;
070    import javax.swing.event.PopupMenuListener;
071    import javax.swing.plaf.ActionMapUIResource;
072    import javax.swing.plaf.ComponentUI;
073    import javax.swing.plaf.PopupMenuUI;
074    
075    /**
076     * UI Delegate for JPopupMenu
077     */
078    public class BasicPopupMenuUI extends PopupMenuUI
079    {
080      /**
081       * Handles keyboard navigation through menus.
082       */
083      private static class NavigateAction
084        extends AbstractAction
085      {
086    
087        /**
088         * Creates a new NavigateAction instance.
089         *
090         * @param name the name of the action
091         */
092        NavigateAction(String name)
093        {
094          super(name);
095        }
096    
097        /**
098         * Actually performs the action.
099         */
100        public void actionPerformed(ActionEvent event)
101        {
102          String name = (String) getValue(Action.NAME);
103          if (name.equals("selectNext"))
104            navigateNextPrevious(true);
105          else if (name.equals("selectPrevious"))
106            navigateNextPrevious(false);
107          else if (name.equals("selectChild"))
108            navigateParentChild(true);
109          else if (name.equals("selectParent"))
110            navigateParentChild(false);
111          else if (name.equals("cancel"))
112            cancel();
113          else if (name.equals("return"))
114            doReturn();
115          else
116            assert false : "Must not reach here";
117        }
118    
119        /**
120         * Navigates to the next or previous menu item.
121         *
122         * @param dir <code>true</code>: navigate to next, <code>false</code>:
123         *        navigate to previous
124         */
125        private void navigateNextPrevious(boolean dir)
126        {
127          MenuSelectionManager msm = MenuSelectionManager.defaultManager();
128          MenuElement path[] = msm.getSelectedPath();
129          int len = path.length;
130          if (len >= 2)
131            {
132    
133              if (path[0] instanceof JMenuBar &&
134                  path[1] instanceof JMenu && len == 2)
135                {
136    
137                  // A toplevel menu is selected, but its popup not yet shown.
138                  // Show the popup and select the first item
139                  JPopupMenu popup = ((JMenu)path[1]).getPopupMenu();
140                  MenuElement next =
141                    findEnabledChild(popup.getSubElements(), -1, true);
142                  MenuElement[] newPath;
143    
144                  if (next != null)
145                    {
146                      newPath = new MenuElement[4];
147                      newPath[3] = next;
148                    }
149                  else
150                    {
151                      // Menu has no enabled items, show the popup anyway.
152                      newPath = new MenuElement[3];
153                    }
154                  System.arraycopy(path, 0, newPath, 0, 2);
155                  newPath[2] = popup;
156                  msm.setSelectedPath(newPath);
157                }
158              else if (path[len - 1] instanceof JPopupMenu &&
159                       path[len - 2] instanceof JMenu)
160                {
161                  // Select next item in already shown popup menu.
162                  JMenu menu = (JMenu) path[len - 2];
163                  JPopupMenu popup = menu.getPopupMenu();
164                  MenuElement next =
165                    findEnabledChild(popup.getSubElements(), -1, dir);
166    
167                  if (next != null)
168                    {
169                      MenuElement[] newPath = new MenuElement[len + 1];
170                      System.arraycopy(path, 0, newPath, 0, len);
171                      newPath[len] = next;
172                      msm.setSelectedPath(newPath);
173                    }
174                  else
175                    {
176                      // All items in the popup are disabled.
177                      // Find the parent popup menu and select
178                      // its next item. If there's no parent popup menu , do nothing.
179                      if (len > 2 && path[len - 3] instanceof JPopupMenu)
180                        {
181                          popup = ((JPopupMenu) path[len - 3]);
182                          next = findEnabledChild(popup.getSubElements(),
183                                                  menu, dir);
184                          if (next != null && next != menu)
185                            {
186                              MenuElement[] newPath = new MenuElement[len - 1];
187                              System.arraycopy(path, 0, newPath, 0, len - 2);
188                              newPath[len - 2] = next;
189                              msm.setSelectedPath(newPath);
190                            }
191                        }
192                    }
193                }
194              else
195                {
196                  // Only select the next item.
197                  MenuElement subs[] = path[len - 2].getSubElements();
198                  MenuElement nextChild =
199                    findEnabledChild(subs, path[len - 1], dir);
200                  if (nextChild == null)
201                    {
202                      nextChild = findEnabledChild(subs, -1, dir);
203                    }
204                  if (nextChild != null)
205                    {
206                      path[len-1] = nextChild;
207                      msm.setSelectedPath(path);
208                    }
209                }
210            }
211        }
212    
213        private MenuElement findEnabledChild(MenuElement[] children,
214                                             MenuElement start, boolean dir)
215        {
216          MenuElement found = null;
217          for (int i = 0; i < children.length && found == null; i++)
218            {
219              if (children[i] == start)
220                {
221                  found = findEnabledChild(children, i, dir);
222                }
223            }
224          return found;
225        }
226    
227        /**
228         * Searches the next or previous enabled child menu element.
229         *
230         * @param children the children to search through
231         * @param start the index at which to start
232         * @param dir the direction (true == forward, false == backward)
233         *
234         * @return the found element or null
235         */
236        private MenuElement findEnabledChild(MenuElement[] children,
237                                             int start, boolean dir)
238        {
239          MenuElement result = null;
240          if (dir)
241            {
242              result = findNextEnabledChild(children, start + 1, children.length-1);
243              if (result == null)
244                result = findNextEnabledChild(children, 0, start - 1);
245            }
246          else
247            {
248              result = findPreviousEnabledChild(children, start - 1, 0);
249              if (result == null)
250                result = findPreviousEnabledChild(children, children.length-1,
251                                              start + 1);
252            }
253          return result;
254        }
255    
256        /**
257         * Finds the next child element that is enabled and visible.
258         *
259         * @param children the children to search through
260         * @param start the start index
261         * @param end the end index
262         *
263         * @return the found child, or null
264         */
265        private MenuElement findNextEnabledChild(MenuElement[] children, int start,
266                                                 int end)
267        {
268          MenuElement found = null;
269          for (int i = start; i <= end && found == null; i++)
270            {
271              if (children[i] != null)
272                {
273                  Component comp = children[i].getComponent();
274                  if (comp != null && comp.isEnabled() && comp.isVisible())
275                    {
276                      found = children[i];
277                    }
278                }
279            }
280          return found;
281        }
282    
283        /**
284         * Finds the previous child element that is enabled and visible.
285         *
286         * @param children the children to search through
287         * @param start the start index
288         * @param end the end index
289         *
290         * @return the found child, or null
291         */
292        private MenuElement findPreviousEnabledChild(MenuElement[] children,
293                                                     int start, int end)
294        {
295          MenuElement found = null;
296          for (int i = start; i >= end && found == null; i--)
297            {
298              if (children[i] != null)
299                {
300                  Component comp = children[i].getComponent();
301                  if (comp != null && comp.isEnabled() && comp.isVisible())
302                    {
303                      found = children[i];
304                    }
305                }
306            }
307          return found;
308        }
309    
310        /**
311         * Navigates to the parent or child menu item.
312         *
313         * @param selectChild <code>true</code>: navigate to child,
314         *        <code>false</code>: navigate to parent
315         */
316        private void navigateParentChild(boolean selectChild)
317        {
318          MenuSelectionManager msm = MenuSelectionManager.defaultManager();
319          MenuElement path[] = msm.getSelectedPath();
320          int len = path.length;
321    
322          if (selectChild)
323            {
324              if (len > 0 && path[len - 1] instanceof JMenu
325                  && ! ((JMenu) path[len-1]).isTopLevelMenu())
326                {
327                  // We have a submenu, open it.
328                  JMenu menu = (JMenu) path[len - 1];
329                  JPopupMenu popup = menu.getPopupMenu();
330                  MenuElement[] subs = popup.getSubElements();
331                  MenuElement item = findEnabledChild(subs, -1, true);
332                  MenuElement[] newPath;
333    
334                  if (item == null)
335                    {
336                      newPath = new MenuElement[len + 1];
337                    }
338                  else
339                    {
340                      newPath = new MenuElement[len + 2];
341                      newPath[len + 1] = item;
342                    }
343                  System.arraycopy(path, 0, newPath, 0, len);
344                  newPath[len] = popup;
345                  msm.setSelectedPath(newPath);
346                  return;
347                }
348            }
349          else
350            {
351              int popupIndex = len-1;
352              if (len > 2
353                  && (path[popupIndex] instanceof JPopupMenu
354                      || path[--popupIndex] instanceof JPopupMenu)
355                      && ! ((JMenu) path[popupIndex - 1]).isTopLevelMenu())
356                {
357                  // We have a submenu, close it.
358                  MenuElement newPath[] = new MenuElement[popupIndex];
359                  System.arraycopy(path, 0, newPath, 0, popupIndex);
360                  msm.setSelectedPath(newPath);
361                  return;
362                }
363            }
364    
365          // If we got here, we have not selected a child or parent.
366          // Check if we have a toplevel menu selected. If so, then select
367          // another one.
368          if (len > 1 && path[0] instanceof JMenuBar)
369            {
370              MenuElement currentMenu = path[1];
371              MenuElement nextMenu = findEnabledChild(path[0].getSubElements(),
372                                                      currentMenu, selectChild);
373    
374              if (nextMenu != null && nextMenu != currentMenu)
375                {
376                  MenuElement newSelection[];
377                  if (len == 2)
378                    {
379                      // Menu is selected but its popup not shown.
380                      newSelection = new MenuElement[2];
381                      newSelection[0] = path[0];
382                      newSelection[1] = nextMenu;
383                    }
384                  else
385                    {
386                      // Menu is selected and its popup is shown.
387                      newSelection = new MenuElement[3];
388                      newSelection[0] = path[0];
389                      newSelection[1] = nextMenu;
390                      newSelection[2] = ((JMenu) nextMenu).getPopupMenu();
391                    }
392                  msm.setSelectedPath(newSelection);
393                }
394            }
395        }
396    
397        /**
398         * Handles cancel requests (ESC key).
399         */
400        private void cancel()
401        {
402          // Fire popup menu cancelled event. Unfortunately the
403          // firePopupMenuCancelled() is protected in JPopupMenu so we work
404          // around this limitation by fetching the listeners and notifying them
405          // directly.
406          JPopupMenu lastPopup = (JPopupMenu) getLastPopup();
407          EventListener[] ll = lastPopup.getListeners(PopupMenuListener.class);
408          for (int i = 0; i < ll.length; i++)
409            {
410              PopupMenuEvent ev = new PopupMenuEvent(lastPopup);
411              ((PopupMenuListener) ll[i]).popupMenuCanceled(ev);
412            }
413    
414          // Close the last popup or the whole selection if there's only one
415          // popup left.
416          MenuSelectionManager msm = MenuSelectionManager.defaultManager();
417          MenuElement path[] = msm.getSelectedPath();
418          if(path.length > 4)
419            {
420              MenuElement newPath[] = new MenuElement[path.length - 2];
421              System.arraycopy(path,0,newPath,0,path.length-2);
422              MenuSelectionManager.defaultManager().setSelectedPath(newPath);
423            }
424          else
425              msm.clearSelectedPath();
426        }
427    
428        /**
429         * Returns the last popup menu in the current selection or null.
430         *
431         * @return the last popup menu in the current selection or null
432         */
433        private JPopupMenu getLastPopup()
434        {
435          MenuSelectionManager msm = MenuSelectionManager.defaultManager();
436          MenuElement[] p = msm.getSelectedPath();
437          JPopupMenu popup = null;
438          for(int i = p.length - 1; popup == null && i >= 0; i--)
439            {
440              if (p[i] instanceof JPopupMenu)
441                popup = (JPopupMenu) p[i];
442            }
443          return popup;
444        }
445    
446        /**
447         * Handles ENTER key requests. This normally opens submenus on JMenu
448         * items, or activates the menu item as if it's been clicked on it.
449         */
450        private void doReturn()
451        {
452          KeyboardFocusManager fmgr =
453            KeyboardFocusManager.getCurrentKeyboardFocusManager();
454          Component focusOwner = fmgr.getFocusOwner();
455          if((focusOwner == null || (focusOwner instanceof JRootPane)))
456            {
457              MenuSelectionManager msm = MenuSelectionManager.defaultManager();
458              MenuElement path[] = msm.getSelectedPath();
459              MenuElement lastElement;
460              if(path.length > 0)
461                {
462                  lastElement = path[path.length - 1];
463                  if(lastElement instanceof JMenu)
464                    {
465                      MenuElement newPath[] = new MenuElement[path.length + 1];
466                      System.arraycopy(path,0,newPath,0,path.length);
467                      newPath[path.length] = ((JMenu) lastElement).getPopupMenu();
468                      msm.setSelectedPath(newPath);
469                    }
470                  else if(lastElement instanceof JMenuItem)
471                    {
472                      JMenuItem mi = (JMenuItem)lastElement;
473                      if (mi.getUI() instanceof BasicMenuItemUI)
474                        {
475                          ((BasicMenuItemUI)mi.getUI()).doClick(msm);
476                        }
477                      else
478                        {
479                          msm.clearSelectedPath();
480                          mi.doClick(0);
481                        }
482                    }
483                }
484            }
485        }
486      }
487    
488      /**
489       * Installs keyboard actions when a popup is opened, and uninstalls the
490       * keyboard actions when closed. This listens on the default
491       * MenuSelectionManager.
492       */
493      private class KeyboardHelper
494        implements ChangeListener
495      {
496        private MenuElement[] lastSelectedPath = new MenuElement[0];
497        private Component lastFocused;
498        private JRootPane invokerRootPane;
499    
500        public void stateChanged(ChangeEvent event)
501        {
502          MenuSelectionManager msm = (MenuSelectionManager) event.getSource();
503          MenuElement[] p = msm.getSelectedPath();
504          JPopupMenu popup = getActivePopup(p);
505          if (popup == null || popup.isFocusable())
506            {
507              if (lastSelectedPath.length != 0 && p.length != 0 )
508                {
509                  if (! invokerEquals(p[0], lastSelectedPath[0]))
510                    {
511                      uninstallKeyboardActionsImpl();
512                      lastSelectedPath = new MenuElement[0];
513                    }
514                }
515    
516              if (lastSelectedPath.length == 0 && p.length > 0)
517                {
518                  JComponent invoker;
519                  if (popup == null)
520                    {
521                      if (p.length == 2 && p[0] instanceof JMenuBar
522                          && p[1] instanceof JMenu)
523                        {
524                          // A menu has been selected but not opened.
525                          invoker = (JComponent)p[1];
526                          popup = ((JMenu)invoker).getPopupMenu();
527                        }
528                      else
529                        {
530                          return;
531                        }
532                    }
533                  else
534                    {
535                    Component c = popup.getInvoker();
536                    if(c instanceof JFrame)
537                      {
538                        invoker = ((JFrame) c).getRootPane();
539                      }
540                    else if(c instanceof JApplet)
541                      {
542                        invoker = ((JApplet) c).getRootPane();
543                      }
544                    else
545                      {
546                        while (!(c instanceof JComponent))
547                          {
548                            if (c == null)
549                              {
550                                return;
551                              }
552                            c = c.getParent();
553                          }
554                        invoker = (JComponent)c;
555                      }
556                    }
557    
558                  // Remember current focus owner.
559                  lastFocused = KeyboardFocusManager.
560                                 getCurrentKeyboardFocusManager().getFocusOwner();
561    
562                  // Install keybindings used for menu navigation.
563                  invokerRootPane = SwingUtilities.getRootPane(invoker);
564                  if (invokerRootPane != null)
565                    {
566                      invokerRootPane.requestFocus(true);
567                      installKeyboardActionsImpl();
568                    }
569                }
570              else if (lastSelectedPath.length != 0 && p.length == 0)
571                {
572                  // menu hidden -- return focus to where it had been before
573                  // and uninstall menu keybindings
574                  uninstallKeyboardActionsImpl();
575                }
576            }
577    
578          // Remember the last path selected
579          lastSelectedPath = p;
580        }
581    
582        private JPopupMenu getActivePopup(MenuElement[] path)
583        {
584          JPopupMenu active = null;
585          for (int i = path.length - 1; i >= 0 && active == null; i--)
586            {
587              MenuElement elem = path[i];
588              if (elem instanceof JPopupMenu)
589                {
590                  active = (JPopupMenu) elem;
591                }
592            }
593          return active;
594        }
595    
596        private boolean invokerEquals(MenuElement el1, MenuElement el2)
597        {
598          Component invoker1 = el1.getComponent();
599          Component invoker2 = el2.getComponent();
600          if (invoker1 instanceof JPopupMenu)
601            invoker1 = ((JPopupMenu) invoker1).getInvoker();
602          if (invoker2 instanceof JPopupMenu)
603            invoker2 = ((JPopupMenu) invoker2).getInvoker();
604          return invoker1 == invoker2;
605        }
606      }
607    
608      /* popupMenu for which this UI delegate is for*/
609      protected JPopupMenu popupMenu;
610    
611      /* PopupMenuListener listens to popup menu events fired by JPopupMenu*/
612      private transient PopupMenuListener popupMenuListener;
613    
614      /* ComponentListener listening to popupMenu's invoker.
615       * This is package-private to avoid an accessor method.  */
616      TopWindowListener topWindowListener;
617    
618      /**
619       * Counts how many popup menus are handled by this UI or a subclass.
620       * This is used to install a KeyboardHelper on the MenuSelectionManager
621       * for the first popup, and uninstall this same KeyboardHelper when the
622       * last popup is uninstalled.
623       */
624      private static int numPopups;
625    
626      /**
627       * This is the KeyboardHelper that listens on the MenuSelectionManager.
628       */
629      private static KeyboardHelper keyboardHelper;
630    
631      /**
632       * Creates a new BasicPopupMenuUI object.
633       */
634      public BasicPopupMenuUI()
635      {
636        popupMenuListener = new PopupMenuHandler();
637        topWindowListener = new TopWindowListener();
638      }
639    
640      /**
641       * Factory method to create a BasicPopupMenuUI for the given {@link
642       * JComponent}, which should be a {@link JMenuItem}.
643       *
644       * @param x The {@link JComponent} a UI is being created for.
645       *
646       * @return A BasicPopupMenuUI for the {@link JComponent}.
647       */
648      public static ComponentUI createUI(JComponent x)
649      {
650        return new BasicPopupMenuUI();
651      }
652    
653      /**
654       * Installs and initializes all fields for this UI delegate. Any properties
655       * of the UI that need to be initialized and/or set to defaults will be
656       * done now. It will also install any listeners necessary.
657       *
658       * @param c The {@link JComponent} that is having this UI installed.
659       */
660      public void installUI(JComponent c)
661      {
662        super.installUI(c);
663    
664        // Install KeyboardHelper when the first popup is initialized.
665        if (numPopups == 0)
666          {
667            keyboardHelper = new KeyboardHelper();
668            MenuSelectionManager msm = MenuSelectionManager.defaultManager();
669            msm.addChangeListener(keyboardHelper);
670          }
671        numPopups++;
672    
673        popupMenu = (JPopupMenu) c;
674        popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS));
675        popupMenu.setBorderPainted(true);
676        JPopupMenu.setDefaultLightWeightPopupEnabled(true);
677    
678        installDefaults();
679        installListeners();
680        installKeyboardActions();
681      }
682    
683      /**
684       * This method installs the defaults that are defined in  the Basic look
685       * and feel for this {@link JPopupMenu}.
686       */
687      public void installDefaults()
688      {
689        LookAndFeel.installColorsAndFont(popupMenu, "PopupMenu.background",
690                                         "PopupMenu.foreground", "PopupMenu.font");
691        LookAndFeel.installBorder(popupMenu, "PopupMenu.border");
692        popupMenu.setOpaque(true);
693      }
694    
695      /**
696       * This method installs the listeners for the {@link JMenuItem}.
697       */
698      protected void installListeners()
699      {
700        popupMenu.addPopupMenuListener(popupMenuListener);
701      }
702    
703      /**
704       * This method installs the keyboard actions for this {@link JPopupMenu}.
705       */
706      protected void installKeyboardActions()
707      {
708        // We can't install the keyboard actions here, because then all
709        // popup menus would have their actions registered in the KeyboardManager.
710        // So we install it when the popup menu is opened, and uninstall it
711        // when it's closed. This is done in the KeyboardHelper class.
712        // Install InputMap.
713      }
714    
715      /**
716       * Called by the KeyboardHandler when a popup is made visible.
717       */
718      void installKeyboardActionsImpl()
719      {
720        Object[] bindings;
721        if (popupMenu.getComponentOrientation().isLeftToRight())
722          {
723            bindings = (Object[])
724                 SharedUIDefaults.get("PopupMenu.selectedWindowInputMapBindings");
725          }
726        else
727          {
728            bindings = (Object[]) SharedUIDefaults.get
729                          ("PopupMenu.selectedWindowInputMapBindings.RightToLeft");
730          }
731        InputMap inputMap = LookAndFeel.makeComponentInputMap(popupMenu, bindings);
732        SwingUtilities.replaceUIInputMap(popupMenu,
733                                         JComponent.WHEN_IN_FOCUSED_WINDOW,
734                                         inputMap);
735    
736        // Install ActionMap.
737        SwingUtilities.replaceUIActionMap(popupMenu, getActionMap());
738      }
739    
740      /**
741       * Creates and returns the shared action map for JTrees.
742       *
743       * @return the shared action map for JTrees
744       */
745      private ActionMap getActionMap()
746      {
747        ActionMap am = (ActionMap) UIManager.get("PopupMenu.actionMap");
748        if (am == null)
749          {
750            am = createDefaultActions();
751            UIManager.getLookAndFeelDefaults().put("PopupMenu.actionMap", am);
752          }
753        return am;
754      }
755    
756      /**
757       * Creates the default actions when there are none specified by the L&F.
758       *
759       * @return the default actions
760       */
761      private ActionMap createDefaultActions()
762      {
763        ActionMapUIResource am = new ActionMapUIResource();
764        Action action = new NavigateAction("selectNext");
765        am.put(action.getValue(Action.NAME), action);
766        action = new NavigateAction("selectPrevious");
767        am.put(action.getValue(Action.NAME), action);
768        action = new NavigateAction("selectParent");
769        am.put(action.getValue(Action.NAME), action);
770        action = new NavigateAction("selectChild");
771        am.put(action.getValue(Action.NAME), action);
772        action = new NavigateAction("return");
773        am.put(action.getValue(Action.NAME), action);
774        action = new NavigateAction("cancel");
775        am.put(action.getValue(Action.NAME), action);
776    
777        return am;
778      }
779    
780      /**
781       * Performs the opposite of installUI. Any properties or resources that need
782       * to be cleaned up will be done now. It will also uninstall any listeners
783       * it has. In addition, any properties of this UI will be nulled.
784       *
785       * @param c The {@link JComponent} that is having this UI uninstalled.
786       */
787      public void uninstallUI(JComponent c)
788      {
789        uninstallListeners();
790        uninstallDefaults();
791        uninstallKeyboardActions();
792        popupMenu = null;
793    
794        // Install KeyboardHelper when the first popup is initialized.
795        numPopups--;
796        if (numPopups == 0)
797          {
798            MenuSelectionManager msm = MenuSelectionManager.defaultManager();
799            msm.removeChangeListener(keyboardHelper);
800          }
801    
802      }
803    
804      /**
805       * This method uninstalls the defaults and sets any objects created during
806       * install to null
807       */
808      protected void uninstallDefaults()
809      {
810        popupMenu.setBackground(null);
811        popupMenu.setBorder(null);
812        popupMenu.setFont(null);
813        popupMenu.setForeground(null);
814      }
815    
816      /**
817       * Unregisters all the listeners that this UI delegate was using.
818       */
819      protected void uninstallListeners()
820      {
821        popupMenu.removePopupMenuListener(popupMenuListener);
822      }
823    
824      /**
825       * Uninstalls any keyboard actions.
826       */
827      protected void uninstallKeyboardActions()
828      {
829        // We can't install the keyboard actions here, because then all
830        // popup menus would have their actions registered in the KeyboardManager.
831        // So we install it when the popup menu is opened, and uninstall it
832        // when it's closed. This is done in the KeyboardHelper class.
833        // Install InputMap.
834      }
835    
836      /**
837       * Called by the KeyboardHandler when a popup is made invisible.
838       */
839      void uninstallKeyboardActionsImpl()
840      {
841        SwingUtilities.replaceUIInputMap(popupMenu,
842                                         JComponent.WHEN_IN_FOCUSED_WINDOW, null);
843        SwingUtilities.replaceUIActionMap(popupMenu, null);
844      }
845    
846      /**
847       * This method returns the minimum size of the JPopupMenu.
848       *
849       * @param c The JComponent to find a size for.
850       *
851       * @return The minimum size.
852       */
853      public Dimension getMinimumSize(JComponent c)
854      {
855        return null;
856      }
857    
858      /**
859       * This method returns the preferred size of the JPopupMenu.
860       *
861       * @param c The JComponent to find a size for.
862       *
863       * @return The preferred size.
864       */
865      public Dimension getPreferredSize(JComponent c)
866      {
867        return null;
868      }
869    
870      /**
871       * This method returns the minimum size of the JPopupMenu.
872       *
873       * @param c The JComponent to find a size for.
874       *
875       * @return The minimum size.
876       */
877      public Dimension getMaximumSize(JComponent c)
878      {
879        return null;
880      }
881    
882      /**
883       * Return true if given mouse event is a platform popup trigger, and false
884       * otherwise
885       *
886       * @param e MouseEvent that is to be checked for popup trigger event
887       *
888       * @return true if given mouse event is a platform popup trigger, and false
889       *         otherwise
890       */
891      public boolean isPopupTrigger(MouseEvent e)
892      {
893        return false;
894      }
895    
896      /**
897       * This listener handles PopupMenuEvents fired by JPopupMenu
898       */
899      private class PopupMenuHandler implements PopupMenuListener
900      {
901        /**
902         * This method is invoked when JPopupMenu is cancelled.
903         *
904         * @param event the PopupMenuEvent
905         */
906        public void popupMenuCanceled(PopupMenuEvent event)
907        {
908          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
909          manager.clearSelectedPath();
910        }
911    
912        /**
913         * This method is invoked when JPopupMenu becomes invisible
914         *
915         * @param event the PopupMenuEvent
916         */
917        public void popupMenuWillBecomeInvisible(PopupMenuEvent event)
918        {
919          // remove listener that listens to component events fired
920          // by the top - level window that this popup belongs to.
921          Component invoker = popupMenu.getInvoker();
922          Component rootContainer = SwingUtilities.getRoot(invoker);
923          if (rootContainer != null)
924            rootContainer.removeComponentListener(topWindowListener);
925        }
926    
927        /**
928         * This method is invoked when JPopupMenu becomes visible
929         *
930         * @param event the PopupMenuEvent
931         */
932        public void popupMenuWillBecomeVisible(PopupMenuEvent event)
933        {
934          // Adds topWindowListener to top-level window to listener to
935          // ComponentEvents fired by it. We need to cancel this popup menu
936          // if topWindow to which this popup belongs was resized or moved.
937          Component invoker = popupMenu.getInvoker();
938          Component rootContainer = SwingUtilities.getRoot(invoker);
939          if (rootContainer != null)
940            rootContainer.addComponentListener(topWindowListener);
941    
942          // if this popup menu is a free floating popup menu,
943          // then by default its first element should be always selected when
944          // this popup menu becomes visible.
945          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
946    
947          if (manager.getSelectedPath().length == 0)
948            {
949              // Set selected path to point to the first item in the popup menu
950              MenuElement[] path = new MenuElement[2];
951              path[0] = popupMenu;
952              Component[] comps = popupMenu.getComponents();
953              if (comps.length != 0 && comps[0] instanceof MenuElement)
954                {
955                  path[1] = (MenuElement) comps[0];
956                  manager.setSelectedPath(path);
957                }
958            }
959        }
960      }
961    
962      /**
963       * ComponentListener that listens to Component Events fired by the top -
964       * level window to which popup menu belongs. If top-level window was
965       * resized, moved or hidded then popup menu will be hidded and selected
966       * path of current menu hierarchy will be set to null.
967       */
968      private class TopWindowListener implements ComponentListener
969      {
970        /**
971         * This method is invoked when top-level window is resized. This method
972         * closes current menu hierarchy.
973         *
974         * @param e The ComponentEvent
975         */
976        public void componentResized(ComponentEvent e)
977        {
978          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
979          manager.clearSelectedPath();
980        }
981    
982        /**
983         * This method is invoked when top-level window is moved. This method
984         * closes current menu hierarchy.
985         *
986         * @param e The ComponentEvent
987         */
988        public void componentMoved(ComponentEvent e)
989        {
990          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
991          manager.clearSelectedPath();
992        }
993    
994        /**
995         * This method is invoked when top-level window is shown This method
996         * does nothing by default.
997         *
998         * @param e The ComponentEvent
999         */
1000        public void componentShown(ComponentEvent e)
1001        {
1002          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1003          manager.clearSelectedPath();
1004        }
1005    
1006        /**
1007         * This method is invoked when top-level window is hidden This method
1008         * closes current menu hierarchy.
1009         *
1010         * @param e The ComponentEvent
1011         */
1012        public void componentHidden(ComponentEvent e)
1013        {
1014          MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1015          manager.clearSelectedPath();
1016        }
1017      }
1018    
1019    }