Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
Re: [albireo-dev] Traversing out of embedded SwingControls on Windows

Gordon Hirsch wrote:

The tricky part is handling extra keystrokes between the first tab and
the transfer of focus to the next control which involves a move to the
SWT thread. I have a partial solution, but there are still a number of
problems if you tab very quickly. (The SAS contribution did not handle
this at all)

My code required a lot more rework than anticipated. I'm discovering that Windows does not report the current focus state reliably when there are embedded AWT frames in the focus cycle. This was causing tab stops to be skipped intermittently, for no good reason. I seem to have worked around it and have committed my changes.

(Basically, Control.forceFocus() is incorrectly returning false in some cases. I've overridden this method in SwingControl and correct the return value when it is clearly wrong (win32 only).)

At this point, tab traversals work reliably for me on two different Windows machines with JDK 1.5/1.6 + Eclipse 3.4 and with JDK1.6 + Eclipse 3.3. On JDK1.5+Eclipse3.3 tabs are still dropped on rare occasions. I'm going to step away from this problem for a while before it drives me completely crazy. :-) There are other focus additions that I need to work on.

For completeness, I'm attaching a patch. But it won't be very readable due to the recent churn.
### Eclipse Workspace Patch 1.0
#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.37
diff -u -r1.37 SwingControl.java
--- src/org/eclipse/albireo/core/SwingControl.java	20 Feb 2008 07:24:26 -0000	1.37
+++ src/org/eclipse/albireo/core/SwingControl.java	20 Feb 2008 23:46:18 -0000
@@ -865,8 +865,18 @@
     }
     
     
+    
+    
     // ============================= Events and Listeners =============================
 
