1. /*
  2. * @(#)KeyboardManager.java 1.8 01/11/29
  3. *
  4. * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
  5. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
  6. */
  7. package javax.swing;
  8. import java.util.*;
  9. import java.awt.*;
  10. import java.awt.event.*;
  11. import java.applet.*;
  12. import java.beans.*;
  13. import javax.swing.event.*;
  14. /**
  15. * The KeyboardManager class is used to help dispatch keyboard actions for the
  16. * WHEN_IN_FOCUSED_WINDOW style actions. Actions with other conditions are handled
  17. * directly in JComponent.
  18. *
  19. * Here's a description of the symantics of how keyboard dispatching should work
  20. * atleast as I understand it.
  21. *
  22. * KeyEvents are dispatched to the focused component. The focus manager gets first
  23. * crack at processing this event. If the focus manager doesn't want it, then
  24. * the JComponent calls super.processKeyEvent() this allows listeners a chance
  25. * to process the event.
  26. *
  27. * If none of the listeners "consumes" the event then the keybindings get a shot.
  28. * This is where things start to get interesting. First, KeyStokes defined with the
  29. * WHEN_FOCUSED condition get a chance. If none of these want the event, then the component
  30. * walks though it's parents looked for actions of type WHEN_ANCESTOR_OF_FOCUSED_COMPONENT.
  31. *
  32. * If no one has taken it yet, then it winds up here. We then look for components registered
  33. * for WHEN_IN_FOCUSED_WINDOW events and fire to them. Note that if none of those are found
  34. * then we pass the event to the menubars and let them have a crack at it. They're handled differently.
  35. *
  36. * Lastly, we check if we're looking at an internal frame. If we are and no one wanted the event
  37. * then we move up to the InternalFrame's creator and see if anyone wants the event (and so on and so on).
  38. *
  39. *
  40. * @see JComponent#registerKeyboardAction
  41. */
  42. class KeyboardManager {
  43. static KeyboardManager currentManager = new KeyboardManager();
  44. /**
  45. * maps top-level containers to a sub-hashtable full of keystrokes
  46. */
  47. Hashtable containerMap = new Hashtable();
  48. /**
  49. * Maps component/keystroke pairs to a topLevel container
  50. * This is mainly used for fast unregister operations
  51. */
  52. Hashtable componentKeyStrokeMap = new Hashtable();
  53. public static KeyboardManager getCurrentManager() {
  54. return currentManager;
  55. }
  56. public static void setCurrentManager(KeyboardManager km) {
  57. currentManager = km;
  58. }
  59. /**
  60. * register keystrokes here which are for the WHEN_IN_FOCUSED_WINDOW
  61. * case.
  62. * Other types of keystrokes will be handled by walking the heirarchy
  63. * That simplifies some potentially hairy stuff.
  64. */
  65. public void registerKeyStroke(KeyStroke k, JComponent c) {
  66. Container topContainer = getTopAncestor(c);
  67. if (topContainer == null) {
  68. return;
  69. }
  70. Hashtable keyMap = (Hashtable)containerMap.get(topContainer);
  71. if (keyMap == null) { // lazy evaluate one
  72. keyMap = registerNewTopContainer(topContainer);
  73. }
  74. Object tmp = keyMap.get(k);
  75. if (tmp == null) {
  76. keyMap.put(k,c);
  77. } else if (tmp instanceof Vector) { // if there's a Vector there then add to it.
  78. Vector v = (Vector)tmp;
  79. if (!v.contains(c)) { // only add if this keystroke isn't registered for this component
  80. v.addElement(c);
  81. }
  82. } else if (tmp instanceof JComponent) {
  83. // if a JComponent is there then remove it and replace it with a vector
  84. // Then add the old compoennt and the new compoent to the vector
  85. // then insert the vector in the table
  86. if (tmp != c) { // this means this is already registered for this component, no need to dup
  87. Vector v = new Vector();
  88. v.addElement(tmp);
  89. v.addElement(c);
  90. keyMap.put(k, v);
  91. }
  92. } else {
  93. System.out.println("Unexpected condition in registerKeyStroke");
  94. Thread.dumpStack();
  95. }
  96. componentKeyStrokeMap.put(new ComponentKeyStrokePair(c,k), topContainer);
  97. }
  98. /**
  99. * find the top Window or Applet
  100. */
  101. private static Container getTopAncestor(JComponent c) {
  102. for(Container p = c.getParent(); p != null; p = p.getParent()) {
  103. if (p instanceof Window || p instanceof Applet || p instanceof JInternalFrame) {
  104. return p;
  105. }
  106. }
  107. return null;
  108. }
  109. public void unregisterKeyStroke(KeyStroke ks, JComponent c) {
  110. // component may have already been removed from the heirarchy, we
  111. // need to look up the container using the componentKeyStrokeMap.
  112. ComponentKeyStrokePair ckp = new ComponentKeyStrokePair(c,ks);
  113. Object topContainer = componentKeyStrokeMap.get(ckp);
  114. if (topContainer == null) { // never heard of this pairing, so bail
  115. return;
  116. }
  117. Hashtable keyMap = (Hashtable)containerMap.get(topContainer);
  118. if (keyMap == null) { // this should never happen, but I'm being safe
  119. Thread.dumpStack();
  120. return;
  121. }
  122. Object tmp = keyMap.get(ks);
  123. if (tmp == null) { // this should never happen, but I'm being safe
  124. Thread.dumpStack();
  125. return;
  126. }
  127. if (tmp instanceof JComponent && tmp == c) {
  128. keyMap.remove(ks); // remove the KeyStroke from the Map
  129. //System.out.println("removed a stroke" + ks);
  130. } else if (tmp instanceof Vector ) { // this means there is more than one component reg for this key
  131. Vector v = (Vector)tmp;
  132. v.removeElement(c);
  133. if ( v.isEmpty() ) {
  134. keyMap.remove(ks); // remove the KeyStroke from the Map
  135. //System.out.println("removed a ks vector");
  136. }
  137. }
  138. if ( keyMap.isEmpty() ) { // if no more bindings in this table
  139. containerMap.remove(topContainer); // remove table to enable GC
  140. //System.out.println("removed a container");
  141. }
  142. componentKeyStrokeMap.remove(ckp);
  143. }
  144. /**
  145. * This method is called when the focused component (and none of
  146. * its ancestors) want the key event. This will look up the keystroke
  147. * to see if any chidren (or subchildren) of the specified container
  148. * want a crack at the event.
  149. * If one of them wants it, then it will "DO-THE-RIGHT-THING"
  150. */
  151. public boolean fireKeyboardAction(KeyEvent e, boolean pressed, Container topAncestor) {
  152. if (e.isConsumed()) {
  153. System.out.println("Aquired pre-used event!");
  154. Thread.dumpStack();
  155. }
  156. KeyStroke ks;
  157. if(e.getID() == KeyEvent.KEY_TYPED) {
  158. ks=KeyStroke.getKeyStroke(e.getKeyChar());
  159. } else {
  160. ks=KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers(), !pressed);
  161. }
  162. Hashtable keyMap = (Hashtable)containerMap.get(topAncestor);
  163. if (keyMap != null) { // this container isn't registered, so bail
  164. Object tmp = keyMap.get(ks);
  165. if (tmp == null) {
  166. // don't do anything
  167. } else if ( tmp instanceof JComponent) {
  168. JComponent c = (JComponent)tmp;
  169. if ( c.isShowing() && c.isEnabled() ) { // only give it out if enabled and visible
  170. fireBinding(c, ks, e);
  171. }
  172. } else if ( tmp instanceof Vector) { //more than one comp registered for this
  173. Vector v = (Vector)tmp;
  174. Enumeration iter = v.elements();
  175. while (iter.hasMoreElements()) {
  176. JComponent c = (JComponent)iter.nextElement();
  177. //System.out.println("Trying collision: " + c + " vector = "+ v.size());
  178. if ( c.isShowing() && c.isEnabled() ) { // don't want to give these out
  179. fireBinding(c, ks, e);
  180. if (e.isConsumed())
  181. return true;
  182. }
  183. }
  184. } else {
  185. System.out.println( "Unexpected condition in fireKeyboardAction " + tmp);
  186. // This means that tmp wasn't null, a JComponent, or a Vector. What is it?
  187. Thread.dumpStack();
  188. }
  189. }
  190. if (e.isConsumed()) {
  191. return true;
  192. }
  193. // if no one else handled it, then give the menus a crack
  194. // The're handled differently. The key is to let any JMenuBars
  195. // process the event
  196. if ( keyMap != null) {
  197. Vector v = (Vector)keyMap.get(JMenuBar.class);
  198. if (v != null) {
  199. Enumeration iter = v.elements();
  200. while (iter.hasMoreElements()) {
  201. JMenuBar mb = (JMenuBar)iter.nextElement();
  202. if ( mb.isShowing() && mb.isEnabled() ) { // don't want to give these out
  203. fireBinding(mb, ks, e);
  204. if (e.isConsumed()) {
  205. return true;
  206. }
  207. }
  208. }
  209. }
  210. }
  211. // if no one has handled it yet, and the container we're working in is
  212. // an internal frame, then move on up to it's top container.
  213. if (topAncestor instanceof JInternalFrame) {
  214. Container newTopContainer = getTopAncestor((JInternalFrame)topAncestor);
  215. if (newTopContainer == null) {
  216. return false; // this case can occur in rare cases where a key action removes
  217. // the internal frame from the heirarchy.
  218. }
  219. fireKeyboardAction( e, pressed, newTopContainer );
  220. }
  221. return e.isConsumed();
  222. }
  223. void fireBinding(JComponent c, KeyStroke ks, KeyEvent e) {
  224. //System.out.println("Firing on: " + c);
  225. JComponent.KeyboardBinding binding = c.bindingForKeyStroke(ks, JComponent.WHEN_IN_FOCUSED_WINDOW);
  226. if(binding != null) { // this block of code stolen from JComponent.processKeyBinding
  227. ActionListener listener = binding.getAction();
  228. if(listener != null) {
  229. if (listener instanceof Action && ((Action)listener).isEnabled() == false) {
  230. // this case handles when we try to dispatch to a disbled action
  231. // instead of sending the event we return, thus giving a chance to
  232. // other components registered for this stroke.
  233. return;
  234. }
  235. listener.actionPerformed(new ActionEvent(c,ActionEvent.ACTION_PERFORMED,binding.getCommand()));
  236. e.consume();
  237. }
  238. } else {
  239. // System.out.println("Binding NULL");
  240. }
  241. }
  242. public void registerMenuBar(JMenuBar mb) {
  243. Container top = getTopAncestor(mb);
  244. Hashtable keyMap = (Hashtable)containerMap.get(top);
  245. if (keyMap == null) { // lazy evaluate one
  246. keyMap = registerNewTopContainer(top);
  247. }
  248. // use the menubar class as the key
  249. Vector menuBars = (Vector)keyMap.get(JMenuBar.class);
  250. if (menuBars == null) { // if we don't have a list of menubars,
  251. // then make one.
  252. menuBars = new Vector();
  253. keyMap.put(JMenuBar.class, menuBars);
  254. }
  255. if (!menuBars.contains(mb)) {
  256. menuBars.addElement(mb);
  257. }
  258. }
  259. public void unregisterMenuBar(JMenuBar mb) {
  260. Object topContainer = getTopAncestor(mb);
  261. Hashtable keyMap = (Hashtable)containerMap.get(topContainer);
  262. if (keyMap!=null) {
  263. Vector v = (Vector)keyMap.get(JMenuBar.class);
  264. if (v != null) {
  265. v.removeElement(mb);
  266. if (v.isEmpty()) {
  267. keyMap.remove(JMenuBar.class);
  268. if (keyMap.isEmpty()) {
  269. // remove table to enable GC
  270. containerMap.remove(topContainer);
  271. }
  272. }
  273. }
  274. }
  275. }
  276. protected Hashtable registerNewTopContainer(Container topContainer) {
  277. Hashtable keyMap = new Hashtable();
  278. containerMap.put(topContainer, keyMap);
  279. return keyMap;
  280. }
  281. /**
  282. * This class is used to create keys for a hashtable
  283. * which looks up topContainers based on component, keystroke pairs
  284. * This is used to make unregistering KeyStrokes fast
  285. */
  286. class ComponentKeyStrokePair {
  287. Object component;
  288. Object keyStroke;
  289. public ComponentKeyStrokePair(Object comp, Object key) {
  290. component = comp;
  291. keyStroke = key;
  292. }
  293. public boolean equals(Object o) {
  294. if ( !(o instanceof ComponentKeyStrokePair)) {
  295. return false;
  296. }
  297. ComponentKeyStrokePair ckp = (ComponentKeyStrokePair)o;
  298. return ((component.equals(ckp.component)) && (keyStroke.equals(ckp.keyStroke)));
  299. }
  300. public int hashCode() {
  301. return component.hashCode() * keyStroke.hashCode();
  302. }
  303. }
  304. } // end KeyboardManager