Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[albireo-dev] Keystroke Contention

I've generalized (a bit) and merged in the SAS code that deals with keystroke contention. In particular, it solves a problem on Windows where a Swing popup menu triggered with Shift-F10 can contend with Windows' default processing of the release of the F10 key.

The new code records a set of keystrokes to be consumed by the SwingControl when it encounters them. Default keystrokes can be initialized by SwingControl, and there is API to add and remove keystrokes from the set.

The keystrokes in the set are consumed by a KeyListener in SwingControl. In the case of Windows popups, we can consume the *release* of Shift-F10, which disables the downstream processing by Windows while not interfering with Swing's handling of the Shift-F10 key *press*.

This code will not help with all conflicts. For example, accelerator keys and RCP key bindings are processed before the SwingControl's key listener gets control. There may be a bit more we can do here, but we should also advise users to avoid conflicts themselves where possible. For example, RCP keys do not have to be bound globally.
### Eclipse Workspace Patch 1.0
#P org.eclipse.albireo.examples.plugin
Index: src/org/eclipse/albireo/examples/plugin/views/AwtPopupView.java
===================================================================
RCS file: /cvsroot/technology/org.eclipse.albireo/org.eclipse.albireo.examples.plugin/src/org/eclipse/albireo/examples/plugin/views/AwtPopupView.java,v
retrieving revision 1.2
diff -u -r1.2 AwtPopupView.java
--- src/org/eclipse/albireo/examples/plugin/views/AwtPopupView.java	28 Feb 2008 05:04:58 -0000	1.2
+++ src/org/eclipse/albireo/examples/plugin/views/AwtPopupView.java	29 Feb 2008 22:47:14 -0000
@@ -4,7 +4,11 @@
 import java.awt.Component;
 import java.awt.GridLayout;
 import java.awt.MenuItem;
+import java.awt.Point;
 import java.awt.PopupMenu;
+import java.awt.Rectangle;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
 import java.awt.event.MouseEvent;
 
@@ -107,7 +111,7 @@
         });
     }
 