+    public boolean forceFocus() {
+        boolean result = super.forceFocus();
+        if (focusHandler != null) {
+            result = focusHandler.handleForceFocus(this, result);
+        }
+        return result;
+    }
+
     private List sizeListeners = new ArrayList();
     
     /**
Index: src/org/eclipse/albireo/internal/FocusHandler.java
===================================================================
RCS file: /cvsroot/technology/org.eclipse.albireo/org.eclipse.albireo.core/src/org/eclipse/albireo/internal/FocusHandler.java,v
retrieving revision 1.5
diff -u -r1.5 FocusHandler.java
--- src/org/eclipse/albireo/internal/FocusHandler.java	20 Feb 2008 08:34:17 -0000	1.5
+++ src/org/eclipse/albireo/internal/FocusHandler.java	20 Feb 2008 23:46:18 -0000
@@ -8,6 +8,7 @@
 import java.awt.KeyboardFocusManager;
 import java.awt.event.KeyEvent;
 import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.Set;
@@ -15,18 +16,29 @@
 import org.eclipse.albireo.core.SwingControl;
 import org.eclipse.albireo.core.ThreadingHandler;
 import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
 import org.eclipse.swt.widgets.Control;
 import org.eclipse.swt.widgets.Display;
 import org.eclipse.swt.widgets.Event;
 import org.eclipse.swt.widgets.Listener;
 
-public class FocusHandler implements Listener, KeyEventDispatcher {
+public class FocusHandler {
 
     private final Frame frame;
     private final SwingControl swingControl;
     private final Display display;
-    private volatile AWTKeyStroke traverseKey = null;
-    private int extraTabCount;
+    private boolean pendingTraverse = false;
+    private int extraTabCount = 0;
+    private boolean isActiveSwt;
+    private boolean isFocusedSwt;
+    private static SwingControl activeSwingControl = null;
+    
+    // Listeners
+    private KeyEventDispatcher keyEventDispatcher = new AwtKeyDispatcher();
+    private WindowFocusListener awtWindowFocusListener = new AwtWindowFocusListener();
+    private FocusListener swtFocusListener = new SwtFocusListener();
+    private Listener swtEventFilter = new SwtEventFilter();
     
     // Whether to print debugging information regarding focus events.
     public static final boolean verboseFocusEvents = false;
@@ -36,7 +48,7 @@
     static private boolean synthesizeMethodInitialized = false;
     static private Method synthesizeMethod = null;
 
-    public FocusHandler(SwingControl swingControl, final Frame frame) {
+    public FocusHandler(final SwingControl swingControl, final Frame frame) {
         assert Display.getCurrent() != null;     // On SWT event thread
 
         if (verboseFocusEvents)
@@ -49,16 +61,22 @@
         getSynthesizeMethod(frame.getClass());
 
         // TODO: optimize by adding just one pair of filters for all SwingControls
-        display.addFilter(SWT.Activate, this);
-        display.addFilter(SWT.Deactivate, this);
+        display.addFilter(SWT.Activate, swtEventFilter);
+        display.addFilter(SWT.Deactivate, swtEventFilter);
+        
+        frame.addWindowFocusListener(awtWindowFocusListener);
+        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(keyEventDispatcher);
+        
+        swingControl.addFocusListener(swtFocusListener);
         
-        KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(this);
     }
     
     public void dispose() {
-        display.removeFilter(SWT.Activate, this);
-        display.removeFilter(SWT.Deactivate, this);
-        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this);
+        display.removeFilter(SWT.Activate, swtEventFilter);
+        display.removeFilter(SWT.Deactivate, swtEventFilter);
+        frame.removeWindowFocusListener(awtWindowFocusListener);
+        KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(keyEventDispatcher);
+        swingControl.removeFocusListener(swtFocusListener);
     }
     
     // =====
@@ -74,18 +92,17 @@
             Set traverseBackwardKeys = frame.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS);
             AWTKeyStroke key = AWTKeyStroke.getAWTKeyStrokeForEvent(e);
             
-            if (traverseKey == null) {
+            if (!pendingTraverse) {
                 // We haven't started to traverse out yet. Check to see if the traversal key has been 
                 // hit and that we are at the last/first compoent in the traversal group. 
                 
                 Component limit = frame.getFocusTraversalPolicy().getLastComponent(frame);
                 if (traverseForwardKeys.contains(key) && (limit == e.getComponent() || limit == null)) {
                     // Tabbing forward from last component in frame (or empty frame)
-                    traverseKey = key;
-                    swtTraverse(SWT.TRAVERSE_TAB_NEXT);
+                    pendingTraverse = true;
+                    swtTraverse(SWT.TRAVERSE_TAB_NEXT, 1);
                     if (verboseTraverseOut) {
-                        System.err.println("@" + System.currentTimeMillis() + " " + System.identityHashCode(this) +  
-                                           " AWT: traversing out (forward)");
+                        trace("AWT: traversing out (forward)");
                     }
                     e.consume();
                     return true;
@@ -94,11 +111,10 @@
                 limit = frame.getFocusTraversalPolicy().getFirstComponent(frame);
                 if (traverseBackwardKeys.contains(key) && (limit == e.getComponent() || limit == null)) {
                     // Tabbing backward from first component in frame
-                    traverseKey = key;
-                    swtTraverse(SWT.TRAVERSE_TAB_PREVIOUS);
+                    pendingTraverse = true;
+                    swtTraverse(SWT.TRAVERSE_TAB_PREVIOUS, 1);
                     if (verboseTraverseOut) {
-                        System.err.println("@" + System.currentTimeMillis() + " " + System.identityHashCode(this) +  
-                                           " AWT: traversing out (backward)");
+                        trace("AWT: traversing out (backward)");
                     }
                     e.consume();
                     return true;
@@ -108,49 +124,46 @@
                 // right number of SWT traversals can be done in the SWT thread. This prevents us from losing
                 // tabs when typing them very fast. 
                 // TODO: this needs to be generalized to all keystrokes, not just traversals
-                synchronized (this) {
-                    if (traverseForwardKeys.contains(key)) {
-                        if (verboseTraverseOut) {
-                            System.err.println("@" + System.currentTimeMillis() + " " + System.identityHashCode(this) +  
-                                               " forward traversal typeahead");
-                        }
-                        extraTabCount++;
-                        e.consume();
-                        return true;
-                    } else if (traverseBackwardKeys.contains(key)) {
-                        if (verboseTraverseOut) {
-                            System.err.println("@" + System.currentTimeMillis() + " " + System.identityHashCode(this) +  
-                                               " backward traversal typeahead");
-                        }
-                        extraTabCount--;
-                        e.consume();
-                        return true;
+                if (traverseForwardKeys.contains(key)) {
+                    if (verboseTraverseOut) {
+                        trace("forward traversal typeahead");
                     }
+                    extraTabCount++;
+                    e.consume();
+                    return true;
+                } else if (traverseBackwardKeys.contains(key)) {
+                    if (verboseTraverseOut) {
+                        trace("backward traversal typeahead");
+                    }
+                    extraTabCount--;
+                    e.consume();
+                    return true;
                 }
             }
         }
         return false;
     }
     
-    protected void swtTraverse(final int direction) {
+    protected void processTypeAheadKeys() {
+        if (pendingTraverse && extraTabCount != 0) {
+            if (verboseTraverseOut) {
+                trace("Processing typeahead traversals, count=" + extraTabCount);
+            }
+            int direction = (extraTabCount > 0) ? SWT.TRAVERSE_TAB_NEXT : SWT.TRAVERSE_TAB_PREVIOUS;
+            swtTraverse(direction, Math.abs(extraTabCount));
+        }
+        pendingTraverse = false;
+        extraTabCount = 0;
+    }
+
+    protected void swtTraverse(final int direction, final int count) {
         assert EventQueue.isDispatchThread();
 
         ThreadingHandler.getInstance().asyncExec(display, new Runnable() {
             public void run() {
-                if (verboseTraverseOut) {
-                    System.err.println("@" + System.currentTimeMillis() + " " + System.identityHashCode(this) +  
-                                       " SWT: traversing out, control=" + swingControl + " direction= " + direction);
+                for(int i = 0; i < count; i++) {
+                    doTraverse(direction);
                 }
-                doTraverse(direction);
-                
-                int increment = (direction == SWT.TRAVERSE_TAB_NEXT) ? -1 : 1;
-                synchronized (FocusHandler.this) {
-                    while (extraTabCount != 0) {
-                        doTraverse(direction);
-                        extraTabCount += increment;
-                    }
-                }
-                traverseKey = null;
             }
 
         });
@@ -161,16 +174,38 @@
 
         Control focusControl = display.getFocusControl();
         if (verboseTraverseOut) {
-            System.err.println("@" + System.currentTimeMillis() + " " + System.identityHashCode(this) +  
-                               " handling typeahead, control=" + focusControl);
+            trace("SWT: traversing, control=" + focusControl);
+        }
+        if ((focusControl == null) && (activeSwingControl != null)) {
+            focusControl = activeSwingControl;
+            if (verboseTraverseOut) {
+                trace("SWT: current focus control is null; using=" + focusControl);
+            }
         }
         if (focusControl != null) {
             boolean traverse = focusControl.traverse(direction);
             if (verboseTraverseOut && !traverse) {
-                System.err.println("@" + System.currentTimeMillis() + " " + System.identityHashCode(this) +  
-                                   " traverse failed, frome" + focusControl);
+                trace("traverse failed, from=" + focusControl);
+            }
+        }
+    }
+
+    public boolean handleForceFocus(SwingControl swingControl, boolean result) {
+        if (Platform.isWin32()) {
+            // On Windows, focus queries are unreliable while traversing SwingControls
+            // In some cases this causes forceFocus() to incorrectly return false, causing
+            // the SwingControl to be skipped while tabbing. Try to fix it here by resetting
+            // the forceFocus result back to true when SWT events indicate we really do have 
+            // focus. 
+            if (!result && isActiveSwt && isFocusedSwt) {
+                // Force focus should have returned true
+                result = true;
+                if (verboseTraverseOut) {
+                    trace(" resetting forceFocus return code to true");
+                }
             }
         }
+        return result;
     }
 
     // ==============
@@ -188,9 +223,8 @@
             ThreadingHandler.getInstance().asyncExec(display, new Runnable() {
                 public void run() {
                     if (!display.isDisposed() && (swingControl == display.getFocusControl())) {
-                        if (FocusHandler.verboseFocusEvents) {
-                            System.err.println("@" + System.currentTimeMillis() + 
-                                    " Manually reactivating " + swingControl);
+                        if (verboseFocusEvents) {
+                            trace("Manually reactivating " + swingControl);
                         }
                         synthesizeWindowActivation(true);
                     }
@@ -225,9 +259,8 @@
                 public void run() {
                     try {
                         if (synthesizeMethod != null) {
-                            if (FocusHandler.verboseFocusEvents) {
-                                System.err.println("@" + System.currentTimeMillis() + 
-                                        " Calling synthesizeWindowActivation(" + activate + ")");
+                            if (verboseFocusEvents) {
+                                trace("Calling synthesizeWindowActivation(" + activate + ")");
                             }
                             synthesizeMethod.invoke(frame,
                                     new Object[] { new Boolean(activate) });
@@ -262,52 +295,96 @@
     }
 
     private void handleSynthesizeException(Exception e) {
-        if (FocusHandler.verboseFocusEvents) {
+        if (verboseFocusEvents) {
             e.printStackTrace();
         }
     }
 
-    // ======== Implementation of org.eclipse.swt.widgets.Listener
-    public void handleEvent(Event event) {
-    	if (event.widget != swingControl) {
-    		return;
-    	}
-        switch (event.type) {
-        case SWT.Activate:
-            if (Platform.isWin32() && (Platform.SWT_VERSION < Platform.SWT_34) && (synthesizeMethod != null)) {
-                synthesizeWindowActivation(true);
-                if (FocusHandler.verboseFocusEvents) {
-                    System.err.println("@" + System.currentTimeMillis() + 
-                            " Consuming SWT.Activate event: " + event);
-                }
-            	event.type = SWT.None;
+
+    // ========== Listener implementations
+    
+    protected class SwtEventFilter implements Listener {
+
+        public void handleEvent(Event event) {
+            if (event.widget != swingControl) {
+                return;
             }
-            break;
+            
+            switch (event.type) {
+            case SWT.Activate:
+                isActiveSwt = true;
+                activeSwingControl = swingControl;
+                if (Platform.isWin32() && (Platform.SWT_VERSION < Platform.SWT_34) && (synthesizeMethod != null)) {
+                    synthesizeWindowActivation(true);
+                    if (verboseFocusEvents) {
+                        trace("Consuming SWT.Activate event: " + event);
+                    }
+                    event.type = SWT.None;
+                }
+                
+                break;
 
-        case SWT.Deactivate:
-            if (Platform.isWin32() && (Platform.SWT_VERSION < Platform.SWT_34) && (synthesizeMethod != null)) {
-                synthesizeWindowActivation(false);
-                if (FocusHandler.verboseFocusEvents) {
-                    System.err.println("@" + System.currentTimeMillis() + 
-                            " Consuming SWT.Deactivate event: " + event);
+            case SWT.Deactivate:
+                isActiveSwt = false;
+                activeSwingControl = null;
+                if (Platform.isWin32() && (Platform.SWT_VERSION < Platform.SWT_34) && (synthesizeMethod != null)) {
+                    synthesizeWindowActivation(false);
+                    if (verboseFocusEvents) {
+                        trace("Consuming SWT.Deactivate event: " + event);
+                    }
+                    event.type = SWT.None;
                 }
-            	event.type = SWT.None;
+                if (Platform.isWin32()) {
+                    postMissingActivation();
+                }
+                break;
             }
+        }
+
+    }
+
+    protected class SwtFocusListener implements FocusListener {
+        public void focusGained(FocusEvent e) {
+            isFocusedSwt = true;
+        }
+        public void focusLost(FocusEvent e) {
+            isFocusedSwt = false;
+        }
+    }
+
+    protected class AwtWindowFocusListener implements WindowFocusListener {
+        public void windowGainedFocus(WindowEvent e) {
+            assert !pendingTraverse;
+            assert extraTabCount == 0;
+        }
+
+        public void windowLostFocus(WindowEvent e) {
             if (Platform.isWin32()) {
-                postMissingActivation();
+                processTypeAheadKeys();
             }
-            break;
         }
-    }
-    
-    // ======== Implementation of KeyEventDispatcher
-    public boolean dispatchKeyEvent(KeyEvent e) {
-        boolean result = false;
         
-        if (Platform.isWin32()) {
-            result = checkForTraverseOut(e);
+    }
+
+    protected class AwtKeyDispatcher implements KeyEventDispatcher {
+
+        public boolean dispatchKeyEvent(KeyEvent e) {
+            boolean result = false;
+            
+            if (Platform.isWin32()) {
+                result = checkForTraverseOut(e);
+            }
+            return result;
         }
-        return result;
+
     }
 
+    private void trace(String msg) {
+        System.err.println(header() + ' ' + msg);
+    }
+    private String header() {
+        return "@" + System.currentTimeMillis() + " " + System.identityHashCode(this);
+    }
+
+
 }

Back to the top