Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]

    1   /*
    2    *  Licensed to the Apache Software Foundation (ASF) under one or more
    3    *  contributor license agreements.  See the NOTICE file distributed with
    4    *  this work for additional information regarding copyright ownership.
    5    *  The ASF licenses this file to You under the Apache License, Version 2.0
    6    *  (the "License"); you may not use this file except in compliance with
    7    *  the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
   10    *
   11    *  Unless required by applicable law or agreed to in writing, software
   12    *  distributed under the License is distributed on an "AS IS" BASIS,
   13    *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   14    *  See the License for the specific language governing permissions and
   15    *  limitations under the License.
   16    */
   17   /**
   18   * @author Alexander T. Simbirtsev
   19   */
   20   package javax.swing.plaf.basic;
   21   
   22   import java.awt.Component;
   23   import java.awt.KeyEventDispatcher;
   24   import java.awt.Toolkit;
   25   import java.awt.AWTEvent;
   26   import java.awt.event.ActionEvent;
   27   import java.awt.event.KeyEvent;
   28   import java.awt.event.AWTEventListener;
   29   import java.lang.reflect.InvocationTargetException;
   30   import java.lang.reflect.Method;
   31   import java.util.HashMap;
   32   
   33   import javax.swing.AbstractAction;
   34   import javax.swing.Action;
   35   import javax.swing.DefaultFocusManager;
   36   import javax.swing.JMenu;
   37   import javax.swing.JMenuBar;
   38   import javax.swing.JMenuItem;
   39   import javax.swing.JPopupMenu;
   40   import javax.swing.KeyStroke;
   41   import javax.swing.MenuElement;
   42   import javax.swing.MenuSelectionManager;
   43   import javax.swing.SwingUtilities;
   44   
   45   import org.apache.harmony.x.swing.Utilities;
   46   
   47   
   48   class MenuKeyBindingProcessor implements KeyEventDispatcher {
   49   
   50       private abstract static class GenericNavigationAction extends AbstractAction {
   51           protected static final int FORWARD = 1;
   52           protected static final int BACKWARD = -1;
   53   
   54           public GenericNavigationAction(final String name) {
   55               super(name);
   56           }
   57   
   58           protected final int getPopupIndex(final MenuElement[] path, final JPopupMenu popup) {
   59               int index = path.length - 1;
   60               while(index >= 0 && path[index] != popup) {
   61                   index--;
   62               }
   63               return index;
   64           }
   65   
   66           protected final MenuElement[] derivePath(final MenuElement[] path,
   67                                                    final int length) {
   68               return derivePath(path, length, length);
   69           }
   70   
   71           protected final MenuElement[] derivePath(final MenuElement[] path,
   72                                              final int newLength,
   73                                              final int copyLength) {
   74   
   75               final MenuElement[] result = new MenuElement[newLength];
   76               System.arraycopy(path, 0, result, 0, copyLength);
   77               return result;
   78           }
   79   
   80           protected final MenuElement[] getSelectedPath() {
   81               return MenuSelectionManager.defaultManager().getSelectedPath();
   82           }
   83   
   84           protected final void setSelectedPath(final MenuElement[] path) {
   85               MenuSelectionManager.defaultManager().setSelectedPath(path);
   86           }
   87       }
   88   
   89       private abstract static class ChildParentAction extends GenericNavigationAction {
   90           private final int direction;
   91   
   92           public ChildParentAction(final String name, final int direction) {
   93               super(name);
   94               this.direction = direction;
   95           }
   96   
   97           protected void selectNextTopLevelMenu(final MenuElement[] path) {
   98               if (Utilities.isEmptyArray(path) || !(path[0] instanceof JMenuBar)) {
   99                   return;
  100               }
  101               final JMenuBar menuBar = (JMenuBar)path[0];
  102               final JMenu menu = (JMenu)path[1];
  103               final int menuIndex = menuBar.getComponentIndex(menu);
  104               final JMenu nextMenu = getNextMenu(menuBar, menuIndex);
  105               if (nextMenu != null && nextMenu != menu) {
  106                   setSelectedPath(new MenuElement[] {menuBar, nextMenu,
  107                                                      nextMenu.getPopupMenu()});
  108               }
  109           }
  110   
  111           private JMenu getNextMenu(final JMenuBar menuBar, final int index) {
  112               final int numElements = menuBar.getMenuCount();
  113               for (int i = 1; i <= numElements; i++) {
  114                   int j = (direction > 0 ? index + i : numElements + index - i) % numElements;
  115                   final JMenu menu = menuBar.getMenu(j);
  116                   if (menu != null) {
  117                       return menu;
  118                   }
  119               }
  120               return null;
  121           }
  122       }
  123   
  124       private static final Action SELECT_PARENT_ACTION = new ChildParentAction("selectParent", GenericNavigationAction.BACKWARD) {
  125           public void actionPerformed(final ActionEvent e) {
  126               final JPopupMenu popup = (JPopupMenu)e.getSource();
  127               final MenuElement[] path = getSelectedPath();
  128               if (path.length > 3) {
  129                   int index = getPopupIndex(path, popup);
  130                   JMenu menu = (JMenu)path[index - 1];
  131                   if (!menu.isTopLevelMenu()) {
  132                       setSelectedPath(derivePath(path, index));
  133                       return;
  134                   }
  135               }
  136               selectNextTopLevelMenu(path);
  137           }
  138       };
  139   
  140       private static final Action SELECT_CHILD_ACTION = new ChildParentAction("selectChild", GenericNavigationAction.FORWARD) {
  141           public void actionPerformed(final ActionEvent e) {
  142               final MenuElement[] path = getSelectedPath();
  143               if (path.length > 3 || path[0] instanceof JPopupMenu) {
  144                   final MenuElement lastElement = path[path.length - 1];
  145                   final MenuElement[] subElements = lastElement.getSubElements();
  146                   if (!Utilities.isEmptyArray(subElements)) {
  147                       MenuElement[] newPath;
  148                       final MenuElement newSelectedItem = subElements[0];
  149                       final MenuElement newSelectedChild = Utilities.getFirstSelectableItem(newSelectedItem.getSubElements());
  150                       if (newSelectedItem instanceof JPopupMenu && newSelectedChild != null) {
  151                           newPath = derivePath(path, path.length + 2, path.length);
  152                           newPath[path.length + 1] = newSelectedChild;
  153                       } else {
  154                           newPath = derivePath(path, path.length + 1, path.length);
  155                       }
  156                       newPath[path.length] = newSelectedItem;
  157                       setSelectedPath(newPath);
  158                       return;
  159                   }
  160               }
  161               selectNextTopLevelMenu(path);
  162           }
  163       };
  164   
  165       private static class PrevNextAction extends GenericNavigationAction {
  166           private final int direction;
  167   
  168           public PrevNextAction(final String name, final int direction) {
  169               super(name);
  170               this.direction = direction;
  171           }
  172   
  173           public void actionPerformed(final ActionEvent e) {
  174               final JPopupMenu popup = (JPopupMenu)e.getSource();
  175               final MenuElement[] path = getSelectedPath();
  176               int index = getPopupIndex(path, popup);
  177               if (popup.getComponentCount() != 0) {
  178                   final MenuElement curSelection = (index != path.length - 1) ? path[path.length - 1] : null;
  179                   setPath(path, index, curSelection, popup);
  180               } else {
  181                   index = getNextPopupIndex(path, --index);
  182                   if (index >= 0) {
  183                       setPath(path, index, path[index + 1], popup);
  184                   }
  185               }
  186           }
  187   
  188           private int getNextPopupIndex(final MenuElement[] path, final int startIndex) {
  189               int index = startIndex;
  190               while(index >= 0 && !(path[index] instanceof JPopupMenu)) {
  191                   index--;
  192               }
  193               return index;
  194           }
  195   
  196           private void setPath(final MenuElement[] path, final int index,
  197                                final MenuElement curSelection, final JPopupMenu menu) {
  198               final MenuElement nextMenuItem = getNextMenuItem(menu, curSelection);
  199               if (nextMenuItem != null) {
  200                   final MenuElement[] newPath = derivePath(path, index + 2, index + 1);
  201                   newPath[newPath.length - 1] = nextMenuItem;
  202                   setSelectedPath(newPath);
  203               }
  204           }
  205   
  206           private MenuElement getNextMenuItem(final JPopupMenu menu, final MenuElement curSelection) {
  207               if (curSelection == null) {
  208                   int startIndex = (direction == FORWARD) ? menu.getComponentCount() - 1 : 0;
  209                   return getNextMenuItem(menu, startIndex);
  210               }
  211               final int startIndex = getSelectionIndex(menu, curSelection);
  212               if (startIndex >= 0) {
  213                   return getNextMenuItem(menu, startIndex);
  214               }
  215               return null;
  216           }
  217   
  218           private MenuElement getNextMenuItem(final JPopupMenu menu, final int index) {
  219               final int numElements = menu.getComponentCount();
  220               for (int i = 1; i <= numElements; i++) {
  221                   int j = ((direction > 0) ? index + i : numElements + index - i) % numElements;
  222                   final Component menuComponent = menu.getComponent(j);
  223                   if (menuComponent.isVisible() && menuComponent.isEnabled()
  224                       && menuComponent instanceof MenuElement) {
  225   
  226                       return (MenuElement)menuComponent;
  227                   }
  228               }
  229               return null;
  230           }
  231   
  232           private int getSelectionIndex(final JPopupMenu menu, final MenuElement selection) {
  233               int numElements = menu.getComponentCount();
  234               for (int i = 0; i < numElements; i++) {
  235                   if (menu.getComponent(i) == selection) {
  236                       return i;
  237                   }
  238               }
  239               return -1;
  240           }
  241       }
  242   
  243       private static final Action SELECT_NEXT_ACTION = new PrevNextAction("selectNext", PrevNextAction.FORWARD);
  244       private static final Action SELECT_PREVIOUS_ACTION = new PrevNextAction("selectPrevious", PrevNextAction.BACKWARD);
  245   
  246       private static final class CancelEventFireStarter extends JPopupMenu {
  247           private static final Class[] NO_CLASSES_NO_ARGUMENTS = new Class[0];
  248   
  249           private Method fireCanceled = null;
  250   
  251           public CancelEventFireStarter() {
  252               try {
  253                   fireCanceled = JPopupMenu.class.getDeclaredMethod("firePopupMenuCanceled", NO_CLASSES_NO_ARGUMENTS);
  254                   fireCanceled.setAccessible(true);
  255               } catch (SecurityException e) {
  256               } catch (NoSuchMethodException e) {}
  257           }
  258   
  259           public void firePopupMenuCanceled(final JPopupMenu popup) {
  260               if (fireCanceled == null) {
  261                   return;
  262               }
  263               try {
  264                   fireCanceled.invoke(popup, (Object[])NO_CLASSES_NO_ARGUMENTS);
  265               } catch (IllegalArgumentException e) {
  266               } catch (IllegalAccessException e) {
  267               } catch (InvocationTargetException e) {}
  268           }
  269       }
  270   
  271       private static final Action CANCEL_ACTION = new GenericNavigationAction("cancel") {
  272           private final CancelEventFireStarter cancelEventFireStarter = new CancelEventFireStarter();
  273   
  274           public void actionPerformed(final ActionEvent e) {
  275               final JPopupMenu popup = (JPopupMenu)e.getSource();
  276               cancelEventFireStarter.firePopupMenuCanceled(popup);
  277   
  278               final MenuElement[] path = getSelectedPath();
  279               if (path.length > 4) {
  280                   setSelectedPath(derivePath(path, path.length - 2));
  281               } else {
  282                   setSelectedPath(null);
  283               }
  284           }
  285       };
  286   
  287       private static final Action RETURN_ACTION = new GenericNavigationAction("return") {
  288           public void actionPerformed(final ActionEvent e) {
  289               final MenuElement[] path = getSelectedPath();
  290               if (Utilities.isEmptyArray(path)) {
  291                   return;
  292               }
  293               final MenuElement selection = path[path.length - 1];
  294               if (selection instanceof JMenuItem) {
  295                   MenuSelectionManager.defaultManager().clearSelectedPath();
  296                   final JMenuItem menuItem = (JMenuItem)selection;
  297                   menuItem.doClick(0);
  298               }
  299           }
  300       };
  301   
  302       private final HashMap actionMap = new HashMap();
  303   
  304       private static int numInstallations;
  305       private static MenuKeyBindingProcessor sharedInstance;
  306   
  307       private MenuKeyBindingProcessor() {
  308           installActions();
  309       }
  310   
  311       public static void attach() {
  312           if (sharedInstance == null) {
  313               sharedInstance = new MenuKeyBindingProcessor();
  314           }
  315           if (numInstallations == 0) {
  316               DefaultFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(sharedInstance);
  317           }
  318           numInstallations++;
  319       }
  320   
  321       public static void detach() {
  322           if (numInstallations == 1) {
  323               DefaultFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(sharedInstance);
  324           }
  325           if (numInstallations > 0) {
  326               numInstallations--;
  327           }
  328       }
  329   
  330       public boolean dispatchKeyEvent(final KeyEvent e) {
  331           if (e.getID() != KeyEvent.KEY_PRESSED || e.isConsumed()) {
  332               return false;
  333           }
  334   
  335           // dispatch event to user listeners
  336           for (AWTEventListener listener :
  337                   Toolkit.getDefaultToolkit().getAWTEventListeners(
  338                       AWTEvent.KEY_EVENT_MASK)) {
  339               listener.eventDispatched(e);
  340           }
  341           
  342           if (e.isConsumed()) {
  343               // consumed by user listener
  344               return true;
  345           }
  346           
  347           final JPopupMenu activePopupMenu = getActivePopupMenu();
  348           if (activePopupMenu == null) {
  349               return false;
  350           }
  351           final KeyStroke ks = KeyStroke.getKeyStrokeForEvent(e);
  352           final Object actionKey = activePopupMenu.getInputMap().get(ks);
  353           final Action action = (Action)actionMap.get(actionKey);
  354           if (action == null) {
  355               return false;
  356           }
  357           
  358           SwingUtilities.notifyAction(action, ks, e, activePopupMenu, e.getModifiersEx());
  359           return true;
  360       }
  361   
  362       private JPopupMenu getActivePopupMenu() {
  363           final MenuElement[] path = MenuSelectionManager.defaultManager().getSelectedPath();
  364           if (Utilities.isEmptyArray(path)) {
  365               return null;
  366           }
  367   
  368           if (!Utilities.isValidFirstPathElement(path[0])) {
  369               return null;
  370           }
  371   
  372           for (int i = path.length - 1; i >= 0; i--) {
  373               if (path[i] instanceof JPopupMenu) {
  374                   return (JPopupMenu)path[i];
  375               }
  376           }
  377   
  378           return null;
  379       }
  380   
  381       private void installActions() {
  382           addAction(actionMap, SELECT_CHILD_ACTION);
  383           addAction(actionMap, SELECT_PARENT_ACTION);
  384           addAction(actionMap, SELECT_PREVIOUS_ACTION);
  385           addAction(actionMap, SELECT_NEXT_ACTION);
  386           addAction(actionMap, RETURN_ACTION);
  387           addAction(actionMap, CANCEL_ACTION);
  388       }
  389   
  390       private void addAction(final HashMap actionMap, final Action action) {
  391           actionMap.put(action.getValue(Action.NAME), action);
  392       }
  393   
  394   }

Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]