Showing the changes to java/pkg/javax/swing/plaf/basic/BasicPopupMenuUI.java (1 of 18352 files). Show all
Unified · Split
| @@ -0,0 +1,1223 @@ | |||
| 1 | /* | ||
| 2 | * Copyright (c) 1997, 2008, 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 javax.swing.*; | ||
| 29 | import javax.swing.event.*; | ||
| 30 | import javax.swing.plaf.*; | ||
| 31 | import javax.swing.plaf.basic.*; | ||
| 32 | import javax.swing.border.*; | ||
| 33 | |||
| 34 | import java.applet.Applet; | ||
| 35 | |||
| 36 | import java.awt.Component; | ||
| 37 | import java.awt.Container; | ||
| 38 | import java.awt.Dimension; | ||
| 39 | import java.awt.KeyboardFocusManager; | ||
| 40 | import java.awt.Window; | ||
| 41 | import java.awt.event.*; | ||
| 42 | import java.awt.AWTEvent; | ||
| 43 | import java.awt.Toolkit; | ||
| 44 | |||
| 45 | import java.beans.PropertyChangeListener; | ||
| 46 | import java.beans.PropertyChangeEvent; | ||
| 47 | |||
| 48 | import java.util.*; | ||
| 49 | |||
| 50 | import sun.swing.DefaultLookup; | ||
| 51 | import sun.swing.UIAction; | ||
| 52 | |||
| 53 | import sun.awt.AppContext; | ||
| 54 | |||
| 55 | /** | ||
| 56 | * A Windows L&F implementation of PopupMenuUI. This implementation | ||
| 57 | * is a "combined" view/controller. | ||
| 58 | * | ||
| 59 | * @author Georges Saab | ||
| 60 | * @author David Karlton | ||
| 61 | * @author Arnaud Weber | ||
| 62 | */ | ||
| 63 | public class BasicPopupMenuUI extends PopupMenuUI { | ||
| 64 | static final StringBuilder MOUSE_GRABBER_KEY = new StringBuilder( | ||
| 65 | "javax.swing.plaf.basic.BasicPopupMenuUI.MouseGrabber"); | ||
| 66 | static final StringBuilder MENU_KEYBOARD_HELPER_KEY = new StringBuilder( | ||
| 67 | "javax.swing.plaf.basic.BasicPopupMenuUI.MenuKeyboardHelper"); | ||
| 68 | |||
| 69 | protected JPopupMenu popupMenu = null; | ||
| 70 | private transient PopupMenuListener popupMenuListener = null; | ||
| 71 | private MenuKeyListener menuKeyListener = null; | ||
| 72 | |||
| 73 | private static boolean checkedUnpostPopup; | ||
| 74 | private static boolean unpostPopup; | ||
| 75 | |||
| 76 | public static ComponentUI createUI(JComponent x) { | ||
| 77 | return new BasicPopupMenuUI(); | ||
| 78 | } | ||
| 79 | |||
| 80 | public BasicPopupMenuUI() { | ||
| 81 | BasicLookAndFeel.needsEventHelper = true; | ||
| 82 | LookAndFeel laf = UIManager.getLookAndFeel(); | ||
| 83 | if (laf instanceof BasicLookAndFeel) { | ||
| 84 | ((BasicLookAndFeel)laf).installAWTEventListener(); | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | public void installUI(JComponent c) { | ||
| 89 | popupMenu = (JPopupMenu) c; | ||
| 90 | |||
| 91 | installDefaults(); | ||
| 92 | installListeners(); | ||
| 93 | installKeyboardActions(); | ||
| 94 | } | ||
| 95 | |||
| 96 | public void installDefaults() { | ||
| 97 | if (popupMenu.getLayout() == null || | ||
| 98 | popupMenu.getLayout() instanceof UIResource) | ||
| 99 | popupMenu.setLayout(new DefaultMenuLayout(popupMenu, BoxLayout.Y_AXIS)); | ||
| 100 | |||
| 101 | LookAndFeel.installProperty(popupMenu, "opaque", Boolean.TRUE); | ||
| 102 | LookAndFeel.installBorder(popupMenu, "PopupMenu.border"); | ||
| 103 | LookAndFeel.installColorsAndFont(popupMenu, | ||
| 104 | "PopupMenu.background", | ||
| 105 | "PopupMenu.foreground", | ||
| 106 | "PopupMenu.font"); | ||
| 107 | } | ||
| 108 | |||
| 109 | protected void installListeners() { | ||
| 110 | if (popupMenuListener == null) { | ||
| 111 | popupMenuListener = new BasicPopupMenuListener(); | ||
| 112 | } | ||
| 113 | popupMenu.addPopupMenuListener(popupMenuListener); | ||
| 114 | |||
| 115 | if (menuKeyListener == null) { | ||
| 116 | menuKeyListener = new BasicMenuKeyListener(); | ||
| 117 | } | ||
| 118 | popupMenu.addMenuKeyListener(menuKeyListener); | ||
| 119 | |||
| 120 | AppContext context = AppContext.getAppContext(); | ||
| 121 | synchronized (MOUSE_GRABBER_KEY) { | ||
| 122 | MouseGrabber mouseGrabber = (MouseGrabber)context.get( | ||
| 123 | MOUSE_GRABBER_KEY); | ||
| 124 | if (mouseGrabber == null) { | ||
| 125 | mouseGrabber = new MouseGrabber(); | ||
| 126 | context.put(MOUSE_GRABBER_KEY, mouseGrabber); | ||
| 127 | } | ||
| 128 | } | ||
| 129 | synchronized (MENU_KEYBOARD_HELPER_KEY) { | ||
| 130 | MenuKeyboardHelper helper = | ||
| 131 | (MenuKeyboardHelper)context.get(MENU_KEYBOARD_HELPER_KEY); | ||
| 132 | if (helper == null) { | ||
| 133 | helper = new MenuKeyboardHelper(); | ||
| 134 | context.put(MENU_KEYBOARD_HELPER_KEY, helper); | ||
| 135 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 136 | msm.addChangeListener(helper); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | protected void installKeyboardActions() { | ||
| 142 | } | ||
| 143 | |||
| 144 | static InputMap getInputMap(JPopupMenu popup, JComponent c) { | ||
| 145 | InputMap windowInputMap = null; | ||
| 146 | Object[] bindings = (Object[])UIManager.get("PopupMenu.selectedWindowInputMapBindings"); | ||
| 147 | if (bindings != null) { | ||
| 148 | windowInputMap = LookAndFeel.makeComponentInputMap(c, bindings); | ||
| 149 | if (!popup.getComponentOrientation().isLeftToRight()) { | ||
| 150 | Object[] km = (Object[])UIManager.get("PopupMenu.selectedWindowInputMapBindings.RightToLeft"); | ||
| 151 | if (km != null) { | ||
| 152 | InputMap rightToLeftInputMap = LookAndFeel.makeComponentInputMap(c, km); | ||
| 153 | rightToLeftInputMap.setParent(windowInputMap); | ||
| 154 | windowInputMap = rightToLeftInputMap; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | } | ||
| 158 | return windowInputMap; | ||
| 159 | } | ||
| 160 | |||
| 161 | static ActionMap getActionMap() { | ||
| 162 | return LazyActionMap.getActionMap(BasicPopupMenuUI.class, | ||
| 163 | "PopupMenu.actionMap"); | ||
| 164 | } | ||
| 165 | |||
| 166 | static void loadActionMap(LazyActionMap map) { | ||
| 167 | map.put(new Actions(Actions.CANCEL)); | ||
| 168 | map.put(new Actions(Actions.SELECT_NEXT)); | ||
| 169 | map.put(new Actions(Actions.SELECT_PREVIOUS)); | ||
| 170 | map.put(new Actions(Actions.SELECT_PARENT)); | ||
| 171 | map.put(new Actions(Actions.SELECT_CHILD)); | ||
| 172 | map.put(new Actions(Actions.RETURN)); | ||
| 173 | BasicLookAndFeel.installAudioActionMap(map); | ||
| 174 | } | ||
| 175 | |||
| 176 | public void uninstallUI(JComponent c) { | ||
| 177 | uninstallDefaults(); | ||
| 178 | uninstallListeners(); | ||
| 179 | uninstallKeyboardActions(); | ||
| 180 | |||
| 181 | popupMenu = null; | ||
| 182 | } | ||
| 183 | |||
| 184 | protected void uninstallDefaults() { | ||
| 185 | LookAndFeel.uninstallBorder(popupMenu); | ||
| 186 | } | ||
| 187 | |||
| 188 | protected void uninstallListeners() { | ||
| 189 | if (popupMenuListener != null) { | ||
| 190 | popupMenu.removePopupMenuListener(popupMenuListener); | ||
| 191 | } | ||
| 192 | if (menuKeyListener != null) { | ||
| 193 | popupMenu.removeMenuKeyListener(menuKeyListener); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | protected void uninstallKeyboardActions() { | ||
| 198 | SwingUtilities.replaceUIActionMap(popupMenu, null); | ||
| 199 | SwingUtilities.replaceUIInputMap(popupMenu, | ||
| 200 | JComponent.WHEN_IN_FOCUSED_WINDOW, null); | ||
| 201 | } | ||
| 202 | |||
| 203 | static MenuElement getFirstPopup() { | ||
| 204 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 205 | MenuElement[] p = msm.getSelectedPath(); | ||
| 206 | MenuElement me = null; | ||
| 207 | |||
| 208 | for(int i = 0 ; me == null && i < p.length ; i++) { | ||
| 209 | if (p[i] instanceof JPopupMenu) | ||
| 210 | me = p[i]; | ||
| 211 | } | ||
| 212 | |||
| 213 | return me; | ||
| 214 | } | ||
| 215 | |||
| 216 | static JPopupMenu getLastPopup() { | ||
| 217 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 218 | MenuElement[] p = msm.getSelectedPath(); | ||
| 219 | JPopupMenu popup = null; | ||
| 220 | |||
| 221 | for(int i = p.length - 1; popup == null && i >= 0; i--) { | ||
| 222 | if (p[i] instanceof JPopupMenu) | ||
| 223 | popup = (JPopupMenu)p[i]; | ||
| 224 | } | ||
| 225 | return popup; | ||
| 226 | } | ||
| 227 | |||
| 228 | static List<JPopupMenu> getPopups() { | ||
| 229 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 230 | MenuElement[] p = msm.getSelectedPath(); | ||
| 231 | |||
| 232 | List<JPopupMenu> list = new ArrayList<JPopupMenu>(p.length); | ||
| 233 | for (MenuElement element : p) { | ||
| 234 | if (element instanceof JPopupMenu) { | ||
| 235 | list.add((JPopupMenu) element); | ||
| 236 | } | ||
| 237 | } | ||
| 238 | return list; | ||
| 239 | } | ||
| 240 | |||
| 241 | public boolean isPopupTrigger(MouseEvent e) { | ||
| 242 | return ((e.getID()==MouseEvent.MOUSE_RELEASED) | ||
| 243 | && ((e.getModifiers() & MouseEvent.BUTTON3_MASK)!=0)); | ||
| 244 | } | ||
| 245 | |||
| 246 | private static boolean checkInvokerEqual(MenuElement present, MenuElement last) { | ||
| 247 | Component invokerPresent = present.getComponent(); | ||
| 248 | Component invokerLast = last.getComponent(); | ||
| 249 | |||
| 250 | if (invokerPresent instanceof JPopupMenu) { | ||
| 251 | invokerPresent = ((JPopupMenu)invokerPresent).getInvoker(); | ||
| 252 | } | ||
| 253 | if (invokerLast instanceof JPopupMenu) { | ||
| 254 | invokerLast = ((JPopupMenu)invokerLast).getInvoker(); | ||
| 255 | } | ||
| 256 | return (invokerPresent == invokerLast); | ||
| 257 | } | ||
| 258 | |||
| 259 | |||
| 260 | /** | ||
| 261 | * This Listener fires the Action that provides the correct auditory | ||
| 262 | * feedback. | ||
| 263 | * | ||
| 264 | * @since 1.4 | ||
| 265 | */ | ||
| 266 | private class BasicPopupMenuListener implements PopupMenuListener { | ||
| 267 | public void popupMenuCanceled(PopupMenuEvent e) { | ||
| 268 | } | ||
| 269 | |||
| 270 | public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { | ||
| 271 | } | ||
| 272 | |||
| 273 | public void popupMenuWillBecomeVisible(PopupMenuEvent e) { | ||
| 274 | BasicLookAndFeel.playSound((JPopupMenu)e.getSource(), | ||
| 275 | "PopupMenu.popupSound"); | ||
| 276 | } | ||
| 277 | } | ||
| 278 | |||
| 279 | /** | ||
| 280 | * Handles mnemonic for children JMenuItems. | ||
| 281 | * @since 1.5 | ||
| 282 | */ | ||
| 283 | private class BasicMenuKeyListener implements MenuKeyListener { | ||
| 284 | MenuElement menuToOpen = null; | ||
| 285 | |||
| 286 | public void menuKeyTyped(MenuKeyEvent e) { | ||
| 287 | if (menuToOpen != null) { | ||
| 288 | // we have a submenu to open | ||
| 289 | JPopupMenu subpopup = ((JMenu)menuToOpen).getPopupMenu(); | ||
| 290 | MenuElement subitem = findEnabledChild( | ||
| 291 | subpopup.getSubElements(), -1, true); | ||
| 292 | |||
| 293 | ArrayList<MenuElement> lst = new ArrayList<MenuElement>(Arrays.asList(e.getPath())); | ||
| 294 | lst.add(menuToOpen); | ||
| 295 | lst.add(subpopup); | ||
| 296 | if (subitem != null) { | ||
| 297 | lst.add(subitem); | ||
| 298 | } | ||
| 299 | MenuElement newPath[] = new MenuElement[0]; | ||
| 300 | newPath = lst.toArray(newPath); | ||
| 301 | MenuSelectionManager.defaultManager().setSelectedPath(newPath); | ||
| 302 | e.consume(); | ||
| 303 | } | ||
| 304 | menuToOpen = null; | ||
| 305 | } | ||
| 306 | |||
| 307 | public void menuKeyPressed(MenuKeyEvent e) { | ||
| 308 | char keyChar = e.getKeyChar(); | ||
| 309 | |||
| 310 | // Handle the case for Escape or Enter... | ||
| 311 | if (!Character.isLetterOrDigit(keyChar)) { | ||
| 312 | return; | ||
| 313 | } | ||
| 314 | |||
| 315 | MenuSelectionManager manager = e.getMenuSelectionManager(); | ||
| 316 | MenuElement path[] = e.getPath(); | ||
| 317 | MenuElement items[] = popupMenu.getSubElements(); | ||
| 318 | int currentIndex = -1; | ||
| 319 | int matches = 0; | ||
| 320 | int firstMatch = -1; | ||
| 321 | int indexes[] = null; | ||
| 322 | |||
| 323 | for (int j = 0; j < items.length; j++) { | ||
| 324 | if (! (items[j] instanceof JMenuItem)) { | ||
| 325 | continue; | ||
| 326 | } | ||
| 327 | JMenuItem item = (JMenuItem)items[j]; | ||
| 328 | int mnemonic = item.getMnemonic(); | ||
| 329 | if (item.isEnabled() && | ||
| 330 | item.isVisible() && lower(keyChar) == lower(mnemonic)) { | ||
| 331 | if (matches == 0) { | ||
| 332 | firstMatch = j; | ||
| 333 | matches++; | ||
| 334 | } else { | ||
| 335 | if (indexes == null) { | ||
| 336 | indexes = new int[items.length]; | ||
| 337 | indexes[0] = firstMatch; | ||
| 338 | } | ||
| 339 | indexes[matches++] = j; | ||
| 340 | } | ||
| 341 | } | ||
| 342 | if (item.isArmed() || item.isSelected()) { | ||
| 343 | currentIndex = matches - 1; | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | if (matches == 0) { | ||
| 348 | // no op | ||
| 349 | } else if (matches == 1) { | ||
| 350 | // Invoke the menu action | ||
| 351 | JMenuItem item = (JMenuItem)items[firstMatch]; | ||
| 352 | if (item instanceof JMenu) { | ||
| 353 | // submenus are handled in menuKeyTyped | ||
| 354 | menuToOpen = item; | ||
| 355 | } else if (item.isEnabled()) { | ||
| 356 | // we have a menu item | ||
| 357 | manager.clearSelectedPath(); | ||
| 358 | item.doClick(); | ||
| 359 | } | ||
| 360 | e.consume(); | ||
| 361 | } else { | ||
| 362 | // Select the menu item with the matching mnemonic. If | ||
| 363 | // the same mnemonic has been invoked then select the next | ||
| 364 | // menu item in the cycle. | ||
| 365 | MenuElement newItem; | ||
| 366 | |||
| 367 | newItem = items[indexes[(currentIndex + 1) % matches]]; | ||
| 368 | |||
| 369 | MenuElement newPath[] = new MenuElement[path.length+1]; | ||
| 370 | System.arraycopy(path, 0, newPath, 0, path.length); | ||
| 371 | newPath[path.length] = newItem; | ||
| 372 | manager.setSelectedPath(newPath); | ||
| 373 | e.consume(); | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | public void menuKeyReleased(MenuKeyEvent e) { | ||
| 378 | } | ||
| 379 | |||
| 380 | private char lower(char keyChar) { | ||
| 381 | return Character.toLowerCase(keyChar); | ||
| 382 | } | ||
| 383 | |||
| 384 | private char lower(int mnemonic) { | ||
| 385 | return Character.toLowerCase((char) mnemonic); | ||
| 386 | } | ||
| 387 | } | ||
| 388 | |||
| 389 | private static class Actions extends UIAction { | ||
| 390 | // Types of actions | ||
| 391 | private static final String CANCEL = "cancel"; | ||
| 392 | private static final String SELECT_NEXT = "selectNext"; | ||
| 393 | private static final String SELECT_PREVIOUS = "selectPrevious"; | ||
| 394 | private static final String SELECT_PARENT = "selectParent"; | ||
| 395 | private static final String SELECT_CHILD = "selectChild"; | ||
| 396 | private static final String RETURN = "return"; | ||
| 397 | |||
| 398 | // Used for next/previous actions | ||
| 399 | private static final boolean FORWARD = true; | ||
| 400 | private static final boolean BACKWARD = false; | ||
| 401 | |||
| 402 | // Used for parent/child actions | ||
| 403 | private static final boolean PARENT = false; | ||
| 404 | private static final boolean CHILD = true; | ||
| 405 | |||
| 406 | |||
| 407 | Actions(String key) { | ||
| 408 | super(key); | ||
| 409 | } | ||
| 410 | |||
| 411 | public void actionPerformed(ActionEvent e) { | ||
| 412 | String key = getName(); | ||
| 413 | if (key == CANCEL) { | ||
| 414 | cancel(); | ||
| 415 | } | ||
| 416 | else if (key == SELECT_NEXT) { | ||
| 417 | selectItem(FORWARD); | ||
| 418 | } | ||
| 419 | else if (key == SELECT_PREVIOUS) { | ||
| 420 | selectItem(BACKWARD); | ||
| 421 | } | ||
| 422 | else if (key == SELECT_PARENT) { | ||
| 423 | selectParentChild(PARENT); | ||
| 424 | } | ||
| 425 | else if (key == SELECT_CHILD) { | ||
| 426 | selectParentChild(CHILD); | ||
| 427 | } | ||
| 428 | else if (key == RETURN) { | ||
| 429 | doReturn(); | ||
| 430 | } | ||
| 431 | } | ||
| 432 | |||
| 433 | private void doReturn() { | ||
| 434 | KeyboardFocusManager fmgr = | ||
| 435 | KeyboardFocusManager.getCurrentKeyboardFocusManager(); | ||
| 436 | Component focusOwner = fmgr.getFocusOwner(); | ||
| 437 | if(focusOwner != null && !(focusOwner instanceof JRootPane)) { | ||
| 438 | return; | ||
| 439 | } | ||
| 440 | |||
| 441 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 442 | MenuElement path[] = msm.getSelectedPath(); | ||
| 443 | MenuElement lastElement; | ||
| 444 | if(path.length > 0) { | ||
| 445 | lastElement = path[path.length-1]; | ||
| 446 | if(lastElement instanceof JMenu) { | ||
| 447 | MenuElement newPath[] = new MenuElement[path.length+1]; | ||
| 448 | System.arraycopy(path,0,newPath,0,path.length); | ||
| 449 | newPath[path.length] = ((JMenu)lastElement).getPopupMenu(); | ||
| 450 | msm.setSelectedPath(newPath); | ||
| 451 | } else if(lastElement instanceof JMenuItem) { | ||
| 452 | JMenuItem mi = (JMenuItem)lastElement; | ||
| 453 | |||
| 454 | if (mi.getUI() instanceof BasicMenuItemUI) { | ||
| 455 | ((BasicMenuItemUI)mi.getUI()).doClick(msm); | ||
| 456 | } | ||
| 457 | else { | ||
| 458 | msm.clearSelectedPath(); | ||
| 459 | mi.doClick(0); | ||
| 460 | } | ||
| 461 | } | ||
| 462 | } | ||
| 463 | } | ||
| 464 | private void selectParentChild(boolean direction) { | ||
| 465 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 466 | MenuElement path[] = msm.getSelectedPath(); | ||
| 467 | int len = path.length; | ||
| 468 | |||
| 469 | if (direction == PARENT) { | ||
| 470 | // selecting parent | ||
| 471 | int popupIndex = len-1; | ||
| 472 | |||
| 473 | if (len > 2 && | ||
| 474 | // check if we have an open submenu. A submenu item may or | ||
| 475 | // may not be selected, so submenu popup can be either the | ||
| 476 | // last or next to the last item. | ||
| 477 | (path[popupIndex] instanceof JPopupMenu || | ||
| 478 | path[--popupIndex] instanceof JPopupMenu) && | ||
| 479 | !((JMenu)path[popupIndex-1]).isTopLevelMenu()) { | ||
| 480 | |||
| 481 | // we have a submenu, just close it | ||
| 482 | MenuElement newPath[] = new MenuElement[popupIndex]; | ||
| 483 | System.arraycopy(path, 0, newPath, 0, popupIndex); | ||
| 484 | msm.setSelectedPath(newPath); | ||
| 485 | return; | ||
| 486 | } | ||
| 487 | } else { | ||
| 488 | // selecting child | ||
| 489 | if (len > 0 && path[len-1] instanceof JMenu && | ||
| 490 | !((JMenu)path[len-1]).isTopLevelMenu()) { | ||
| 491 | |||
| 492 | // we have a submenu, open it | ||
| 493 | JMenu menu = (JMenu)path[len-1]; | ||
| 494 | JPopupMenu popup = menu.getPopupMenu(); | ||
| 495 | MenuElement[] subs = popup.getSubElements(); | ||
| 496 | MenuElement item = findEnabledChild(subs, -1, true); | ||
| 497 | MenuElement[] newPath; | ||
| 498 | |||
| 499 | if (item == null) { | ||
| 500 | newPath = new MenuElement[len+1]; | ||
| 501 | } else { | ||
| 502 | newPath = new MenuElement[len+2]; | ||
| 503 | newPath[len+1] = item; | ||
| 504 | } | ||
| 505 | System.arraycopy(path, 0, newPath, 0, len); | ||
| 506 | newPath[len] = popup; | ||
| 507 | msm.setSelectedPath(newPath); | ||
| 508 | return; | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | // check if we have a toplevel menu selected. | ||
| 513 | // If this is the case, we select another toplevel menu | ||
| 514 | if (len > 1 && path[0] instanceof JMenuBar) { | ||
| 515 | MenuElement currentMenu = path[1]; | ||
| 516 | MenuElement nextMenu = findEnabledChild( | ||
| 517 | path[0].getSubElements(), currentMenu, direction); | ||
| 518 | |||
| 519 | if (nextMenu != null && nextMenu != currentMenu) { | ||
| 520 | MenuElement newSelection[]; | ||
| 521 | if (len == 2) { | ||
| 522 | // menu is selected but its popup not shown | ||
| 523 | newSelection = new MenuElement[2]; | ||
| 524 | newSelection[0] = path[0]; | ||
| 525 | newSelection[1] = nextMenu; | ||
| 526 | } else { | ||
| 527 | // menu is selected and its popup is shown | ||
| 528 | newSelection = new MenuElement[3]; | ||
| 529 | newSelection[0] = path[0]; | ||
| 530 | newSelection[1] = nextMenu; | ||
| 531 | newSelection[2] = ((JMenu)nextMenu).getPopupMenu(); | ||
| 532 | } | ||
| 533 | msm.setSelectedPath(newSelection); | ||
| 534 | } | ||
| 535 | } | ||
| 536 | } | ||
| 537 | |||
| 538 | private void selectItem(boolean direction) { | ||
| 539 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 540 | MenuElement path[] = msm.getSelectedPath(); | ||
| 541 | if (path.length == 0) { | ||
| 542 | return; | ||
| 543 | } | ||
| 544 | int len = path.length; | ||
| 545 | if (len == 1 && path[0] instanceof JPopupMenu) { | ||
| 546 | |||
| 547 | JPopupMenu popup = (JPopupMenu) path[0]; | ||
| 548 | MenuElement[] newPath = new MenuElement[2]; | ||
| 549 | newPath[0] = popup; | ||
| 550 | newPath[1] = findEnabledChild(popup.getSubElements(), -1, direction); | ||
| 551 | msm.setSelectedPath(newPath); | ||
| 552 | } else if (len == 2 && | ||
| 553 | path[0] instanceof JMenuBar && path[1] instanceof JMenu) { | ||
| 554 | |||
| 555 | // a toplevel menu is selected, but its popup not shown. | ||
| 556 | // Show the popup and select the first item | ||
| 557 | JPopupMenu popup = ((JMenu)path[1]).getPopupMenu(); | ||
| 558 | MenuElement next = | ||
| 559 | findEnabledChild(popup.getSubElements(), -1, FORWARD); | ||
| 560 | MenuElement[] newPath; | ||
| 561 | |||
| 562 | if (next != null) { | ||
| 563 | // an enabled item found -- include it in newPath | ||
| 564 | newPath = new MenuElement[4]; | ||
| 565 | newPath[3] = next; | ||
| 566 | } else { | ||
| 567 | // menu has no enabled items -- still must show the popup | ||
| 568 | newPath = new MenuElement[3]; | ||
| 569 | } | ||
| 570 | System.arraycopy(path, 0, newPath, 0, 2); | ||
| 571 | newPath[2] = popup; | ||
| 572 | msm.setSelectedPath(newPath); | ||
| 573 | |||
| 574 | } else if (path[len-1] instanceof JPopupMenu && | ||
| 575 | path[len-2] instanceof JMenu) { | ||
| 576 | |||
| 577 | // a menu (not necessarily toplevel) is open and its popup | ||
| 578 | // shown. Select the appropriate menu item | ||
| 579 | JMenu menu = (JMenu)path[len-2]; | ||
| 580 | JPopupMenu popup = menu.getPopupMenu(); | ||
| 581 | MenuElement next = | ||
| 582 | findEnabledChild(popup.getSubElements(), -1, direction); | ||
| 583 | |||
| 584 | if (next != null) { | ||
| 585 | MenuElement[] newPath = new MenuElement[len+1]; | ||
| 586 | System.arraycopy(path, 0, newPath, 0, len); | ||
| 587 | newPath[len] = next; | ||
| 588 | msm.setSelectedPath(newPath); | ||
| 589 | } else { | ||
| 590 | // all items in the popup are disabled. | ||
| 591 | // We're going to find the parent popup menu and select | ||
| 592 | // its next item. If there's no parent popup menu (i.e. | ||
| 593 | // current menu is toplevel), do nothing | ||
| 594 | if (len > 2 && path[len-3] instanceof JPopupMenu) { | ||
| 595 | popup = ((JPopupMenu)path[len-3]); | ||
| 596 | next = findEnabledChild(popup.getSubElements(), | ||
| 597 | menu, direction); | ||
| 598 | |||
| 599 | if (next != null && next != menu) { | ||
| 600 | MenuElement[] newPath = new MenuElement[len-1]; | ||
| 601 | System.arraycopy(path, 0, newPath, 0, len-2); | ||
| 602 | newPath[len-2] = next; | ||
| 603 | msm.setSelectedPath(newPath); | ||
| 604 | } | ||
| 605 | } | ||
| 606 | } | ||
| 607 | |||
| 608 | } else { | ||
| 609 | // just select the next item, no path expansion needed | ||
| 610 | MenuElement subs[] = path[len-2].getSubElements(); | ||
| 611 | MenuElement nextChild = | ||
| 612 | findEnabledChild(subs, path[len-1], direction); | ||
| 613 | if (nextChild == null) { | ||
| 614 | nextChild = findEnabledChild(subs, -1, direction); | ||
| 615 | } | ||
| 616 | if (nextChild != null) { | ||
| 617 | path[len-1] = nextChild; | ||
| 618 | msm.setSelectedPath(path); | ||
| 619 | } | ||
| 620 | } | ||
| 621 | } | ||
| 622 | |||
| 623 | private void cancel() { | ||
| 624 | // 4234793: This action should call JPopupMenu.firePopupMenuCanceled but it's | ||
| 625 | // a protected method. The real solution could be to make | ||
| 626 | // firePopupMenuCanceled public and call it directly. | ||
| 627 | JPopupMenu lastPopup = getLastPopup(); | ||
| 628 | if (lastPopup != null) { | ||
| 629 | lastPopup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE); | ||
| 630 | } | ||
| 631 | String mode = UIManager.getString("Menu.cancelMode"); | ||
| 632 | if ("hideMenuTree".equals(mode)) { | ||
| 633 | MenuSelectionManager.defaultManager().clearSelectedPath(); | ||
| 634 | } else { | ||
| 635 | shortenSelectedPath(); | ||
| 636 | } | ||
| 637 | } | ||
| 638 | |||
| 639 | private void shortenSelectedPath() { | ||
| 640 | MenuElement path[] = MenuSelectionManager.defaultManager().getSelectedPath(); | ||
| 641 | if (path.length <= 2) { | ||
| 642 | MenuSelectionManager.defaultManager().clearSelectedPath(); | ||
| 643 | return; | ||
| 644 | } | ||
| 645 | // unselect MenuItem and its Popup by default | ||
| 646 | int value = 2; | ||
| 647 | MenuElement lastElement = path[path.length - 1]; | ||
| 648 | JPopupMenu lastPopup = getLastPopup(); | ||
| 649 | if (lastElement == lastPopup) { | ||
| 650 | MenuElement previousElement = path[path.length - 2]; | ||
| 651 | if (previousElement instanceof JMenu) { | ||
| 652 | JMenu lastMenu = (JMenu) previousElement; | ||
| 653 | if (lastMenu.isEnabled() && lastPopup.getComponentCount() > 0) { | ||
| 654 | // unselect the last visible popup only | ||
| 655 | value = 1; | ||
| 656 | } else { | ||
| 657 | // unselect invisible popup and two visible elements | ||
| 658 | value = 3; | ||
| 659 | } | ||
| 660 | } | ||
| 661 | } | ||
| 662 | if (path.length - value <= 2 | ||
| 663 | && !UIManager.getBoolean("Menu.preserveTopLevelSelection")) { | ||
| 664 | // clear selection for the topLevelMenu | ||
| 665 | value = path.length; | ||
| 666 | } | ||
| 667 | MenuElement newPath[] = new MenuElement[path.length - value]; | ||
| 668 | System.arraycopy(path, 0, newPath, 0, path.length - value); | ||
| 669 | MenuSelectionManager.defaultManager().setSelectedPath(newPath); | ||
| 670 | } | ||
| 671 | } | ||
| 672 | |||
| 673 | private static MenuElement nextEnabledChild(MenuElement e[], | ||
| 674 | int fromIndex, int toIndex) { | ||
| 675 | for (int i=fromIndex; i<=toIndex; i++) { | ||
| 676 | if (e[i] != null) { | ||
| 677 | Component comp = e[i].getComponent(); | ||
| 678 | if ( comp != null | ||
| 679 | && (comp.isEnabled() || UIManager.getBoolean("MenuItem.disabledAreNavigable")) | ||
| 680 | && comp.isVisible()) { | ||
| 681 | return e[i]; | ||
| 682 | } | ||
| 683 | } | ||
| 684 | } | ||
| 685 | return null; | ||
| 686 | } | ||
| 687 | |||
| 688 | private static MenuElement previousEnabledChild(MenuElement e[], | ||
| 689 | int fromIndex, int toIndex) { | ||
| 690 | for (int i=fromIndex; i>=toIndex; i--) { | ||
| 691 | if (e[i] != null) { | ||
| 692 | Component comp = e[i].getComponent(); | ||
| 693 | if ( comp != null | ||
| 694 | && (comp.isEnabled() || UIManager.getBoolean("MenuItem.disabledAreNavigable")) | ||
| 695 | && comp.isVisible()) { | ||
| 696 | return e[i]; | ||
| 697 | } | ||
| 698 | } | ||
| 699 | } | ||
| 700 | return null; | ||
| 701 | } | ||
| 702 | |||
| 703 | static MenuElement findEnabledChild(MenuElement e[], int fromIndex, | ||
| 704 | boolean forward) { | ||
| 705 | MenuElement result; | ||
| 706 | if (forward) { | ||
| 707 | result = nextEnabledChild(e, fromIndex+1, e.length-1); | ||
| 708 | if (result == null) result = nextEnabledChild(e, 0, fromIndex-1); | ||
| 709 | } else { | ||
| 710 | result = previousEnabledChild(e, fromIndex-1, 0); | ||
| 711 | if (result == null) result = previousEnabledChild(e, e.length-1, | ||
| 712 | fromIndex+1); | ||
| 713 | } | ||
| 714 | return result; | ||
| 715 | } | ||
| 716 | |||
| 717 | static MenuElement findEnabledChild(MenuElement e[], | ||
| 718 | MenuElement elem, boolean forward) { | ||
| 719 | for (int i=0; i<e.length; i++) { | ||
| 720 | if (e[i] == elem) { | ||
| 721 | return findEnabledChild(e, i, forward); | ||
| 722 | } | ||
| 723 | } | ||
| 724 | return null; | ||
| 725 | } | ||
| 726 | |||
| 727 | static class MouseGrabber implements ChangeListener, | ||
| 728 | AWTEventListener, ComponentListener, WindowListener { | ||
| 729 | |||
| 730 | Window grabbedWindow; | ||
| 731 | MenuElement[] lastPathSelected; | ||
| 732 | |||
| 733 | public MouseGrabber() { | ||
| 734 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 735 | msm.addChangeListener(this); | ||
| 736 | this.lastPathSelected = msm.getSelectedPath(); | ||
| 737 | if(this.lastPathSelected.length != 0) { | ||
| 738 | grabWindow(this.lastPathSelected); | ||
| 739 | } | ||
| 740 | } | ||
| 741 | |||
| 742 | void uninstall() { | ||
| 743 | synchronized (MOUSE_GRABBER_KEY) { | ||
| 744 | MenuSelectionManager.defaultManager().removeChangeListener(this); | ||
| 745 | ungrabWindow(); | ||
| 746 | AppContext.getAppContext().remove(MOUSE_GRABBER_KEY); | ||
| 747 | } | ||
| 748 | } | ||
| 749 | |||
| 750 | void grabWindow(MenuElement[] newPath) { | ||
| 751 | // A grab needs to be added | ||
| 752 | final Toolkit tk = Toolkit.getDefaultToolkit(); | ||
| 753 | java.security.AccessController.doPrivileged( | ||
| 754 | new java.security.PrivilegedAction<Object>() { | ||
| 755 | public Object run() { | ||
| 756 | tk.addAWTEventListener(MouseGrabber.this, | ||
| 757 | AWTEvent.MOUSE_EVENT_MASK | | ||
| 758 | AWTEvent.MOUSE_MOTION_EVENT_MASK | | ||
| 759 | AWTEvent.MOUSE_WHEEL_EVENT_MASK | | ||
| 760 | AWTEvent.WINDOW_EVENT_MASK | sun.awt.SunToolkit.GRAB_EVENT_MASK); | ||
| 761 | return null; | ||
| 762 | } | ||
| 763 | } | ||
| 764 | ); | ||
| 765 | |||
| 766 | Component invoker = newPath[0].getComponent(); | ||
| 767 | if (invoker instanceof JPopupMenu) { | ||
| 768 | invoker = ((JPopupMenu)invoker).getInvoker(); | ||
| 769 | } | ||
| 770 | grabbedWindow = invoker instanceof Window? | ||
| 771 | (Window)invoker : | ||
| 772 | SwingUtilities.getWindowAncestor(invoker); | ||
| 773 | if(grabbedWindow != null) { | ||
| 774 | if(tk instanceof sun.awt.SunToolkit) { | ||
| 775 | ((sun.awt.SunToolkit)tk).grab(grabbedWindow); | ||
| 776 | } else { | ||
| 777 | grabbedWindow.addComponentListener(this); | ||
| 778 | grabbedWindow.addWindowListener(this); | ||
| 779 | } | ||
| 780 | } | ||
| 781 | } | ||
| 782 | |||
| 783 | void ungrabWindow() { | ||
| 784 | final Toolkit tk = Toolkit.getDefaultToolkit(); | ||
| 785 | // The grab should be removed | ||
| 786 | java.security.AccessController.doPrivileged( | ||
| 787 | new java.security.PrivilegedAction<Object>() { | ||
| 788 | public Object run() { | ||
| 789 | tk.removeAWTEventListener(MouseGrabber.this); | ||
| 790 | return null; | ||
| 791 | } | ||
| 792 | } | ||
| 793 | ); | ||
| 794 | realUngrabWindow(); | ||
| 795 | } | ||
| 796 | |||
| 797 | void realUngrabWindow() { | ||
| 798 | Toolkit tk = Toolkit.getDefaultToolkit(); | ||
| 799 | if(grabbedWindow != null) { | ||
| 800 | if(tk instanceof sun.awt.SunToolkit) { | ||
| 801 | ((sun.awt.SunToolkit)tk).ungrab(grabbedWindow); | ||
| 802 | } else { | ||
| 803 | grabbedWindow.removeComponentListener(this); | ||
| 804 | grabbedWindow.removeWindowListener(this); | ||
| 805 | } | ||
| 806 | grabbedWindow = null; | ||
| 807 | } | ||
| 808 | } | ||
| 809 | |||
| 810 | public void stateChanged(ChangeEvent e) { | ||
| 811 | MenuSelectionManager msm = MenuSelectionManager.defaultManager(); | ||
| 812 | MenuElement[] p = msm.getSelectedPath(); | ||
| 813 | |||
| 814 | if (lastPathSelected.length == 0 && p.length != 0) { | ||
| 815 | grabWindow(p); | ||
| 816 | } | ||
| 817 | |||
| 818 | if (lastPathSelected.length != 0 && p.length == 0) { | ||
| 819 | ungrabWindow(); | ||
| 820 | } | ||
| 821 | |||
| 822 | lastPathSelected = p; | ||
| 823 | } | ||
| 824 | |||
| 825 | public void eventDispatched(AWTEvent ev) { | ||
| 826 | if(ev instanceof sun.awt.UngrabEvent) { | ||
| 827 | // Popup should be canceled in case of ungrab event | ||
| 828 | cancelPopupMenu( ); | ||
| 829 | return; | ||
| 830 | } | ||
| 831 | if (!(ev instanceof MouseEvent)) { | ||
| 832 | // We are interested in MouseEvents only | ||
| 833 | return; | ||
| 834 | } | ||
| 835 | MouseEvent me = (MouseEvent) ev; | ||
| 836 | Component src = me.getComponent(); | ||
| 837 | switch (me.getID()) { | ||
| 838 | case MouseEvent.MOUSE_PRESSED: | ||
| 839 | if (isInPopup(src) || | ||
| 840 | (src instanceof JMenu && ((JMenu)src).isSelected())) { | ||
| 841 | return; | ||
| 842 | } | ||
| 843 | if (!(src instanceof JComponent) || | ||
| 844 | ! (((JComponent)src).getClientProperty("doNotCancelPopup") | ||
| 845 | == BasicComboBoxUI.HIDE_POPUP_KEY)) { | ||
| 846 | // Cancel popup only if this property was not set. | ||
| 847 | // If this property is set to TRUE component wants | ||
| 848 | // to deal with this event by himself. | ||
| 849 | cancelPopupMenu(); | ||
| 850 | // Ask UIManager about should we consume event that closes | ||
| 851 | // popup. This made to match native apps behaviour. | ||
| 852 | boolean consumeEvent = | ||
| 853 | UIManager.getBoolean("PopupMenu.consumeEventOnClose"); | ||
| 854 | // Consume the event so that normal processing stops. | ||
| 855 | if(consumeEvent && !(src instanceof MenuElement)) { | ||
| 856 | me.consume(); | ||
| 857 | } | ||
| 858 | } | ||
| 859 | break; | ||
| 860 | |||
| 861 | case MouseEvent.MOUSE_RELEASED: | ||
| 862 | if(!(src instanceof MenuElement)) { | ||
| 863 | // Do not forward event to MSM, let component handle it | ||
| 864 | if (isInPopup(src)) { | ||
| 865 | break; | ||
| 866 | } | ||
| 867 | } | ||
| 868 | if(src instanceof JMenu || !(src instanceof JMenuItem)) { | ||
| 869 | MenuSelectionManager.defaultManager(). | ||
| 870 | processMouseEvent(me); | ||
| 871 | } | ||
| 872 | break; | ||
| 873 | case MouseEvent.MOUSE_DRAGGED: | ||
| 874 | if(!(src instanceof MenuElement)) { | ||
| 875 | // For the MOUSE_DRAGGED event the src is | ||
| 876 | // the Component in which mouse button was pressed. | ||
| 877 | // If the src is in popupMenu, | ||
| 878 | // do not forward event to MSM, let component handle it. | ||
| 879 | if (isInPopup(src)) { | ||
| 880 | break; | ||
| 881 | } | ||
| 882 | } | ||
| 883 | MenuSelectionManager.defaultManager(). | ||
| 884 | processMouseEvent(me); | ||
| 885 | break; | ||
| 886 | case MouseEvent.MOUSE_WHEEL: | ||
| 887 | if (isInPopup(src)) { | ||
| 888 | return; | ||
| 889 | } | ||
| 890 | cancelPopupMenu(); | ||
| 891 | break; | ||
| 892 | } | ||
| 893 | } | ||
| 894 | |||
| 895 | boolean isInPopup(Component src) { | ||
| 896 | for (Component c=src; c!=null; c=c.getParent()) { | ||
| 897 | if (c instanceof Applet || c instanceof Window) { | ||
| 898 | break; | ||
| 899 | } else if (c instanceof JPopupMenu) { | ||
| 900 | return true; | ||
| 901 | } | ||
| 902 | } | ||
| 903 | return false; | ||
| 904 | } | ||
| 905 | |||
| 906 | void cancelPopupMenu() { | ||
| 907 | // We should ungrab window if a user code throws | ||
| 908 | // an unexpected runtime exception. See 6495920. | ||
| 909 | try { | ||
| 910 | // 4234793: This action should call firePopupMenuCanceled but it's | ||
| 911 | // a protected method. The real solution could be to make | ||
| 912 | // firePopupMenuCanceled public and call it directly. | ||
| 913 | List<JPopupMenu> popups = getPopups(); | ||
| 914 | for (JPopupMenu popup : popups) { | ||
| 915 | popup.putClientProperty("JPopupMenu.firePopupMenuCanceled", Boolean.TRUE); | ||
| 916 | } | ||
| 917 | MenuSelectionManager.defaultManager().clearSelectedPath(); | ||
| 918 | } catch (RuntimeException ex) { | ||
| 919 | realUngrabWindow(); | ||
| 920 | throw ex; | ||
| 921 | } catch (Error err) { | ||
| 922 | realUngrabWindow(); | ||
| 923 | throw err; | ||
| 924 | } | ||
| 925 | } | ||
| 926 | |||
| 927 | public void componentResized(ComponentEvent e) { | ||
| 928 | cancelPopupMenu(); | ||
| 929 | } | ||
| 930 | public void componentMoved(ComponentEvent e) { | ||
| 931 | cancelPopupMenu(); | ||
| 932 | } | ||
| 933 | public void componentShown(ComponentEvent e) { | ||
| 934 | cancelPopupMenu(); | ||
| 935 | } | ||
| 936 | public void componentHidden(ComponentEvent e) { | ||
| 937 | cancelPopupMenu(); | ||
| 938 | } | ||
| 939 | public void windowClosing(WindowEvent e) { | ||
| 940 | cancelPopupMenu(); | ||
| 941 | } | ||
| 942 | public void windowClosed(WindowEvent e) { | ||
| 943 | cancelPopupMenu(); | ||
| 944 | } | ||
| 945 | public void windowIconified(WindowEvent e) { | ||
| 946 | cancelPopupMenu(); | ||
| 947 | } | ||
| 948 | public void windowDeactivated(WindowEvent e) { | ||
| 949 | cancelPopupMenu(); | ||
| 950 | } | ||
| 951 | public void windowOpened(WindowEvent e) {} | ||
| 952 | public void windowDeiconified(WindowEvent e) {} | ||
| 953 | public void windowActivated(WindowEvent e) {} | ||
| 954 | } | ||
| 955 | |||
| 956 | /** | ||
| 957 | * This helper is added to MenuSelectionManager as a ChangeListener to | ||
| 958 | * listen to menu selection changes. When a menu is activated, it passes | ||
| 959 | * focus to its parent JRootPane, and installs an ActionMap/InputMap pair | ||
| 960 | * on that JRootPane. Those maps are necessary in order for menu | ||
| 961 | * navigation to work. When menu is being deactivated, it restores focus | ||
| 962 | * to the component that has had it before menu activation, and uninstalls | ||
| 963 | * the maps. | ||
| 964 | * This helper is also installed as a KeyListener on root pane when menu | ||
| 965 | * is active. It forwards key events to MenuSelectionManager for mnemonic | ||
| 966 | * keys handling. | ||
| 967 | */ | ||
| 968 | static class MenuKeyboardHelper | ||
| 969 | implements ChangeListener, KeyListener { | ||
| 970 | |||
| 971 | private Component lastFocused = null; | ||
| 972 | private MenuElement[] lastPathSelected = new MenuElement[0]; | ||
| 973 | private JPopupMenu lastPopup; | ||
| 974 | |||
| 975 | private JRootPane invokerRootPane; | ||
| 976 | private ActionMap menuActionMap = getActionMap(); | ||
| 977 | private InputMap menuInputMap; | ||
| 978 | private boolean focusTraversalKeysEnabled; | ||
| 979 | |||
| 980 | /* | ||
| 981 | * Fix for 4213634 | ||
| 982 | * If this is false, KEY_TYPED and KEY_RELEASED events are NOT | ||
| 983 | * processed. This is needed to avoid activating a menuitem when | ||
| 984 | * the menu and menuitem share the same mnemonic. | ||
| 985 | */ | ||
| 986 | private boolean receivedKeyPressed = false; | ||
| 987 | |||
| 988 | void removeItems() { | ||
| 989 | if (lastFocused != null) { | ||
| 990 | if(!lastFocused.requestFocusInWindow()) { | ||
| 991 | // Workarounr for 4810575. | ||
| 992 | // If lastFocused is not in currently focused window | ||
| 993 | // requestFocusInWindow will fail. In this case we must | ||
| 994 | // request focus by requestFocus() if it was not | ||
| 995 | // transferred from our popup. | ||
| 996 | Window cfw = KeyboardFocusManager | ||
| 997 | .getCurrentKeyboardFocusManager() | ||
| 998 | .getFocusedWindow(); | ||
| 999 | if(cfw != null && | ||
| 1000 | "###focusableSwingPopup###".equals(cfw.getName())) { | ||
| 1001 | lastFocused.requestFocus(); | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | } | ||
| 1005 | lastFocused = null; | ||
| 1006 | } | ||
| 1007 | if (invokerRootPane != null) { | ||
| 1008 | invokerRootPane.removeKeyListener(this); | ||
| 1009 | invokerRootPane.setFocusTraversalKeysEnabled(focusTraversalKeysEnabled); | ||
| 1010 | removeUIInputMap(invokerRootPane, menuInputMap); | ||
| 1011 | removeUIActionMap(invokerRootPane, menuActionMap); | ||
| 1012 | invokerRootPane = null; | ||
| 1013 | } | ||
| 1014 | receivedKeyPressed = false; | ||
| 1015 | } | ||
| 1016 | |||
| 1017 | private FocusListener rootPaneFocusListener = new FocusAdapter() { | ||
| 1018 | public void focusGained(FocusEvent ev) { | ||
| 1019 | Component opposite = ev.getOppositeComponent(); | ||
| 1020 | if (opposite != null) { | ||
| 1021 | lastFocused = opposite; | ||
| 1022 | } | ||
| 1023 | ev.getComponent().removeFocusListener(this); | ||
| 1024 | } | ||
| 1025 | }; | ||
| 1026 | |||
| 1027 | /** | ||
| 1028 | * Return the last JPopupMenu in <code>path</code>, | ||
| 1029 | * or <code>null</code> if none found | ||
| 1030 | */ | ||
| 1031 | JPopupMenu getActivePopup(MenuElement[] path) { | ||
| 1032 | for (int i=path.length-1; i>=0; i--) { | ||
| 1033 | MenuElement elem = path[i]; | ||
| 1034 | if (elem instanceof JPopupMenu) { | ||
| 1035 | return (JPopupMenu)elem; | ||
| 1036 | } | ||
| 1037 | } | ||
| 1038 | return null; | ||
| 1039 | } | ||
| 1040 | |||
| 1041 | void addUIInputMap(JComponent c, InputMap map) { | ||
| 1042 | InputMap lastNonUI = null; | ||
| 1043 | InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); | ||
| 1044 | |||
| 1045 | while (parent != null && !(parent instanceof UIResource)) { | ||
| 1046 | lastNonUI = parent; | ||
| 1047 | parent = parent.getParent(); | ||
| 1048 | } | ||
| 1049 | |||
| 1050 | if (lastNonUI == null) { | ||
| 1051 | c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, map); | ||
| 1052 | } else { | ||
| 1053 | lastNonUI.setParent(map); | ||
| 1054 | } | ||
| 1055 | map.setParent(parent); | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | void addUIActionMap(JComponent c, ActionMap map) { | ||
| 1059 | ActionMap lastNonUI = null; | ||
| 1060 | ActionMap parent = c.getActionMap(); | ||
| 1061 | |||
| 1062 | while (parent != null && !(parent instanceof UIResource)) { | ||
| 1063 | lastNonUI = parent; | ||
| 1064 | parent = parent.getParent(); | ||
| 1065 | } | ||
| 1066 | |||
| 1067 | if (lastNonUI == null) { | ||
| 1068 | c.setActionMap(map); | ||
| 1069 | } else { | ||
| 1070 | lastNonUI.setParent(map); | ||
| 1071 | } | ||
| 1072 | map.setParent(parent); | ||
| 1073 | } | ||
| 1074 | |||
| 1075 | void removeUIInputMap(JComponent c, InputMap map) { | ||
| 1076 | InputMap im = null; | ||
| 1077 | InputMap parent = c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); | ||
| 1078 | |||
| 1079 | while (parent != null) { | ||
| 1080 | if (parent == map) { | ||
| 1081 | if (im == null) { | ||
| 1082 | c.setInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW, | ||
| 1083 | map.getParent()); | ||
| 1084 | } else { | ||
| 1085 | im.setParent(map.getParent()); | ||
| 1086 | } | ||
| 1087 | break; | ||
| 1088 | } | ||
| 1089 | im = parent; | ||
| 1090 | parent = parent.getParent(); | ||
| 1091 | } | ||
| 1092 | } | ||
| 1093 | |||
| 1094 | void removeUIActionMap(JComponent c, ActionMap map) { | ||
| 1095 | ActionMap im = null; | ||
| 1096 | ActionMap parent = c.getActionMap(); | ||
| 1097 | |||
| 1098 | while (parent != null) { | ||
| 1099 | if (parent == map) { | ||
| 1100 | if (im == null) { | ||
| 1101 | c.setActionMap(map.getParent()); | ||
| 1102 | } else { | ||
| 1103 | im.setParent(map.getParent()); | ||
| 1104 | } | ||
| 1105 | break; | ||
| 1106 | } | ||
| 1107 | im = parent; | ||
| 1108 | parent = parent.getParent(); | ||
| 1109 | } | ||
| 1110 | } | ||
| 1111 | |||
| 1112 | public void stateChanged(ChangeEvent ev) { | ||
| 1113 | if (!(UIManager.getLookAndFeel() instanceof BasicLookAndFeel)) { | ||
| 1114 | uninstall(); | ||
| 1115 | return; | ||
| 1116 | } | ||
| 1117 | MenuSelectionManager msm = (MenuSelectionManager)ev.getSource(); | ||
| 1118 | MenuElement[] p = msm.getSelectedPath(); | ||
| 1119 | JPopupMenu popup = getActivePopup(p); | ||
| 1120 | if (popup != null && !popup.isFocusable()) { | ||
| 1121 | // Do nothing for non-focusable popups | ||
| 1122 | return; | ||
| 1123 | } | ||
| 1124 | |||
| 1125 | if (lastPathSelected.length != 0 && p.length != 0 ) { | ||
| 1126 | if (!checkInvokerEqual(p[0],lastPathSelected[0])) { | ||
| 1127 | removeItems(); | ||
| 1128 | lastPathSelected = new MenuElement[0]; | ||
| 1129 | } | ||
| 1130 | } | ||
| 1131 | |||
| 1132 | if (lastPathSelected.length == 0 && p.length > 0) { | ||
| 1133 | // menu posted | ||
| 1134 | JComponent invoker; | ||
| 1135 | |||
| 1136 | if (popup == null) { | ||
| 1137 | if (p.length == 2 && p[0] instanceof JMenuBar && | ||
| 1138 | p[1] instanceof JMenu) { | ||
| 1139 | // a menu has been selected but not open | ||
| 1140 | invoker = (JComponent)p[1]; | ||
| 1141 | popup = ((JMenu)invoker).getPopupMenu(); | ||
| 1142 | } else { | ||
| 1143 | return; | ||
| 1144 | } | ||
| 1145 | } else { | ||
| 1146 | Component c = popup.getInvoker(); | ||
| 1147 | if(c instanceof JFrame) { | ||
| 1148 | invoker = ((JFrame)c).getRootPane(); | ||
| 1149 | } else if(c instanceof JDialog) { | ||
| 1150 | invoker = ((JDialog)c).getRootPane(); | ||
| 1151 | } else if(c instanceof JApplet) { | ||
| 1152 | invoker = ((JApplet)c).getRootPane(); | ||
| 1153 | } else { | ||
| 1154 | while (!(c instanceof JComponent)) { | ||
| 1155 | if (c == null) { | ||
| 1156 | return; | ||
| 1157 | } | ||
| 1158 | c = c.getParent(); | ||
| 1159 | } | ||
| 1160 | invoker = (JComponent)c; | ||
| 1161 | } | ||
| 1162 | } | ||
| 1163 | |||
| 1164 | // remember current focus owner | ||
| 1165 | lastFocused = KeyboardFocusManager. | ||
| 1166 | getCurrentKeyboardFocusManager().getFocusOwner(); | ||
| 1167 | |||
| 1168 | // request focus on root pane and install keybindings | ||
| 1169 | // used for menu navigation | ||
| 1170 | invokerRootPane = SwingUtilities.getRootPane(invoker); | ||
| 1171 | if (invokerRootPane != null) { | ||
| 1172 | invokerRootPane.addFocusListener(rootPaneFocusListener); | ||
| 1173 | invokerRootPane.requestFocus(true); | ||
| 1174 | invokerRootPane.addKeyListener(this); | ||
| 1175 | focusTraversalKeysEnabled = invokerRootPane. | ||
| 1176 | getFocusTraversalKeysEnabled(); | ||
| 1177 | invokerRootPane.setFocusTraversalKeysEnabled(false); | ||
| 1178 | |||
| 1179 | menuInputMap = getInputMap(popup, invokerRootPane); | ||
| 1180 | addUIInputMap(invokerRootPane, menuInputMap); | ||
| 1181 | addUIActionMap(invokerRootPane, menuActionMap); | ||
| 1182 | } | ||
| 1183 | } else if (lastPathSelected.length != 0 && p.length == 0) { | ||
| 1184 | // menu hidden -- return focus to where it had been before | ||
| 1185 | // and uninstall menu keybindings | ||
| 1186 | removeItems(); | ||
| 1187 | } else { | ||
| 1188 | if (popup != lastPopup) { | ||
| 1189 | receivedKeyPressed = false; | ||
| 1190 | } | ||
| 1191 | } | ||
| 1192 | |||
| 1193 | // Remember the last path selected | ||
| 1194 | lastPathSelected = p; | ||
| 1195 | lastPopup = popup; | ||
| 1196 | } | ||
| 1197 | |||
| 1198 | public void keyPressed(KeyEvent ev) { | ||
| 1199 | receivedKeyPressed = true; | ||
| 1200 | MenuSelectionManager.defaultManager().processKeyEvent(ev); | ||
| 1201 | } | ||
| 1202 | |||
| 1203 | public void keyReleased(KeyEvent ev) { | ||
| 1204 | if (receivedKeyPressed) { | ||
| 1205 | receivedKeyPressed = false; | ||
| 1206 | MenuSelectionManager.defaultManager().processKeyEvent(ev); | ||
| 1207 | } | ||
| 1208 | } | ||
| 1209 | |||
| 1210 | public void keyTyped(KeyEvent ev) { | ||
| 1211 | if (receivedKeyPressed) { | ||
| 1212 | MenuSelectionManager.defaultManager().processKeyEvent(ev); | ||
| 1213 | } | ||
| 1214 | } | ||
| 1215 | |||
| 1216 | void uninstall() { | ||
| 1217 | synchronized (MENU_KEYBOARD_HELPER_KEY) { | ||
| 1218 | MenuSelectionManager.defaultManager().removeChangeListener(this); | ||
| 1219 | AppContext.getAppContext().remove(MENU_KEYBOARD_HELPER_KEY); | ||
| 1220 | } | ||
| 1221 | } | ||
| 1222 | } | ||
| 1223 | } | ||