1. /*
  2. * @(#)KeyboardManager.java 1.16 03/12/19
  3. *
  4. * Copyright 2004 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 && ((Window)p).isFocusableWindow() ||
  104. p instanceof Applet || p instanceof JInternalFrame) {
  105. return p;
  106. }
  107. }
  108. return null;
  109. }
  110. public void unregisterKeyStroke(KeyStroke ks, JComponent c) {
  111. // component may have already been removed from the hierarchy, we
  112. // need to look up the container using the componentKeyStrokeMap.
  113. ComponentKeyStrokePair ckp = new ComponentKeyStrokePair(c,ks);
  114. Object topContainer = componentKeyStrokeMap.get(ckp);
  115. if (topContainer == null) { // never heard of this pairing, so bail
  116. return;
  117. }
  118. Hashtable keyMap = (Hashtable)containerMap.get(topContainer);
  119. if (keyMap == null) { // this should never happen, but I'm being safe
  120. Thread.dumpStack();
  121. return;
  122. }
  123. Object tmp = keyMap.get(ks);
  124. if (tmp == null) { // this should never happen, but I'm being safe
  125. Thread.dumpStack();
  126. return;
  127. }
  128. if (tmp instanceof JComponent && tmp == c) {
  129. keyMap.remove(ks); // remove the KeyStroke from the Map
  130. //System.out.println("removed a stroke" + ks);
  131. } else if (tmp instanceof Vector ) { // this means there is more than one component reg for this key
  132. Vector v = (Vector)tmp;
  133. v.removeElement(c);
  134. if ( v.isEmpty() ) {
  135. keyMap.remove(ks); // remove the KeyStroke from the Map
  136. //System.out.println("removed a ks vector");
  137. }
  138. }
  139. if ( keyMap.isEmpty() ) { // if no more bindings in this table
  140. containerMap.remove(topContainer); // remove table to enable GC
  141. //System.out.println("removed a container");
  142. }
  143. componentKeyStrokeMap.remove(ckp);
  144. }
  145. /**
  146. * This method is called when the focused component (and none of
  147. * its ancestors) want the key event. This will look up the keystroke
  148. * to see if any chidren (or subchildren) of the specified container
  149. * want a crack at the event.
  150. * If one of them wants it, then it will "DO-THE-RIGHT-THING"
  151. */
  152. public boolean fireKeyboardAction(KeyEvent e, boolean pressed, Container topAncestor) {
  153. if (e.isConsumed()) {
  154. System.out.println("Aquired pre-used event!");
  155. Thread.dumpStack();
  156. }
  157. KeyStroke ks;
  158. if(e.getID() == KeyEvent.KEY_TYPED) {
  159. ks=KeyStroke.getKeyStroke(e.getKeyChar());
  160. } else {
  161. ks=KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers(), !pressed);
  162. }
  163. Hashtable keyMap = (Hashtable)containerMap.get(topAncestor);
  164. if (keyMap != null) { // this container isn't registered, so bail
  165. Object tmp = keyMap.get(ks);
  166. if (tmp == null) {
  167. // don't do anything
  168. } else if ( tmp instanceof JComponent) {
  169. JComponent c = (JComponent)tmp;
  170. if ( c.isShowing() && c.isEnabled() ) { // only give it out if enabled and visible
  171. fireBinding(c, ks, e, pressed);
  172. }
  173. } else if ( tmp instanceof Vector) { //more than one comp registered for this
  174. Vector v = (Vector)tmp;
  175. // There is no well defined order for WHEN_IN_FOCUSED_WINDOW
  176. // bindings, but we give precedence to those bindings just
  177. // added. This is done so that JMenus WHEN_IN_FOCUSED_WINDOW
  178. // bindings are accessed before those of the JRootPane (they
  179. // both have a WHEN_IN_FOCUSED_WINDOW binding for enter).
  180. for (int counter = v.size() - 1; counter >= 0; counter--) {
  181. JComponent c = (JComponent)v.elementAt(counter);
  182. //System.out.println("Trying collision: " + c + " vector = "+ v.size());
  183. if ( c.isShowing() && c.isEnabled() ) { // don't want to give these out
  184. fireBinding(c, ks, e, pressed);
  185. if (e.isConsumed())
  186. return true;
  187. }
  188. }
  189. } else {
  190. System.out.println( "Unexpected condition in fireKeyboardAction " + tmp);
  191. // This means that tmp wasn't null, a JComponent, or a Vector. What is it?
  192. Thread.dumpStack();
  193. }
  194. }
  195. if (e.isConsumed()) {
  196. return true;
  197. }
  198. // if no one else handled it, then give the menus a crack
  199. // The're handled differently. The key is to let any JMenuBars
  200. // process the event
  201. if ( keyMap != null) {
  202. Vector v = (Vector)keyMap.get(JMenuBar.class);
  203. if (v != null) {
  204. Enumeration iter = v.elements();
  205. while (iter.hasMoreElements()) {
  206. JMenuBar mb = (JMenuBar)iter.nextElement();
  207. if ( mb.isShowing() && mb.isEnabled() ) { // don't want to give these out
  208. fireBinding(mb, ks, e, pressed);
  209. if (e.isConsumed()) {
  210. return true;
  211. }
  212. }
  213. }
  214. }
  215. }
  216. return e.isConsumed();
  217. }
  218. void fireBinding(JComponent c, KeyStroke ks, KeyEvent e, boolean pressed) {
  219. if (c.processKeyBinding(ks, e, JComponent.WHEN_IN_FOCUSED_WINDOW,
  220. pressed)) {
  221. e.consume();
  222. }
  223. }
  224. public void registerMenuBar(JMenuBar mb) {
  225. Container top = getTopAncestor(mb);
  226. Hashtable keyMap = (Hashtable)containerMap.get(top);
  227. if (keyMap == null) { // lazy evaluate one
  228. keyMap = registerNewTopContainer(top);
  229. }
  230. // use the menubar class as the key
  231. Vector menuBars = (Vector)keyMap.get(JMenuBar.class);
  232. if (menuBars == null) { // if we don't have a list of menubars,
  233. // then make one.
  234. menuBars = new Vector();
  235. keyMap.put(JMenuBar.class, menuBars);
  236. }
  237. if (!menuBars.contains(mb)) {
  238. menuBars.addElement(mb);
  239. }
  240. }
  241. public void unregisterMenuBar(JMenuBar mb) {
  242. Object topContainer = getTopAncestor(mb);
  243. Hashtable keyMap = (Hashtable)containerMap.get(topContainer);
  244. if (keyMap!=null) {
  245. Vector v = (Vector)keyMap.get(JMenuBar.class);
  246. if (v != null) {
  247. v.removeElement(mb);
  248. if (v.isEmpty()) {
  249. keyMap.remove(JMenuBar.class);
  250. if (keyMap.isEmpty()) {
  251. // remove table to enable GC
  252. containerMap.remove(topContainer);
  253. }
  254. }
  255. }
  256. }
  257. }
  258. protected Hashtable registerNewTopContainer(Container topContainer) {
  259. Hashtable keyMap = new Hashtable();
  260. containerMap.put(topContainer, keyMap);
  261. return keyMap;
  262. }
  263. /**
  264. * This class is used to create keys for a hashtable
  265. * which looks up topContainers based on component, keystroke pairs
  266. * This is used to make unregistering KeyStrokes fast
  267. */
  268. class ComponentKeyStrokePair {
  269. Object component;
  270. Object keyStroke;
  271. public ComponentKeyStrokePair(Object comp, Object key) {
  272. component = comp;
  273. keyStroke = key;
  274. }
  275. public boolean equals(Object o) {
  276. if ( !(o instanceof ComponentKeyStrokePair)) {
  277. return false;
  278. }
  279. ComponentKeyStrokePair ckp = (ComponentKeyStrokePair)o;
  280. return ((component.equals(ckp.component)) && (keyStroke.equals(ckp.keyStroke)));
  281. }
  282. public int hashCode() {
  283. return component.hashCode() * keyStroke.hashCode();
  284. }
  285. }
  286. } // end KeyboardManager