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