History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/plaf/basic/BasicPopupMenuUI.java (1 of 18352 files). Show all

Unified · Split

java/pkg/javax/swing/plaf/basic/BasicPopupMenuUI.java+1223−0
@@ -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
26package javax.swing.plaf.basic;
27
28import javax.swing.*;
29import javax.swing.event.*;
30import javax.swing.plaf.*;
31import javax.swing.plaf.basic.*;
32import javax.swing.border.*;
33
34import java.applet.Applet;
35
36import java.awt.Component;
37import java.awt.Container;
38import java.awt.Dimension;
39import java.awt.KeyboardFocusManager;
40import java.awt.Window;
41import java.awt.event.*;
42import java.awt.AWTEvent;
43import java.awt.Toolkit;
44
45import java.beans.PropertyChangeListener;
46import java.beans.PropertyChangeEvent;
47
48import java.util.*;
49
50import sun.swing.DefaultLookup;
51import sun.swing.UIAction;
52
53import 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 */
63public 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}