1 /* 2 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.basic; 27 28 import sun.swing.DefaultLookup; 29 import sun.swing.UIAction; 30 import java.awt.*; 31 import java.awt.event.*; 32 import java.beans.*; 33 import javax.swing.*; 34 import javax.swing.event.*; 35 import javax.swing.plaf.*; 36 import javax.swing.border.*; 37 import java.util.Arrays; 38 import java.util.ArrayList; 39 40 41 /** 42 * A default L&F implementation of MenuUI. This implementation 43 * is a "combined" view/controller. 44 * 45 * @author Georges Saab 46 * @author David Karlton 47 * @author Arnaud Weber 48 */ 49 public class BasicMenuUI extends BasicMenuItemUI 50 { 51 protected ChangeListener changeListener; 52 protected MenuListener menuListener; 53 54 private int lastMnemonic = 0; 55 56 /** Uses as the parent of the windowInputMap when selected. */ 57 private InputMap selectedWindowInputMap; 58 59 /* diagnostic aids -- should be false for production builds. */ 60 private static final boolean TRACE = false; // trace creates and disposes 61 private static final boolean VERBOSE = false; // show reuse hits/misses 62 private static final boolean DEBUG = false; // show bad params, misc. 63 64 private static boolean crossMenuMnemonic = true; 65 66 public static ComponentUI createUI(JComponent x) { 67 return new BasicMenuUI(); 68 } 69 70 static void loadActionMap(LazyActionMap map) { 71 BasicMenuItemUI.loadActionMap(map); 72 map.put(new Actions(Actions.SELECT, null, true)); 73 } 74 75 76 protected void installDefaults() { 77 super.installDefaults(); 78 updateDefaultBackgroundColor(); 79 ((JMenu)menuItem).setDelay(200); 80 crossMenuMnemonic = UIManager.getBoolean("Menu.crossMenuMnemonic"); 81 } 82 83 protected String getPropertyPrefix() { 84 return "Menu"; 85 } 86 87 protected void installListeners() { 88 super.installListeners(); 89 90 if (changeListener == null) 91 changeListener = createChangeListener(menuItem); 92 93 if (changeListener != null) 94 menuItem.addChangeListener(changeListener); 95 96 if (menuListener == null) 97 menuListener = createMenuListener(menuItem); 98 99 if (menuListener != null) 100 ((JMenu)menuItem).addMenuListener(menuListener); 101 } 102 103 protected void installKeyboardActions() { 104 super.installKeyboardActions(); 105 updateMnemonicBinding(); 106 } 107 108 void installLazyActionMap() { 109 LazyActionMap.installLazyActionMap(menuItem, BasicMenuUI.class, 110 getPropertyPrefix() + ".actionMap"); 111 } 112 113 void updateMnemonicBinding() { 114 int mnemonic = menuItem.getModel().getMnemonic(); 115 int[] shortcutKeys = (int[])DefaultLookup.get(menuItem, this, 116 "Menu.shortcutKeys"); 117 if (shortcutKeys == null) { 118 shortcutKeys = new int[] {KeyEvent.ALT_MASK}; 119 } 120 if (mnemonic == lastMnemonic) { 121 return; 122 } 123 InputMap windowInputMap = SwingUtilities.getUIInputMap( 124 menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW); 125 if (lastMnemonic != 0 && windowInputMap != null) { 126 for (int shortcutKey : shortcutKeys) { 127 windowInputMap.remove(KeyStroke.getKeyStroke 128 (lastMnemonic, shortcutKey, false)); 129 } 130 } 131 if (mnemonic != 0) { 132 if (windowInputMap == null) { 133 windowInputMap = createInputMap(JComponent. 134 WHEN_IN_FOCUSED_WINDOW); 135 SwingUtilities.replaceUIInputMap(menuItem, JComponent. 136 WHEN_IN_FOCUSED_WINDOW, windowInputMap); 137 } 138 for (int shortcutKey : shortcutKeys) { 139 windowInputMap.put(KeyStroke.getKeyStroke(mnemonic, 140 shortcutKey, false), "selectMenu"); 141 } 142 } 143 lastMnemonic = mnemonic; 144 } 145 146 protected void uninstallKeyboardActions() { 147 super.uninstallKeyboardActions(); 148 lastMnemonic = 0; 149 } 150 151 protected MouseInputListener createMouseInputListener(JComponent c) { 152 return getHandler(); 153 } 154 155 protected MenuListener createMenuListener(JComponent c) { 156 return null; 157 } 158 159 protected ChangeListener createChangeListener(JComponent c) { 160 return null; 161 } 162 163 protected PropertyChangeListener createPropertyChangeListener(JComponent c) { 164 return getHandler(); 165 } 166 167 BasicMenuItemUI.Handler getHandler() { 168 if (handler == null) { 169 handler = new Handler(); 170 } 171 return handler; 172 } 173 174 protected void uninstallDefaults() { 175 menuItem.setArmed(false); 176 menuItem.setSelected(false); 177 menuItem.resetKeyboardActions(); 178 super.uninstallDefaults(); 179 } 180 181 protected void uninstallListeners() { 182 super.uninstallListeners(); 183 184 if (changeListener != null) 185 menuItem.removeChangeListener(changeListener); 186 187 if (menuListener != null) 188 ((JMenu)menuItem).removeMenuListener(menuListener); 189 190 changeListener = null; 191 menuListener = null; 192 handler = null; 193 } 194 195 protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) { 196 return getHandler(); 197 } 198 199 protected MenuKeyListener createMenuKeyListener(JComponent c) { 200 return (MenuKeyListener)getHandler(); 201 } 202 203 public Dimension getMaximumSize(JComponent c) { 204 if (((JMenu)menuItem).isTopLevelMenu() == true) { 205 Dimension d = c.getPreferredSize(); 206 return new Dimension(d.width, Short.MAX_VALUE); 207 } 208 return null; 209 } 210 211 protected void setupPostTimer(JMenu menu) { 212 Timer timer = new Timer(menu.getDelay(), new Actions( 213 Actions.SELECT, menu,false)); 214 timer.setRepeats(false); 215 timer.start(); 216 } 217 218 private static void appendPath(MenuElement[] path, MenuElement elem) { 219 MenuElement newPath[] = new MenuElement[path.length+1]; 220 System.arraycopy(path, 0, newPath, 0, path.length); 221 newPath[path.length] = elem; 222 MenuSelectionManager.defaultManager().setSelectedPath(newPath); 223 } 224 225 private static class Actions extends UIAction { 226 private static final String SELECT = "selectMenu"; 227 228 // NOTE: This will be null if the action is registered in the 229 // ActionMap. For the timer use it will be non-null. 230 private JMenu menu; 231 private boolean force=false; 232 233 Actions(String key, JMenu menu, boolean shouldForce) { 234 super(key); 235 this.menu = menu; 236 this.force = shouldForce; 237 } 238 239 private JMenu getMenu(ActionEvent e) { 240 if (e.getSource() instanceof JMenu) { 241 return (JMenu)e.getSource(); 242 } 243 return menu; 244 } 245 246 public void actionPerformed(ActionEvent e) { 247 JMenu menu = getMenu(e); 248 if (!crossMenuMnemonic) { 249 JPopupMenu pm = BasicPopupMenuUI.getLastPopup(); 250 if (pm != null && pm != menu.getParent()) { 251 return; 252 } 253 } 254 255 final MenuSelectionManager defaultManager = MenuSelectionManager.defaultManager(); 256 if(force) { 257 Container cnt = menu.getParent(); 258 if(cnt != null && cnt instanceof JMenuBar) { 259 MenuElement me[]; 260 MenuElement subElements[]; 261 262 subElements = menu.getPopupMenu().getSubElements(); 263 if(subElements.length > 0) { 264 me = new MenuElement[4]; 265 me[0] = (MenuElement) cnt; 266 me[1] = menu; 267 me[2] = menu.getPopupMenu(); 268 me[3] = subElements[0]; 269 } else { 270 me = new MenuElement[3]; 271 me[0] = (MenuElement)cnt; 272 me[1] = menu; 273 me[2] = menu.getPopupMenu(); 274 } 275 defaultManager.setSelectedPath(me); 276 } 277 } else { 278 MenuElement path[] = defaultManager.getSelectedPath(); 279 if(path.length > 0 && path[path.length-1] == menu) { 280 appendPath(path, menu.getPopupMenu()); 281 } 282 } 283 } 284 285 public boolean isEnabled(Object c) { 286 if (c instanceof JMenu) { 287 return ((JMenu)c).isEnabled(); 288 } 289 return true; 290 } 291 } 292 293 /* 294 * Set the background color depending on whether this is a toplevel menu 295 * in a menubar or a submenu of another menu. 296 */ 297 private void updateDefaultBackgroundColor() { 298 if (!UIManager.getBoolean("Menu.useMenuBarBackgroundForTopLevel")) { 299 return; 300 } 301 JMenu menu = (JMenu)menuItem; 302 if (menu.getBackground() instanceof UIResource) { 303 if (menu.isTopLevelMenu()) { 304 menu.setBackground(UIManager.getColor("MenuBar.background")); 305 } else { 306 menu.setBackground(UIManager.getColor(getPropertyPrefix() + ".background")); 307 } 308 } 309 } 310 311 /** 312 * Instantiated and used by a menu item to handle the current menu selection 313 * from mouse events. A MouseInputHandler processes and forwards all mouse events 314 * to a shared instance of the MenuSelectionManager. 315 * <p> 316 * This class is protected so that it can be subclassed by other look and 317 * feels to implement their own mouse handling behavior. All overridden 318 * methods should call the parent methods so that the menu selection 319 * is correct. 320 * 321 * @see javax.swing.MenuSelectionManager 322 * @since 1.4 323 */ 324 protected class MouseInputHandler implements MouseInputListener { 325 // NOTE: This class exists only for backward compatibility. All 326 // its functionality has been moved into Handler. If you need to add 327 // new functionality add it to the Handler, but make sure this 328 // class calls into the Handler. 329 330 public void mouseClicked(MouseEvent e) { 331 getHandler().mouseClicked(e); 332 } 333 334 /** 335 * Invoked when the mouse has been clicked on the menu. This 336 * method clears or sets the selection path of the 337 * MenuSelectionManager. 338 * 339 * @param e the mouse event 340 */ 341 public void mousePressed(MouseEvent e) { 342 getHandler().mousePressed(e); 343 } 344 345 /** 346 * Invoked when the mouse has been released on the menu. Delegates the 347 * mouse event to the MenuSelectionManager. 348 * 349 * @param e the mouse event 350 */ 351 public void mouseReleased(MouseEvent e) { 352 getHandler().mouseReleased(e); 353 } 354 355 /** 356 * Invoked when the cursor enters the menu. This method sets the selected 357 * path for the MenuSelectionManager and handles the case 358 * in which a menu item is used to pop up an additional menu, as in a 359 * hierarchical menu system. 360 * 361 * @param e the mouse event; not used 362 */ 363 public void mouseEntered(MouseEvent e) { 364 getHandler().mouseEntered(e); 365 } 366 public void mouseExited(MouseEvent e) { 367 getHandler().mouseExited(e); 368 } 369 370 /** 371 * Invoked when a mouse button is pressed on the menu and then dragged. 372 * Delegates the mouse event to the MenuSelectionManager. 373 * 374 * @param e the mouse event 375 * @see java.awt.event.MouseMotionListener#mouseDragged 376 */ 377 public void mouseDragged(MouseEvent e) { 378 getHandler().mouseDragged(e); 379 } 380 381 public void mouseMoved(MouseEvent e) { 382 getHandler().mouseMoved(e); 383 } 384 } 385 386 /** 387 * As of Java 2 platform 1.4, this previously undocumented class 388 * is now obsolete. KeyBindings are now managed by the popup menu. 389 */ 390 public class ChangeHandler implements ChangeListener { 391 public JMenu menu; 392 public BasicMenuUI ui; 393 public boolean isSelected = false; 394 public Component wasFocused; 395 396 public ChangeHandler(JMenu m, BasicMenuUI ui) { 397 menu = m; 398 this.ui = ui; 399 } 400 401 public void stateChanged(ChangeEvent e) { } 402 } 403 404 private class Handler extends BasicMenuItemUI.Handler implements MenuKeyListener { 405 // 406 // PropertyChangeListener 407 // 408 public void propertyChange(PropertyChangeEvent e) { 409 if (e.getPropertyName() == AbstractButton. 410 MNEMONIC_CHANGED_PROPERTY) { 411 updateMnemonicBinding(); 412 } 413 else { 414 if (e.getPropertyName().equals("ancestor")) { 415 updateDefaultBackgroundColor(); 416 } 417 super.propertyChange(e); 418 } 419 } 420 421 // 422 // MouseInputListener 423 // 424 public void mouseClicked(MouseEvent e) { 425 } 426 427 /** 428 * Invoked when the mouse has been clicked on the menu. This 429 * method clears or sets the selection path of the 430 * MenuSelectionManager. 431 * 432 * @param e the mouse event 433 */ 434 public void mousePressed(MouseEvent e) { 435 JMenu menu = (JMenu)menuItem; 436 if (!menu.isEnabled()) 437 return; 438 439 MenuSelectionManager manager = 440 MenuSelectionManager.defaultManager(); 441 if(menu.isTopLevelMenu()) { 442 if(menu.isSelected() && menu.getPopupMenu().isShowing()) { 443 manager.clearSelectedPath(); 444 } else { 445 Container cnt = menu.getParent(); 446 if(cnt != null && cnt instanceof JMenuBar) { 447 MenuElement me[] = new MenuElement[2]; 448 me[0]=(MenuElement)cnt; 449 me[1]=menu; 450 manager.setSelectedPath(me); 451 } 452 } 453 } 454 455 MenuElement selectedPath[] = manager.getSelectedPath(); 456 if (selectedPath.length > 0 && 457 selectedPath[selectedPath.length-1] != menu.getPopupMenu()) { 458 459 if(menu.isTopLevelMenu() || 460 menu.getDelay() == 0) { 461 appendPath(selectedPath, menu.getPopupMenu()); 462 } else { 463 setupPostTimer(menu); 464 } 465 } 466 } 467 468 /** 469 * Invoked when the mouse has been released on the menu. Delegates the 470 * mouse event to the MenuSelectionManager. 471 * 472 * @param e the mouse event 473 */ 474 public void mouseReleased(MouseEvent e) { 475 JMenu menu = (JMenu)menuItem; 476 if (!menu.isEnabled()) 477 return; 478 MenuSelectionManager manager = 479 MenuSelectionManager.defaultManager(); 480 manager.processMouseEvent(e); 481 if (!e.isConsumed()) 482 manager.clearSelectedPath(); 483 } 484 485 /** 486 * Invoked when the cursor enters the menu. This method sets the selected 487 * path for the MenuSelectionManager and handles the case 488 * in which a menu item is used to pop up an additional menu, as in a 489 * hierarchical menu system. 490 * 491 * @param e the mouse event; not used 492 */ 493 public void mouseEntered(MouseEvent e) { 494 JMenu menu = (JMenu)menuItem; 495 // only disable the menu highlighting if it's disabled and the property isn't 496 // true. This allows disabled rollovers to work in WinL&F 497 if (!menu.isEnabled() && !UIManager.getBoolean("MenuItem.disabledAreNavigable")) { 498 return; 499 } 500 501 MenuSelectionManager manager = 502 MenuSelectionManager.defaultManager(); 503 MenuElement selectedPath[] = manager.getSelectedPath(); 504 if (!menu.isTopLevelMenu()) { 505 if(!(selectedPath.length > 0 && 506 selectedPath[selectedPath.length-1] == 507 menu.getPopupMenu())) { 508 if(menu.getDelay() == 0) { 509 appendPath(getPath(), menu.getPopupMenu()); 510 } else { 511 manager.setSelectedPath(getPath()); 512 setupPostTimer(menu); 513 } 514 } 515 } else { 516 if(selectedPath.length > 0 && 517 selectedPath[0] == menu.getParent()) { 518 MenuElement newPath[] = new MenuElement[3]; 519 // A top level menu's parent is by definition 520 // a JMenuBar 521 newPath[0] = (MenuElement)menu.getParent(); 522 newPath[1] = menu; 523 if (BasicPopupMenuUI.getLastPopup() != null) { 524 newPath[2] = menu.getPopupMenu(); 525 } 526 manager.setSelectedPath(newPath); 527 } 528 } 529 } 530 public void mouseExited(MouseEvent e) { 531 } 532 533 /** 534 * Invoked when a mouse button is pressed on the menu and then dragged. 535 * Delegates the mouse event to the MenuSelectionManager. 536 * 537 * @param e the mouse event 538 * @see java.awt.event.MouseMotionListener#mouseDragged 539 */ 540 public void mouseDragged(MouseEvent e) { 541 JMenu menu = (JMenu)menuItem; 542 if (!menu.isEnabled()) 543 return; 544 MenuSelectionManager.defaultManager().processMouseEvent(e); 545 } 546 public void mouseMoved(MouseEvent e) { 547 } 548 549 550 // 551 // MenuDragHandler 552 // 553 public void menuDragMouseEntered(MenuDragMouseEvent e) {} 554 public void menuDragMouseDragged(MenuDragMouseEvent e) { 555 if (menuItem.isEnabled() == false) 556 return; 557 558 MenuSelectionManager manager = e.getMenuSelectionManager(); 559 MenuElement path[] = e.getPath(); 560 561 Point p = e.getPoint(); 562 if(p.x >= 0 && p.x < menuItem.getWidth() && 563 p.y >= 0 && p.y < menuItem.getHeight()) { 564 JMenu menu = (JMenu)menuItem; 565 MenuElement selectedPath[] = manager.getSelectedPath(); 566 if(!(selectedPath.length > 0 && 567 selectedPath[selectedPath.length-1] == 568 menu.getPopupMenu())) { 569 if(menu.isTopLevelMenu() || 570 menu.getDelay() == 0 || 571 e.getID() == MouseEvent.MOUSE_DRAGGED) { 572 appendPath(path, menu.getPopupMenu()); 573 } else { 574 manager.setSelectedPath(path); 575 setupPostTimer(menu); 576 } 577 } 578 } else if(e.getID() == MouseEvent.MOUSE_RELEASED) { 579 Component comp = manager.componentForPoint(e.getComponent(), e.getPoint()); 580 if (comp == null) 581 manager.clearSelectedPath(); 582 } 583 584 } 585 public void menuDragMouseExited(MenuDragMouseEvent e) {} 586 public void menuDragMouseReleased(MenuDragMouseEvent e) {} 587 588 // 589 // MenuKeyListener 590 // 591 /** 592 * Open the Menu 593 */ 594 public void menuKeyTyped(MenuKeyEvent e) { 595 if (!crossMenuMnemonic && BasicPopupMenuUI.getLastPopup() != null) { 596 // when crossMenuMnemonic is not set, we don't open a toplevel 597 // menu if another toplevel menu is already open 598 return; 599 } 600 601 if (BasicPopupMenuUI.getPopups().size() != 0) { 602 //Fix 6939261: to return in case not on the main menu 603 //and has a pop-up. 604 //after return code will be handled in BasicPopupMenuUI.java 605 return; 606 } 607 608 char key = Character.toLowerCase((char)menuItem.getMnemonic()); 609 MenuElement path[] = e.getPath(); 610 if (key == Character.toLowerCase(e.getKeyChar())) { 611 JPopupMenu popupMenu = ((JMenu)menuItem).getPopupMenu(); 612 ArrayList<MenuElement> newList = new ArrayList<>(Arrays.asList(path)); 613 newList.add(popupMenu); 614 MenuElement subs[] = popupMenu.getSubElements(); 615 MenuElement sub = 616 BasicPopupMenuUI.findEnabledChild(subs, -1, true); 617 if(sub != null) { 618 newList.add(sub); 619 } 620 MenuSelectionManager manager = e.getMenuSelectionManager(); 621 MenuElement newPath[] = new MenuElement[0];; 622 newPath = newList.toArray(newPath); 623 manager.setSelectedPath(newPath); 624 e.consume(); 625 } 626 } 627 628 public void menuKeyPressed(MenuKeyEvent e) {} 629 public void menuKeyReleased(MenuKeyEvent e) {} 630 } 631 }