Source for javax.swing.plaf.basic.BasicPopupMenuUI

   1: /* BasicPopupMenuUI.java
   2:    Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   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)
   9: any later version.
  10: 
  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.
  15: 
  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
  19: 02110-1301 USA.
  20: 
  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
  24: combination.
  25: 
  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. */
  37: 
  38: package javax.swing.plaf.basic;
  39: 
  40: import java.awt.Component;
  41: import java.awt.Dimension;
  42: import java.awt.KeyboardFocusManager;
  43: import java.awt.event.ActionEvent;
  44: import java.awt.event.ComponentEvent;
  45: import java.awt.event.ComponentListener;
  46: import java.awt.event.MouseEvent;
  47: import java.util.EventListener;
  48: 
  49: import javax.swing.AbstractAction;
  50: import javax.swing.Action;
  51: import javax.swing.ActionMap;
  52: import javax.swing.BoxLayout;
  53: import javax.swing.InputMap;
  54: import javax.swing.JApplet;
  55: import javax.swing.JComponent;
  56: import javax.swing.JFrame;
  57: import javax.swing.JMenu;
  58: import javax.swing.JMenuBar;
  59: import javax.swing.JMenuItem;
  60: import javax.swing.JPopupMenu;
  61: import javax.swing.JRootPane;
  62: import javax.swing.LookAndFeel;
  63: import javax.swing.MenuElement;
  64: import javax.swing.MenuSelectionManager;
  65: import javax.swing.SwingUtilities;
  66: import javax.swing.UIManager;
  67: import javax.swing.event.ChangeEvent;
  68: import javax.swing.event.ChangeListener;
  69: import javax.swing.event.PopupMenuEvent;
  70: import javax.swing.event.PopupMenuListener;
  71: import javax.swing.plaf.ActionMapUIResource;
  72: import javax.swing.plaf.ComponentUI;
  73: import javax.swing.plaf.PopupMenuUI;
  74: 
  75: /**
  76:  * UI Delegate for JPopupMenu
  77:  */
  78: public class BasicPopupMenuUI extends PopupMenuUI
  79: {
  80:   /**
  81:    * Handles keyboard navigation through menus.
  82:    */
  83:   private static class NavigateAction
  84:     extends AbstractAction
  85:   {
  86: 
  87:     /**
  88:      * Creates a new NavigateAction instance.
  89:      *
  90:      * @param name the name of the action
  91:      */
  92:     NavigateAction(String name)
  93:     {
  94:       super(name);
  95:     }
  96: 
  97:     /**
  98:      * Actually performs the action.
  99:      */
 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: }