001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Alexander T. Simbirtsev
019: * @version $Revision$
020: */package javax.swing.plaf.basic;
021:
022: import java.awt.Component;
023: import java.awt.KeyEventDispatcher;
024: import java.awt.Toolkit;
025: import java.awt.AWTEvent;
026: import java.awt.event.ActionEvent;
027: import java.awt.event.KeyEvent;
028: import java.awt.event.AWTEventListener;
029: import java.lang.reflect.InvocationTargetException;
030: import java.lang.reflect.Method;
031: import java.util.HashMap;
032:
033: import javax.swing.AbstractAction;
034: import javax.swing.Action;
035: import javax.swing.DefaultFocusManager;
036: import javax.swing.JMenu;
037: import javax.swing.JMenuBar;
038: import javax.swing.JMenuItem;
039: import javax.swing.JPopupMenu;
040: import javax.swing.KeyStroke;
041: import javax.swing.MenuElement;
042: import javax.swing.MenuSelectionManager;
043: import javax.swing.SwingUtilities;
044:
045: import org.apache.harmony.x.swing.Utilities;
046:
047: class MenuKeyBindingProcessor implements KeyEventDispatcher {
048:
049: private abstract static class GenericNavigationAction extends
050: AbstractAction {
051: protected static final int FORWARD = 1;
052: protected static final int BACKWARD = -1;
053:
054: public GenericNavigationAction(final String name) {
055: super (name);
056: }
057:
058: protected final int getPopupIndex(final MenuElement[] path,
059: final JPopupMenu popup) {
060: int index = path.length - 1;
061: while (index >= 0 && path[index] != popup) {
062: index--;
063: }
064: return index;
065: }
066:
067: protected final MenuElement[] derivePath(
068: final MenuElement[] path, final int length) {
069: return derivePath(path, length, length);
070: }
071:
072: protected final MenuElement[] derivePath(
073: final MenuElement[] path, final int newLength,
074: final int copyLength) {
075:
076: final MenuElement[] result = new MenuElement[newLength];
077: System.arraycopy(path, 0, result, 0, copyLength);
078: return result;
079: }
080:
081: protected final MenuElement[] getSelectedPath() {
082: return MenuSelectionManager.defaultManager()
083: .getSelectedPath();
084: }
085:
086: protected final void setSelectedPath(final MenuElement[] path) {
087: MenuSelectionManager.defaultManager().setSelectedPath(path);
088: }
089: }
090:
091: private abstract static class ChildParentAction extends
092: GenericNavigationAction {
093: private final int direction;
094:
095: public ChildParentAction(final String name, final int direction) {
096: super (name);
097: this .direction = direction;
098: }
099:
100: protected void selectNextTopLevelMenu(final MenuElement[] path) {
101: if (Utilities.isEmptyArray(path)
102: || !(path[0] instanceof JMenuBar)) {
103: return;
104: }
105: final JMenuBar menuBar = (JMenuBar) path[0];
106: final JMenu menu = (JMenu) path[1];
107: final int menuIndex = menuBar.getComponentIndex(menu);
108: final JMenu nextMenu = getNextMenu(menuBar, menuIndex);
109: if (nextMenu != null && nextMenu != menu) {
110: setSelectedPath(new MenuElement[] { menuBar, nextMenu,
111: nextMenu.getPopupMenu() });
112: }
113: }
114:
115: private JMenu getNextMenu(final JMenuBar menuBar,
116: final int index) {
117: final int numElements = menuBar.getMenuCount();
118: for (int i = 1; i <= numElements; i++) {
119: int j = (direction > 0 ? index + i : numElements
120: + index - i)
121: % numElements;
122: final JMenu menu = menuBar.getMenu(j);
123: if (menu != null) {
124: return menu;
125: }
126: }
127: return null;
128: }
129: }
130:
131: private static final Action SELECT_PARENT_ACTION = new ChildParentAction(
132: "selectParent", GenericNavigationAction.BACKWARD) {
133: public void actionPerformed(final ActionEvent e) {
134: final JPopupMenu popup = (JPopupMenu) e.getSource();
135: final MenuElement[] path = getSelectedPath();
136: if (path.length > 3) {
137: int index = getPopupIndex(path, popup);
138: JMenu menu = (JMenu) path[index - 1];
139: if (!menu.isTopLevelMenu()) {
140: setSelectedPath(derivePath(path, index));
141: return;
142: }
143: }
144: selectNextTopLevelMenu(path);
145: }
146: };
147:
148: private static final Action SELECT_CHILD_ACTION = new ChildParentAction(
149: "selectChild", GenericNavigationAction.FORWARD) {
150: public void actionPerformed(final ActionEvent e) {
151: final MenuElement[] path = getSelectedPath();
152: if (path.length > 3 || path[0] instanceof JPopupMenu) {
153: final MenuElement lastElement = path[path.length - 1];
154: final MenuElement[] subElements = lastElement
155: .getSubElements();
156: if (!Utilities.isEmptyArray(subElements)) {
157: MenuElement[] newPath;
158: final MenuElement newSelectedItem = subElements[0];
159: final MenuElement newSelectedChild = Utilities
160: .getFirstSelectableItem(newSelectedItem
161: .getSubElements());
162: if (newSelectedItem instanceof JPopupMenu
163: && newSelectedChild != null) {
164: newPath = derivePath(path, path.length + 2,
165: path.length);
166: newPath[path.length + 1] = newSelectedChild;
167: } else {
168: newPath = derivePath(path, path.length + 1,
169: path.length);
170: }
171: newPath[path.length] = newSelectedItem;
172: setSelectedPath(newPath);
173: return;
174: }
175: }
176: selectNextTopLevelMenu(path);
177: }
178: };
179:
180: private static class PrevNextAction extends GenericNavigationAction {
181: private final int direction;
182:
183: public PrevNextAction(final String name, final int direction) {
184: super (name);
185: this .direction = direction;
186: }
187:
188: public void actionPerformed(final ActionEvent e) {
189: final JPopupMenu popup = (JPopupMenu) e.getSource();
190: final MenuElement[] path = getSelectedPath();
191: int index = getPopupIndex(path, popup);
192: if (popup.getComponentCount() != 0) {
193: final MenuElement curSelection = (index != path.length - 1) ? path[path.length - 1]
194: : null;
195: setPath(path, index, curSelection, popup);
196: } else {
197: index = getNextPopupIndex(path, --index);
198: if (index >= 0) {
199: setPath(path, index, path[index + 1], popup);
200: }
201: }
202: }
203:
204: private int getNextPopupIndex(final MenuElement[] path,
205: final int startIndex) {
206: int index = startIndex;
207: while (index >= 0 && !(path[index] instanceof JPopupMenu)) {
208: index--;
209: }
210: return index;
211: }
212:
213: private void setPath(final MenuElement[] path, final int index,
214: final MenuElement curSelection, final JPopupMenu menu) {
215: final MenuElement nextMenuItem = getNextMenuItem(menu,
216: curSelection);
217: if (nextMenuItem != null) {
218: final MenuElement[] newPath = derivePath(path,
219: index + 2, index + 1);
220: newPath[newPath.length - 1] = nextMenuItem;
221: setSelectedPath(newPath);
222: }
223: }
224:
225: private MenuElement getNextMenuItem(final JPopupMenu menu,
226: final MenuElement curSelection) {
227: if (curSelection == null) {
228: int startIndex = (direction == FORWARD) ? menu
229: .getComponentCount() - 1 : 0;
230: return getNextMenuItem(menu, startIndex);
231: }
232: final int startIndex = getSelectionIndex(menu, curSelection);
233: if (startIndex >= 0) {
234: return getNextMenuItem(menu, startIndex);
235: }
236: return null;
237: }
238:
239: private MenuElement getNextMenuItem(final JPopupMenu menu,
240: final int index) {
241: final int numElements = menu.getComponentCount();
242: for (int i = 1; i <= numElements; i++) {
243: int j = ((direction > 0) ? index + i : numElements
244: + index - i)
245: % numElements;
246: final Component menuComponent = menu.getComponent(j);
247: if (menuComponent.isVisible()
248: && menuComponent.isEnabled()
249: && menuComponent instanceof MenuElement) {
250:
251: return (MenuElement) menuComponent;
252: }
253: }
254: return null;
255: }
256:
257: private int getSelectionIndex(final JPopupMenu menu,
258: final MenuElement selection) {
259: int numElements = menu.getComponentCount();
260: for (int i = 0; i < numElements; i++) {
261: if (menu.getComponent(i) == selection) {
262: return i;
263: }
264: }
265: return -1;
266: }
267: }
268:
269: private static final Action SELECT_NEXT_ACTION = new PrevNextAction(
270: "selectNext", PrevNextAction.FORWARD);
271: private static final Action SELECT_PREVIOUS_ACTION = new PrevNextAction(
272: "selectPrevious", PrevNextAction.BACKWARD);
273:
274: private static final class CancelEventFireStarter extends
275: JPopupMenu {
276: private static final Class[] NO_CLASSES_NO_ARGUMENTS = new Class[0];
277:
278: private Method fireCanceled = null;
279:
280: public CancelEventFireStarter() {
281: try {
282: fireCanceled = JPopupMenu.class.getDeclaredMethod(
283: "firePopupMenuCanceled",
284: NO_CLASSES_NO_ARGUMENTS);
285: fireCanceled.setAccessible(true);
286: } catch (SecurityException e) {
287: } catch (NoSuchMethodException e) {
288: }
289: }
290:
291: public void firePopupMenuCanceled(final JPopupMenu popup) {
292: if (fireCanceled == null) {
293: return;
294: }
295: try {
296: fireCanceled.invoke(popup,
297: (Object[]) NO_CLASSES_NO_ARGUMENTS);
298: } catch (IllegalArgumentException e) {
299: } catch (IllegalAccessException e) {
300: } catch (InvocationTargetException e) {
301: }
302: }
303: }
304:
305: private static final Action CANCEL_ACTION = new GenericNavigationAction(
306: "cancel") {
307: private final CancelEventFireStarter cancelEventFireStarter = new CancelEventFireStarter();
308:
309: public void actionPerformed(final ActionEvent e) {
310: final JPopupMenu popup = (JPopupMenu) e.getSource();
311: cancelEventFireStarter.firePopupMenuCanceled(popup);
312:
313: final MenuElement[] path = getSelectedPath();
314: if (path.length > 4) {
315: setSelectedPath(derivePath(path, path.length - 2));
316: } else {
317: setSelectedPath(null);
318: }
319: }
320: };
321:
322: private static final Action RETURN_ACTION = new GenericNavigationAction(
323: "return") {
324: public void actionPerformed(final ActionEvent e) {
325: final MenuElement[] path = getSelectedPath();
326: if (Utilities.isEmptyArray(path)) {
327: return;
328: }
329: final MenuElement selection = path[path.length - 1];
330: if (selection instanceof JMenuItem) {
331: MenuSelectionManager.defaultManager()
332: .clearSelectedPath();
333: final JMenuItem menuItem = (JMenuItem) selection;
334: menuItem.doClick(0);
335: }
336: }
337: };
338:
339: private final HashMap actionMap = new HashMap();
340:
341: private static int numInstallations;
342: private static MenuKeyBindingProcessor sharedInstance;
343:
344: private MenuKeyBindingProcessor() {
345: installActions();
346: }
347:
348: public static void attach() {
349: if (sharedInstance == null) {
350: sharedInstance = new MenuKeyBindingProcessor();
351: }
352: if (numInstallations == 0) {
353: DefaultFocusManager.getCurrentKeyboardFocusManager()
354: .addKeyEventDispatcher(sharedInstance);
355: }
356: numInstallations++;
357: }
358:
359: public static void detach() {
360: if (numInstallations == 1) {
361: DefaultFocusManager.getCurrentKeyboardFocusManager()
362: .removeKeyEventDispatcher(sharedInstance);
363: }
364: if (numInstallations > 0) {
365: numInstallations--;
366: }
367: }
368:
369: public boolean dispatchKeyEvent(final KeyEvent e) {
370: if (e.getID() != KeyEvent.KEY_PRESSED || e.isConsumed()) {
371: return false;
372: }
373:
374: // dispatch event to user listeners
375: for (AWTEventListener listener : Toolkit.getDefaultToolkit()
376: .getAWTEventListeners(AWTEvent.KEY_EVENT_MASK)) {
377: listener.eventDispatched(e);
378: }
379:
380: if (e.isConsumed()) {
381: // consumed by user listener
382: return true;
383: }
384:
385: final JPopupMenu activePopupMenu = getActivePopupMenu();
386: if (activePopupMenu == null) {
387: return false;
388: }
389: final KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
390: final Object actionKey = activePopupMenu.getInputMap().get(ks);
391: final Action action = (Action) actionMap.get(actionKey);
392: if (action == null) {
393: return false;
394: }
395:
396: SwingUtilities.notifyAction(action, ks, e, activePopupMenu, e
397: .getModifiersEx());
398: return true;
399: }
400:
401: private JPopupMenu getActivePopupMenu() {
402: final MenuElement[] path = MenuSelectionManager
403: .defaultManager().getSelectedPath();
404: if (Utilities.isEmptyArray(path)) {
405: return null;
406: }
407:
408: if (!Utilities.isValidFirstPathElement(path[0])) {
409: return null;
410: }
411:
412: for (int i = path.length - 1; i >= 0; i--) {
413: if (path[i] instanceof JPopupMenu) {
414: return (JPopupMenu) path[i];
415: }
416: }
417:
418: return null;
419: }
420:
421: private void installActions() {
422: addAction(actionMap, SELECT_CHILD_ACTION);
423: addAction(actionMap, SELECT_PARENT_ACTION);
424: addAction(actionMap, SELECT_PREVIOUS_ACTION);
425: addAction(actionMap, SELECT_NEXT_ACTION);
426: addAction(actionMap, RETURN_ACTION);
427: addAction(actionMap, CANCEL_ACTION);
428: }
429:
430: private void addAction(final HashMap actionMap, final Action action) {
431: actionMap.put(action.getValue(Action.NAME), action);
432: }
433:
434: }
|