1 /* KeyboardManager.java -- 2 Copyright (C) 2005 Free Software Foundation, Inc. 3 4 This file is part of GNU Classpath. 5 6 GNU Classpath is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 GNU Classpath is distributed in the hope that it will be useful, but 12 WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with GNU Classpath; see the file COPYING. If not, write to the 18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19 02110-1301 USA. 20 21 Linking this library statically or dynamically with other modules is 22 making a combined work based on this library. Thus, the terms and 23 conditions of the GNU General Public License cover the whole 24 combination. 25 26 As a special exception, the copyright holders of this library give you 27 permission to link this library with independent modules to produce an 28 executable, regardless of the license terms of these independent 29 modules, and to copy and distribute the resulting executable under 30 terms of your choice, provided that you also meet, for each linked 31 independent module, the terms and conditions of the license of that 32 module. An independent module is a module which is not derived from 33 or based on this library. If you modify this library, you may extend 34 this exception to your version of the library, but you are not 35 obligated to do so. If you do not wish to do so, delete this 36 exception statement from your version. */ 37 38 39 package javax.swing; 40 41 import java.applet.Applet; 42 import java.awt.Component; 43 import java.awt.Container; 44 import java.awt.Window; 45 import java.awt.event.KeyEvent; 46 import java.util.Enumeration; 47 import java.util.Hashtable; 48 import java.util.Vector; 49 import java.util.WeakHashMap; 50 51 /** 52 * This class maintains a mapping from top-level containers to a 53 * Hashtable. The Hashtable maps KeyStrokes to Components to be used when 54 * Components register keyboard actions with the condition 55 * JComponent.WHEN_IN_FOCUSED_WINDOW. 56 * 57 * @author Anthony Balkissoon abalkiss at redhat dot com 58 * 59 */ 60 class KeyboardManager 61 { 62 /** Shared instance of KeyboardManager **/ 63 static KeyboardManager manager = new KeyboardManager(); 64 65 /** 66 * A mapping between top level containers and Hashtables that 67 * map KeyStrokes to Components. 68 */ 69 WeakHashMap topLevelLookup = new WeakHashMap(); 70 71 /** 72 * A mapping between top level containers and Vectors of JMenuBars 73 * used to allow all the JMenuBars within a top level container 74 * a chance to consume key events. 75 */ 76 Hashtable menuBarLookup = new Hashtable(); 77 /** 78 * Returns the shared instance of KeyboardManager. 79 * @return the shared instance of KeybaordManager. 80 */ getManager()81 public static KeyboardManager getManager() 82 { 83 return manager; 84 } 85 /** 86 * Returns the top-level ancestor for the given JComponent. 87 * @param c the JComponent whose top-level ancestor we want 88 * @return the top-level ancestor for the given JComponent. 89 */ findTopLevel(Component c)90 static Container findTopLevel (Component c) 91 { 92 Container topLevel = (c instanceof Container) ? (Container) c 93 : c.getParent(); 94 while (topLevel != null && 95 !(topLevel instanceof Window) && 96 !(topLevel instanceof Applet) && 97 !(topLevel instanceof JInternalFrame)) 98 topLevel = topLevel.getParent(); 99 return topLevel; 100 } 101 102 /** 103 * Returns the Hashtable that maps KeyStrokes to Components, for 104 * the specified top-level container c. If no Hashtable exists 105 * we create and register it here and return the newly created 106 * Hashtable. 107 * 108 * @param c the top-level container whose Hashtable we want 109 * @return the Hashtable mapping KeyStrokes to Components for the 110 * specified top-level container 111 */ getHashtableForTopLevel(Container c)112 Hashtable getHashtableForTopLevel (Container c) 113 { 114 Hashtable keyToComponent = (Hashtable)topLevelLookup.get(c); 115 if (keyToComponent == null) 116 { 117 keyToComponent = new Hashtable(); 118 topLevelLookup.put(c, keyToComponent); 119 } 120 return keyToComponent; 121 } 122 123 /** 124 * Registers a KeyStroke with a Component. This does not register 125 * the KeyStroke to a specific Action. When searching for a 126 * WHEN_IN_FOCUSED_WINDOW binding we will first go up to the focused 127 * top-level Container, then get the Hashtable that maps KeyStrokes 128 * to components for that particular top-level Container, then 129 * call processKeyBindings on that component with the condition 130 * JComponent.WHEN_IN_FOCUSED_WINDOW. 131 * @param comp the JComponent associated with the KeyStroke 132 * @param key the KeyStroke 133 */ registerBinding(JComponent comp, KeyStroke key)134 public void registerBinding(JComponent comp, KeyStroke key) 135 { 136 // This method associates a KeyStroke with a particular JComponent 137 // When the KeyStroke occurs, if this component's top-level ancestor 138 // has focus (one of its children is the focused Component) then 139 // comp.processKeyBindings will be called with condition 140 // JComponent.WHEN_IN_FOCUSED_WINDOW. 141 142 // Look for the JComponent's top-level parent and return if it is null 143 Container topLevel = findTopLevel(comp); 144 if (topLevel == null) 145 return; 146 147 // Now get the Hashtable for this top-level container 148 Hashtable keyToComponent = getHashtableForTopLevel(topLevel); 149 150 // And add the new binding to this Hashtable 151 // FIXME: should allow more than one JComponent to be associated 152 // with a KeyStroke, in case one of them is disabled 153 keyToComponent.put(key, comp); 154 } 155 clearBindingsForComp(JComponent comp)156 public void clearBindingsForComp(JComponent comp) 157 { 158 // This method clears all the WHEN_IN_FOCUSED_WINDOW bindings associated 159 // with <code>comp</code>. This is used for a terribly ineffcient 160 // strategy in which JComponent.updateComponentInputMap simply clears 161 // all bindings associated with its component and then reloads all the 162 // bindings from the updated ComponentInputMap. This is only a preliminary 163 // strategy and should be improved upon once the WHEN_IN_FOCUSED_WINDOW 164 // bindings work. 165 166 // Find the top-level ancestor 167 168 Container topLevel = findTopLevel(comp); 169 if (topLevel == null) 170 return; 171 // And now get its Hashtable 172 Hashtable keyToComponent = getHashtableForTopLevel(topLevel); 173 174 Enumeration keys = keyToComponent.keys(); 175 Object temp; 176 177 // Iterate through the keys and remove any key whose value is comp 178 while (keys.hasMoreElements()) 179 { 180 temp = keys.nextElement(); 181 if (comp == (JComponent)keyToComponent.get(temp)) 182 keyToComponent.remove(temp); 183 } 184 } 185 186 /** 187 * This method registers all the bindings in the given ComponentInputMap. 188 * Rather than call registerBinding on all the keys, we do the work here 189 * so that we don't duplicate finding the top-level container and 190 * getting its Hashtable. 191 * 192 * @param map the ComponentInputMap whose bindings we want to register 193 */ registerEntireMap(ComponentInputMap map)194 public void registerEntireMap (ComponentInputMap map) 195 { 196 if (map == null) 197 return; 198 JComponent comp = map.getComponent(); 199 KeyStroke[] keys = map.allKeys(); 200 if (keys == null) 201 return; 202 // Find the top-level container associated with this ComponentInputMap 203 Container topLevel = findTopLevel(comp); 204 if (topLevel == null) 205 return; 206 207 // Register the KeyStrokes in the top-level container's Hashtable 208 Hashtable keyToComponent = getHashtableForTopLevel(topLevel); 209 for (int i = 0; i < keys.length; i++) 210 keyToComponent.put(keys[i], comp); 211 } 212 processKeyStroke(Component comp, KeyStroke key, KeyEvent e)213 public boolean processKeyStroke (Component comp, KeyStroke key, KeyEvent e) 214 { 215 boolean pressed = e.getID() == KeyEvent.KEY_PRESSED; 216 217 // Look for the top-level ancestor 218 Container topLevel = findTopLevel(comp); 219 if (topLevel == null) 220 return false; 221 // Now get the Hashtable for that top-level container 222 Hashtable keyToComponent = getHashtableForTopLevel(topLevel); 223 Enumeration keys = keyToComponent.keys(); 224 JComponent target = (JComponent)keyToComponent.get(key); 225 if (target != null && target.processKeyBinding 226 (key, e, JComponent.WHEN_IN_FOCUSED_WINDOW, pressed)) 227 return true; 228 229 // Have to give all the JMenuBars a chance to consume the event 230 Vector menuBars = getVectorForTopLevel(topLevel); 231 for (int i = 0; i < menuBars.size(); i++) 232 if (((JMenuBar)menuBars.elementAt(i)).processKeyBinding(key, e, JComponent.WHEN_IN_FOCUSED_WINDOW, pressed)) 233 return true; 234 return false; 235 } 236 237 /** 238 * Returns the Vector of JMenuBars associated with the top-level 239 * @param c the top-level container whose JMenuBar Vector we want 240 * @return the Vector of JMenuBars for this top level container 241 */ getVectorForTopLevel(Container c)242 Vector getVectorForTopLevel(Container c) 243 { 244 Vector result = (Vector) menuBarLookup.get(c); 245 if (result == null) 246 { 247 result = new Vector(); 248 menuBarLookup.put (c, result); 249 } 250 return result; 251 } 252 253 /** 254 * In processKeyStroke, KeyManager must give all JMenuBars in the 255 * focused top-level container a chance to process the event. So, 256 * JMenuBars must be registered in KeyManager and associated with a 257 * top-level container. That's what this method is for. 258 * @param menuBar the JMenuBar to register 259 */ registerJMenuBar(JMenuBar menuBar)260 public void registerJMenuBar (JMenuBar menuBar) 261 { 262 Container topLevel = findTopLevel(menuBar); 263 Vector menuBars = getVectorForTopLevel(topLevel); 264 if (!menuBars.contains(menuBar)) 265 menuBars.add(menuBar); 266 } 267 268 /** 269 * Unregisters a JMenuBar from its top-level container. This is 270 * called before the JMenuBar is actually removed from the container 271 * so findTopLevel will still find us the correct top-level container. 272 * @param menuBar the JMenuBar to unregister. 273 */ unregisterJMenuBar(JMenuBar menuBar)274 public void unregisterJMenuBar (JMenuBar menuBar) 275 { 276 Container topLevel = findTopLevel(menuBar); 277 Vector menuBars = getVectorForTopLevel(topLevel); 278 if (menuBars.contains(menuBar)) 279 menuBars.remove(menuBar); 280 } 281 } 282