1. /*
  2. * @(#)KeyboardManager.java 1.14 03/01/23
  3. *
  4. * Copyright 2003 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 InputMap
  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 hierarchy
  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 hierarchy, 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, pressed);
  171. }
  172. } else if ( tmp instanceof Vector) { //more than one comp registered for this
  173. Vector v = (Vector)tmp;
  174. // There is no well defined order for WHEN_IN_FOCUSED_WINDOW
  175. // bindings, but we give precedence to those bindings just
  176. // added. This is done so that JMenus WHEN_IN_FOCUSED_WINDOW
  177. // bindings are accessed before those of the JRootPane (they
  178. // both have a WHEN_IN_FOCUSED_WINDOW binding for enter).
  179. for (int counter = v.size() - 1; counter >= 0; counter--) {
  180. JComponent c = (JComponent)v.elementAt(counter);
  181. //System.out.println("Trying collision: " + c + " vector = "+ v.size());
  182. if ( c.isShowing() && c.isEnabled() ) { // don't want to give these out
  183. fireBinding(c, ks, e, pressed);
  184. if (e.isConsumed())
  185. return true;
  186. }
  187. }
  188. } else {
  189. System.out.println( "Unexpected condition in fireKeyboardAction " + tmp);
  190. // This means that tmp wasn't null, a JComponent, or a Vector. What is it?
  191. Thread.dumpStack();
  192. }
  193. }
  194. if (e.isConsumed()) {
  195. return true;
  196. }
  197. // if no one else handled it, then give the menus a crack
  198. // The're handled differently. The key is to let any JMenuBars
  199. // process the event
  200. if ( keyMap != null) {
  201. Vector v = (Vector)keyMap.get(JMenuBar.class);
  202. if (v != null) {
  203. Enumeration iter = v.elements();
  204. while (iter.hasMoreElements()) {
  205. JMenuBar mb = (JMenuBar)iter.nextElement();
  206. if ( mb.isShowing() && mb.isEnabled() ) { // don't want to give these out
  207. fireBinding(mb, ks, e, pressed);
  208. if (e.isConsumed()) {
  209. return true;
  210. }
  211. }
  212. }
  213. }
  214. }
  215. return e.isConsumed();
  216. }
  217. void fireBinding(JComponent c, KeyStroke ks, KeyEvent e, boolean pressed) {
  218. if (c.processKeyBinding(ks, e, JComponent.WHEN_IN_FOCUSED_WINDOW,
  219. pressed)) {
  220. e.consume();
  221. }
  222. }
  223. public void registerMenuBar(JMenuBar mb) {
  224. Container top = getTopAncestor(mb);
  225. Hashtable keyMap = (Hashtable)containerMap.get(top);
  226. if (keyMap == null) { // lazy evaluate one
  227. keyMap = registerNewTopContainer(top);
  228. }
  229. // use the menubar class as the key
  230. Vector menuBars = (Vector)keyMap.get(JMenuBar.class);
  231. if (menuBars == null) { // if we don't have a list of menubars,
  232. // then make one.
  233. menuBars = new Vector();
  234. keyMap.put(JMenuBar.class, menuBars);
  235. }
  236. if (!menuBars.contains(mb)) {
  237. menuBars.addElement(mb);
  238. }
  239. }
  240. public void unregisterMenuBar(JMenuBar mb) {
  241. Object topContainer = getTopAncestor(mb);
  242. Hashtable keyMap = (Hashtable)containerMap.get(topContainer);
  243. if (keyMap!=null) {
  244. Vector v = (Vector)keyMap.get(JMenuBar.class);
  245. if (v != null) {
  246. v.removeElement(mb);
  247. if (v.isEmpty()) {
  248. keyMap.remove(JMenuBar.class);
  249. if (keyMap.isEmpty()) {
  250. // remove table to enable GC
  251. containerMap.remove(topContainer);
  252. }
  253. }
  254. }
  255. }
  256. }
  257. protected Hashtable registerNewTopContainer(Container topContainer) {
  258. Hashtable keyMap = new Hashtable();
  259. containerMap.put(topContainer, keyMap);
  260. return keyMap;
  261. }
  262. /**
  263. * This class is used to create keys for a hashtable
  264. * which looks up topContainers based on component, keystroke pairs
  265. * This is used to make unregistering KeyStrokes fast
  266. */
  267. class ComponentKeyStrokePair {
  268. Object component;
  269. Object keyStroke;
  270. public ComponentKeyStrokePair(Object comp, Object key) {
  271. component = comp;
  272. keyStroke = key;
  273. }
  274. public boolean equals(Object o) {
  275. if ( !(o instanceof ComponentKeyStrokePair)) {
  276. return false;
  277. }
  278. ComponentKeyStrokePair ckp = (ComponentKeyStrokePair)o;
  279. return ((component.equals(ckp.component)) && (keyStroke.equals(ckp.keyStroke)));
  280. }
  281. public int hashCode() {
  282. return component.hashCode() * keyStroke.hashCode();
  283. }
  284. }
  285. } // end KeyboardManager