-    private void addAwtPopup(Component c) {
+    private void addAwtPopup(final JComponent c) {
         final PopupMenu popup = new PopupMenu();
         MenuItem item = new MenuItem("AWT Item 1");
         popup.add(item);
@@ -116,6 +120,18 @@
         popup.add(item);
 
         c.add(popup);
+        c.addKeyListener(new KeyAdapter() {
+            public void keyPressed(KeyEvent e) { 
+                if (e.getKeyCode() == KeyEvent.VK_F10 && 
+                        ((e.getModifiers() & KeyEvent.SHIFT_MASK) != 0)) {
+                    Rectangle r = c.getVisibleRect();
+                    Point point = new Point(r.x + r.width/2,
+                                            r.y + r.height/2);
+                    popup.show(c, point.x, point.y);
+                }
+            }
+            
+        });
         c.addMouseListener(new MouseAdapter() {
 
             public void mousePressed(MouseEvent e) {
@@ -135,13 +151,27 @@
         });
     }
 
-    private void addSwingPopup(Component c) {
+    private void addSwingPopup(final JComponent c) {
         final JPopupMenu popup = new JPopupMenu();
         JMenuItem item = new JMenuItem("Swing Item 1");
         popup.add(item);
         item = new JMenuItem("Swing Item 2");
         popup.add(item);
         
+        // We still run on Java 1.4, so can't use the new JComponent.setComponentPopupMenu
+        // method
+        c.addKeyListener(new KeyAdapter() {
+            public void keyPressed(KeyEvent e) { 
+                if (e.getKeyCode() == KeyEvent.VK_F10 && 
+                        ((e.getModifiers() & KeyEvent.SHIFT_MASK) != 0)) {
+                    Rectangle r = c.getVisibleRect();
+                    Point point = new Point(r.x + r.width/2,
+                                            r.y + r.height/2);
+                    popup.show(c, point.x, point.y);
+                }
+            }
+            
+        });
         c.addMouseListener(new MouseAdapter() {
 
             public void mousePressed(MouseEvent e) {
@@ -159,6 +189,7 @@
             }
             
         });
+        
     }
     public void setFocus() {
         swingControl.setFocus();
#P org.eclipse.albireo.core
Index: src/org/eclipse/albireo/core/SwingControl.java
===================================================================
RCS file: /cvsroot/technology/org.eclipse.albireo/org.eclipse.albireo.core/src/org/eclipse/albireo/core/SwingControl.java,v
retrieving revision 1.50
diff -u -r1.50 SwingControl.java
--- src/org/eclipse/albireo/core/SwingControl.java	28 Feb 2008 05:04:57 -0000	1.50
+++ src/org/eclipse/albireo/core/SwingControl.java	29 Feb 2008 22:47:14 -0000
@@ -19,9 +19,11 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.swing.JApplet;
 import javax.swing.JComponent;
@@ -41,6 +43,8 @@
 import org.eclipse.swt.awt.SWT_AWT;
 import org.eclipse.swt.events.ControlEvent;
 import org.eclipse.swt.events.ControlListener;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
 import org.eclipse.swt.graphics.Color;
 import org.eclipse.swt.graphics.Font;
 import org.eclipse.swt.graphics.FontData;
@@ -192,6 +196,7 @@
             ComponentDebugging.addComponentSizeDebugListeners(frame);
 
         initializeFocusManagement();
+        initKeystrokeManagement();
 
         if (HIDE_SWING_POPUPS_ON_SWT_SHELL_BOUNDS_CHANGE) {
             getShell().addControlListener(shellControlListener);
@@ -1120,4 +1125,90 @@
     public String toString() {
         return super.toString() + " [frame=" + ((frame != null) ? frame.getName() : "null") + "]";
     }
+    
+    // ============================= Keystroke Management =============================
+    
+    Set consumedKeystrokes = new HashSet();
+    
+    /**
+     * Initializes keystroke management for this control. 
+     */
+    protected void initKeystrokeManagement() {
+        assert Display.getCurrent() != null;
+        
+        // Platform-specific default consumed keystrokes
+        if (Platform.isWin32()) {
+            // Shift-F10 is normally used to display a context popup menu. 
+            // When this happens in Windows and inside of a Swing component,
+            // the consumption of the key is unknown to SWT. As a result, 
+            // when SWT passes control to the default windows windowProc, 
+            // Windows will handle the Shift-F10 like it handles F10 and Alt 
+            // (alone); it will shift keyboard focus to the main menu bar. 
+            // This will interfere with the Swing popup menu by removing 
+            // its focus. Prevents the default windows behavior by 
+            // consuming the released keystroke event for Shift-F10, so that 
+            // the Swing context menu, if any. can be properly used.
+            //
+            // TODO: This is really l&f-dependent. Find a way to query the l&f for the popup key
+            addConsumedKeystroke(new SwtKeystroke(SWT.KeyUp, SWT.F10, SWT.SHIFT));
+        }
+        
+        addKeyListener(new KeyListener() {
+            public void keyPressed(KeyEvent e) {
+                handleKeyEvent(SWT.KeyDown, e);
+            }
+            public void keyReleased(KeyEvent e) {
+                handleKeyEvent(SWT.KeyUp, e);
+            }
+        });
+        
+    }
+    
+    protected void handleKeyEvent(int type, KeyEvent e) {
+        assert Display.getCurrent() != null;
+        SwtKeystroke key = new SwtKeystroke(type, e);
+        if (consumedKeystrokes.contains(key)) {
+            // System.err.println("Capturing key " + key);
+            e.doit = false;
+        }
+    }
+    
+    /**
+     * Returns the set of keystrokes which will be consumed by this control. See 
+     * {@link #addConsumedKeystroke(SwtKeystroke)} for more information. 
+     * 
+     * @return Set the keystrokes configured to be consumed. 
+     */
+    public Set getConsumedKeystrokes() {
+        checkWidget();
+        return Collections.unmodifiableSet(consumedKeystrokes); 
+    }
+    
+    /**
+     * Configures a SWT keystroke to be consumed automatically by this control
+     * whenever it is detected. 
+     * <p>
+     * This method can be used to block a SWT keystroke from being propagated 
+     * both to the embedded Swing component and to the native window system. 
+     * By consuming a keystroke, you can avoid conflicts in key handling between
+     * the Swing component and the rest of the application. 
+     * 
+     * @param key the keystroke to consume. 
+     */
+    public void addConsumedKeystroke(SwtKeystroke key) {
+        checkWidget();
+        consumedKeystrokes.add(key);
+    }
+    
+    /**
+     * Removes a SWT keystroke from the set of keystrokes to be consumed by this control. 
+     * See {@link #addConsumedKeystroke(SwtKeystroke)} for more information. 
+     * 
+     * @return <code>true</code> if a keystroke was successfully removed from the set.  
+     */
+    public boolean removeConsumedKeystroke(SwtKeystroke key) {
+        checkWidget();
+        return consumedKeystrokes.remove(key);
+    }
+    
 }
Index: src/org/eclipse/albireo/core/SwtKeystroke.java
===================================================================
RCS file: src/org/eclipse/albireo/core/SwtKeystroke.java
diff -N src/org/eclipse/albireo/core/SwtKeystroke.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/albireo/core/SwtKeystroke.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,125 @@
+package org.eclipse.albireo.core;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.widgets.Event;
+
+/**
+ * A Keystroke as reported in a SWT KeyEvent. 
+ */
+public class SwtKeystroke {
+    private final int eventType;
+    private final int keyCode;
+    private final int stateMask;
+    
+    /**
+     * Constructor
+     * 
+     * @param eventType the SWT event type
+     * @param keyCode the SWT key code
+     * @param stateMask the SWT state mask
+     */
+    public SwtKeystroke(int eventType, int keyCode, int stateMask) {
+        if ((eventType != SWT.KeyDown) && (eventType != SWT.KeyUp)) {
+            SWT.error(SWT.ERROR_INVALID_ARGUMENT);
+        }
+        this.eventType = eventType;
+        this.keyCode = keyCode;
+        this.stateMask = stateMask;
+    }
+    
+    /**
+     * Constructor
+     * 
+     * @param eventType the SWT event type
+     * @param e the SWT KeyEvent
+     */
+    public SwtKeystroke(int eventType, KeyEvent e) {
+        this.eventType = eventType;
+        keyCode = e.keyCode;
+        stateMask = e.stateMask;
+    }
+
+    /**
+     * Constructor
+     * 
+     * @param e the SWT Event
+     */
+    public SwtKeystroke(Event e) {
+        eventType = e.type;
+        keyCode = e.keyCode;
+        stateMask = e.stateMask;
+    }
+
+    /**
+     * Returns the type of SWT key event represented by this object. 
+     * 
+     * @return {@link SWT#KeyDown} or {@link SWT#KeyUp}
+     */
+    public int getEventType() {
+        return eventType;
+    }
+
+    /**
+     * Returns the SWT key code for this keystroke. See the 
+     * constants in the {@link SWT} class for the possible
+     * values.  
+     * 
+     * @return int key code
+     */
+    public int getKeyCode() {
+        return keyCode;
+    }
+
+    /**
+     * Returns the SWT state mask for this keystroke. See the 
+     * constants in the {@link SWT} class for the possible
+     * values.  
+     * 
+     * @return int state mask
+     */
+    public int getStateMask() {
+        return stateMask;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#hashCode()
+     */
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + eventType;
+        result = prime * result + keyCode;
+        result = prime * result + stateMask;
+        return result;
+    }
+
+    /* (non-Javadoc)
+     * @see java.lang.Object#equals(java.lang.Object)
+     */
+    public boolean equals(Object obj) {
+        if (this == obj)
+            return true;
+        if (obj == null)
+            return false;
+        if (getClass() != obj.getClass())
+            return false;
+        SwtKeystroke other = (SwtKeystroke)obj;
+        if (eventType != other.eventType)
+            return false;
+        if (keyCode != other.keyCode)
+            return false;
+        if (stateMask != other.stateMask)
+            return false;
+        return true;
+    }
+    
+    /* (non-Javadoc)
+     * @see java.lang.Object#toString()
+     */
+    public String toString() {
+        return "key: type=" + eventType + ", code=" + keyCode + ", stateMask=" + stateMask;
+    }
+    
+
+}

Back to the top