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