This patch implements the package-private class
javax.swing.KeyboardManager and makes some adjustments in JComponent to
allow keybindings registered with the condition WHEN_IN_FOCUSED_WINDOW
to work.  

This mechanism is not 100% correct yet.  Attached is a test case that
works.  Click on the JButton to give it focus.  Then pressing 'a' prints
"I'm Focused" to the screen.  Clicking on the JCheckBox gives it focus
(so the JButton is not focused) and then pressing 'a' prints "I'm In
Focused Window" to the screen ==> this is new functionality, the JButton
can register key bindings that work even when it is not focused or an
ancestor of the focused component, it's just in the focused window.

However, if, in the testcase, the keybinding is registered before the
JButton is added to the JFrame (its top-level ancestor), our
implementation fails.  This is because when the key action is bound the
JButton had no top-level ancestor, and when the JButton was added to the
JFrame it doesn't re-hash its bindings.  

So, I have to find a way to re-hash WHEN_IN_FOCUSED_WINDOW bindings when
components are added to containers.  Unfortunately, adding occurs in
Containers in AWT and much of the needed code is in package-private
methods or classes in Swing.  For instance, to re-hash bindings I need
to use the KeyboardManager class, or at least the method
JComponent.updateComponentInputMap.  Anyone have any suggestions?

2005-11-09  Anthony Balkissoon  <[EMAIL PROTECTED]>

        * javax/swing/JComponent.java:
        (processKeyEvent): Use local variables for boolean pressed and for 
        the KeyStroke.  Implemented the code for WHEN_IN_FOCUSED_WINDOW
        bindings.
        (updateComponentInputMap): Implemented and fixed typo in docs.
        * javax/swing/KeyboardManager.java: New class.


Index: javax/swing/JComponent.java
===================================================================
RCS file: /cvsroot/classpath/classpath/javax/swing/JComponent.java,v
retrieving revision 1.79
diff -u -r1.79 JComponent.java
--- javax/swing/JComponent.java	9 Nov 2005 19:15:03 -0000	1.79
+++ javax/swing/JComponent.java	9 Nov 2005 21:54:20 -0000
@@ -2167,13 +2167,14 @@
     // 4. The WHEN_IN_FOCUSED_WINDOW maps of all the enabled components in
     //    the focused window are searched.
     
-    if (processKeyBinding(KeyStroke.getKeyStrokeForEvent(e), 
-                          e, WHEN_FOCUSED, e.getID() == KeyEvent.KEY_PRESSED))
+    KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
+    boolean pressed = e.getID() == KeyEvent.KEY_PRESSED;
+    
+    if (processKeyBinding(keyStroke, e, WHEN_FOCUSED, pressed))
       // This is step 1 from above comment.
       e.consume();
-    else if (processKeyBinding(KeyStroke.getKeyStrokeForEvent(e),
-                               e, WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
-                               e.getID() == KeyEvent.KEY_PRESSED))
+    else if (processKeyBinding
+             (keyStroke, e, WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, pressed))
       // This is step 2 from above comment.
       e.consume();
     else
@@ -2183,9 +2184,8 @@
         while ((current = current.getParent()) instanceof JComponent)
           {
             if (((JComponent)current).processKeyBinding
-                (KeyStroke.getKeyStrokeForEvent(e), e, 
-                 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 
-                 e.getID() == KeyEvent.KEY_PRESSED))
+                (keyStroke, e,WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 
+                 pressed))
               {
                 e.consume();
                 break;
@@ -2196,10 +2196,10 @@
           }
         if (e.isConsumed())
           return;
-        
+                
         // This is step 4 from above comment.
-        // FIXME: Implement.  Note, should use ComponentInputMaps rather
-        // than walking the entire containment hierarchy.
+        if (KeyboardManager.getManager().processKeyStroke(this, keyStroke, e))
+          e.consume();
       }
   }
 
@@ -3306,7 +3306,7 @@
   /**
    * This is the method that gets called when the WHEN_IN_FOCUSED_WINDOW map
    * is changed.
-   * @param c the JComponent associated with the WHEN_IN_FOCUSED_WINDOW map
+   * @param changed the JComponent associated with the WHEN_IN_FOCUSED_WINDOW map
    */
   void updateComponentInputMap(ComponentInputMap changed)
   {
@@ -3322,6 +3322,12 @@
       return;
     
     // Now we have to update the keyboard manager's hashtable
-    // FIXME: not yet implemented
+    KeyboardManager km = KeyboardManager.getManager();
+    
+    // This is a poor strategy, should be improved.  We currently 
+    // delete all the old bindings for the component and then register
+    // the current bindings.
+    km.clearBindingsForComp(changed.getComponent());
+    km.registerEntireMap((ComponentInputMap) getInputMap(WHEN_IN_FOCUSED_WINDOW));
   }
 }
