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+
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+}