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 }