Index: javax/swing/KeyboardManager.java
===================================================================
RCS file: javax/swing/KeyboardManager.java
diff -N javax/swing/KeyboardManager.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ javax/swing/KeyboardManager.java	9 Nov 2005 21:54:20 -0000
@@ -0,0 +1,216 @@
+/* KeyboardManager.java -- 
+   Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2, or (at your option)
+any later version.
+
+GNU Classpath is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Classpath; see the file COPYING.  If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library.  Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module.  An independent module is a module which is not derived from
+or based on this library.  If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so.  If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package javax.swing;
+
+import java.applet.Applet;
+import java.awt.Container;
+import java.awt.Window;
+import java.awt.event.KeyEvent;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * This class maintains a mapping from top-level containers to a 
+ * Hashtable.  The Hashtable maps KeyStrokes to Components to be used when 
+ * Components register keyboard actions with the condition
+ * JComponent.WHEN_IN_FOCUSED_WINDOW.
+ * 
+ * @author Anthony Balkissoon <[EMAIL PROTECTED]>
+ *
+ */
+class KeyboardManager
+{
+  /** Shared instance of KeyboardManager **/
+  static KeyboardManager manager = new KeyboardManager();
+  
+  /** 
+   * A mapping between top level containers and Hashtables that 
+   * map KeyStrokes to Components.
+   */
+  Hashtable topLevelLookup = new Hashtable();  
+  
+  /**
+   * Returns the shared instance of KeyboardManager.
+   * @return the shared instance of KeybaordManager.
+   */
+  public static KeyboardManager getManager()
+  {
+    return manager;
+  }
+  /**
+   * Returns the top-level ancestor for the given JComponent.
+   * @param c the JComponent whose top-level ancestor we want
+   * @return the top-level ancestor for the given JComponent.
+   */
+  static Container findTopLevel (JComponent c)
+  {
+    Container topLevel = (c instanceof Container) ? c : c.getParent();
+    while (topLevel != null && 
+           !(topLevel instanceof Window) && 
+           !(topLevel instanceof Applet) && 
+           !(topLevel instanceof JInternalFrame))
+      topLevel = topLevel.getParent();
+    return topLevel;
+  }
+  
+  /**
+   * Returns the Hashtable that maps KeyStrokes to Components, for 
+   * the specified top-level container c.  If no Hashtable exists
+   * we create and register it here and return the newly created
+   * Hashtable.
+   * 
+   * @param c the top-level container whose Hashtable we want
+   * @return the Hashtable mapping KeyStrokes to Components for the 
+   * specified top-level container
+   */
+  public Hashtable getHashtableForTopLevel (Container c)
+  {
+    Hashtable keyToComponent = (Hashtable)topLevelLookup.get(c);
+    if (keyToComponent == null)
+      {
+        keyToComponent = new Hashtable();
+        topLevelLookup.put(c, keyToComponent);
+      }
+    return keyToComponent;
+  }
+  
+  /**
+   * Registers a KeyStroke with a Component.  This does not register
+   * the KeyStroke to a specific Action.  When searching for a 
+   * WHEN_IN_FOCUSED_WINDOW binding we will first go up to the focused
+   * top-level Container, then get the Hashtable that maps KeyStrokes 
+   * to components for that particular top-level Container, then 
+   * call processKeyBindings on that component with the condition
+   * JComponent.WHEN_IN_FOCUSED_WINDOW.
+   * @param comp the JComponent associated with the KeyStroke
+   * @param key the KeyStroke
+   */
+  public void registerBinding(JComponent comp, KeyStroke key)
+  {
+    // This method associates a KeyStroke with a particular JComponent
+    // When the KeyStroke occurs, if this component's top-level ancestor
+    // has focus (one of its children is the focused Component) then 
+    // comp.processKeyBindings will be called with condition
+    // JComponent.WHEN_IN_FOCUSED_WINDOW.
+
+    // Look for the JComponent's top-level parent and return if it is null
+    Container topLevel = findTopLevel(comp);
+    if (topLevel == null)
+      return;
+    
+    // Now get the Hashtable for this top-level container
+    Hashtable keyToComponent = getHashtableForTopLevel(topLevel);    
+    
+    // And add the new binding to this Hashtable
+    // FIXME: should allow more than one JComponent to be associated
+    // with a KeyStroke, in case one of them is disabled
+    keyToComponent.put(key, comp);
+  }
+  
+  public void clearBindingsForComp(JComponent comp)
+  {
+    // This method clears all the WHEN_IN_FOCUSED_WINDOW bindings associated
+    // with <code>comp</code>.  This is used for a terribly ineffcient
+    // strategy in which JComponent.updateComponentInputMap simply clears
+    // all bindings associated with its component and then reloads all the
+    // bindings from the updated ComponentInputMap.  This is only a preliminary
+    // strategy and should be improved upon once the WHEN_IN_FOCUSED_WINDOW
+    // bindings work.
+    
+    // Find the top-level ancestor
+    
+    Container topLevel = findTopLevel(comp);
+    if (topLevel == null)
+      return;
+    // And now get its Hashtable
+    Hashtable keyToComponent = getHashtableForTopLevel(topLevel);
+
+    Enumeration keys = keyToComponent.keys();
+    Object temp;
+
+    // Iterate through the keys and remove any key whose value is comp
+    while (keys.hasMoreElements())
+      {
+        temp = keys.nextElement();
+        if (comp == (JComponent)keyToComponent.get(temp))
+          keyToComponent.remove(temp);          
+      }
+  }
+    
+  /**
+   * This method registers all the bindings in the given ComponentInputMap.
+   * Rather than call registerBinding on all the keys, we do the work here
+   * so that we don't duplicate finding the top-level container and 
+   * getting its Hashtable.
+   * 
+   * @param map the ComponentInputMap whose bindings we want to register
+   */
+  public void registerEntireMap (ComponentInputMap map)
+  {
+    JComponent comp = map.getComponent();
+    KeyStroke[] keys = map.keys();
+    
+    // Find the top-level container associated with this ComponentInputMap
+    Container topLevel = findTopLevel(comp);
+    if (topLevel == null)
+      return;
+    
+    // Register the KeyStrokes in the top-level container's Hashtable
+    Hashtable keyToComponent = getHashtableForTopLevel(topLevel);
+    for (int i = 0; i < keys.length; i++)
+      keyToComponent.put(keys[i], comp);
+  }
+  
+  public boolean processKeyStroke (JComponent comp, KeyStroke key, KeyEvent e)
+  {
+    // Look for the top-level ancestor
+    Container topLevel = findTopLevel(comp);
+    if (topLevel == null)
+      return false;
+    // Now get the Hashtable for that top-level container
+    Hashtable keyToComponent = getHashtableForTopLevel(topLevel);
+    Enumeration keys = keyToComponent.keys();
+    JComponent target = (JComponent)keyToComponent.get(key);
+    if (target == null)
+      return false;
+    return target.processKeyBinding
+      (key, e, JComponent.WHEN_IN_FOCUSED_WINDOW, 
+       e.getID() == KeyEvent.KEY_PRESSED);
+  }
+}
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

class Test
{
  public static void main(String args[]) 
  {
    // Set up the JFrame
    JFrame jf = new JFrame();
    jf.setSize(200,200);
    jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    // A label and a button
    JCheckBox cb = new JCheckBox("dummy");
    JButton b = new JButton();

    // Actions for the button to perform
    Action focused = new AbstractAction(){
        public void actionPerformed (ActionEvent e)
        {
          System.out.println ("I'm Focused");
        }
      };
    Action inWindow = new AbstractAction(){
        public void actionPerformed (ActionEvent e)
        {
          System.out.println ("I'm In Focused Window");
        }
      };
        
    // Add the components to the JFrame and show it
    jf.add(cb, BorderLayout.NORTH);
    jf.add(b, BorderLayout.SOUTH);

    // Register the actions with the key 'a'
    KeyStroke k = KeyStroke.getKeyStroke('a');
    b.registerKeyboardAction(focused, "focused", k, JComponent.WHEN_FOCUSED);
    b.registerKeyboardAction(inWindow, "window", k, JComponent.WHEN_IN_FOCUSED_WINDOW);

    jf.show();
  }
}

_______________________________________________
Classpath-patches mailing list
Classpath-patches@gnu.org
http://lists.gnu.org/mailman/listinfo/classpath-patches

Reply via email to