[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[albireo-dev] Focus Management - initial commit

Bruno,

I've committed some very early focus management code. I've documented the changes and (more importantly) their corresponding test on a new eclipsepedia page:

http://wiki.eclipse.org/Albireo_Focus_Management_Test_Cases

I hope we can maintain this page as we find and fix other problems. As I think we agreed earlier, this information will be crucial in preventing regressions.

I've created a separate FocusHandler class and moved the verbose focus event flag there. I encountered win32 problems first, so that's what I've fixed so far, but I have been running on Windows/Linux, SWT 3.3/3.4, and Java 1.5/1.6.

The AWT KeyboardFocusHandler has some very useful logging code, so I have added that to our verbose output (and removed the original property change listener). I also added tracing of SWT Activate and Deactivate events on the SwingControl itself.

Your example views were very helpful, and I have added another example called FocusTraversalView. I've had no time to debug the obvious problems with that one.
### Eclipse Workspace Patch 1.0
#P org.eclipse.albireo.core
Index: src/org/eclipse/albireo/core/AwtEnvironment.java
===================================================================
RCS file: /cvsroot/technology/org.eclipse.albireo/org.eclipse.albireo.core/src/org/eclipse/albireo/core/AwtEnvironment.java,v
retrieving revision 1.9
diff -u -r1.9 AwtEnvironment.java
--- src/org/eclipse/albireo/core/AwtEnvironment.java	13 Feb 2008 19:19:04 -0000	1.9
+++ src/org/eclipse/albireo/core/AwtEnvironment.java	18 Feb 2008 00:58:02 -0000
@@ -23,6 +23,7 @@
 import javax.swing.UnsupportedLookAndFeelException;
 
 import org.eclipse.albireo.internal.FocusDebugging;
+import org.eclipse.albireo.internal.FocusHandler;
 import org.eclipse.albireo.internal.SwtInputBlocker;
 import org.eclipse.albireo.internal.AwtDialogListener;
 import org.eclipse.swt.SWT;
@@ -137,8 +138,8 @@
             EventQueue.invokeAndWait(new Runnable() {
                 public void run() {
                     setLookAndFeel();
-                    if (SwingControl.verboseFocusEvents)
-                        FocusDebugging.addFocusDebugListenerOnKeyboardFocusManager();
+                    if (FocusHandler.verboseFocusEvents)
+                        FocusDebugging.enableKeyboardFocusManagerLogging();
                 }
             });
         } catch (InterruptedException e) {
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.35
diff -u -r1.35 SwingControl.java
--- src/org/eclipse/albireo/core/SwingControl.java	16 Feb 2008 23:27:20 -0000	1.35
+++ src/org/eclipse/albireo/core/SwingControl.java	18 Feb 2008 00:58:03 -0000
@@ -30,7 +30,7 @@
 
 import org.eclipse.albireo.internal.CleanResizeListener;
 import org.eclipse.albireo.internal.ComponentDebugging;
-import org.eclipse.albireo.internal.FocusDebugging;
+import org.eclipse.albireo.internal.FocusHandler;
 import org.eclipse.albireo.internal.Platform;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.SWTException;
@@ -53,9 +53,6 @@
     // and layout.
     static final boolean verboseSizeLayout = false;
 
-    // Whether to print debugging information regarding focus events.
-    static final boolean verboseFocusEvents = true;
-
     private Listener settingsListener = new Listener() {
         public void handleEvent(Event event) {
             handleSettingsChange();
@@ -183,9 +180,10 @@
 
         if (verboseSizeLayout)
             ComponentDebugging.addComponentSizeDebugListeners(frame);
-        if (verboseFocusEvents)
-            FocusDebugging.addFocusDebugListeners(this, frame);
+
+        initializeFocusManagement();
     }
+    
 
     protected void scheduleComponentCreation() {
         assert frame != null;
@@ -625,7 +623,7 @@
      */
     public abstract Composite getLayoutableAncestor();
 
-	// TODO: remove this method and just leave the listener for advanced users?
+    // TODO: remove this method and just leave the listener for advanced users?
     /**
      * Called when the preferred sizes of this control, as computed by
      * AWT, have changed. This method 
@@ -854,6 +852,18 @@
             }
         }
     }
+    
+    // ============================= Focus Management =============================
+    private FocusHandler focusHandler;
+    
+    protected void initializeFocusManagement() {
+        assert frame != null;
+        assert Display.getCurrent() != null;     // On SWT event thread
+        
+        focusHandler = new FocusHandler(this, frame);   
+    }
+    
+    
     // ============================= Events and Listeners =============================
 
     private List sizeListeners = new ArrayList();
Index: src/org/eclipse/albireo/internal/FocusDebugging.java
===================================================================
RCS file: /cvsroot/technology/org.eclipse.albireo/org.eclipse.albireo.core/src/org/eclipse/albireo/internal/FocusDebugging.java,v
retrieving revision 1.3
diff -u -r1.3 FocusDebugging.java
--- src/org/eclipse/albireo/internal/FocusDebugging.java	13 Feb 2008 19:28:23 -0000	1.3
+++ src/org/eclipse/albireo/internal/FocusDebugging.java	18 Feb 2008 00:58:03 -0000
@@ -21,8 +21,13 @@
 import java.awt.event.FocusListener;
 import java.awt.event.WindowEvent;
 import java.awt.event.WindowFocusListener;
-import java.beans.PropertyChangeEvent;
-import java.beans.PropertyChangeListener;
+import java.util.logging.ConsoleHandler;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Event;
 
 /**
  * This class contains utility functions for debugging focus issues relating
@@ -45,12 +50,14 @@
  */
 public class FocusDebugging {
 
-    /**
+   /**
      * Adds listeners for debugging the three first kinds of focus events.
      */
     public static void addFocusDebugListeners(org.eclipse.swt.widgets.Composite control,
                                               Container topLevelComponent) {
         control.addFocusListener(_SWTFocusListener);
+        control.addListener(SWT.Activate, _SWTActivationListener);
+        control.addListener(SWT.Deactivate, _SWTActivationListener);
         if (topLevelComponent instanceof Window)
             ((Window)topLevelComponent).addWindowFocusListener(_AWTWindowFocusListener);
         addFocusListenerToTree(topLevelComponent);
@@ -72,6 +79,28 @@
     private static SWTFocusListener _SWTFocusListener = new SWTFocusListener();
 
     /**
+     * Shows activation events on the SWT side. Note: events that are eaten by the filter
+     * in FocusHander will not be displayed here.
+     */
+    private static class SWTActivationListener implements org.eclipse.swt.widgets.Listener {
+		public void handleEvent(Event event) {
+			String name = null;
+			switch (event.type) {
+			case SWT.Deactivate:
+				name = "Deactivate";
+				break;
+				
+			case SWT.Activate:
+				name = "Activate";
+				break;
+			}
+            System.err.println("@"+System.currentTimeMillis() + 
+                    " SWT Event: " + name + " " + System.identityHashCode(event.widget));
+		}
+    }
+    private static SWTActivationListener _SWTActivationListener = new SWTActivationListener();
+    
+    /**
      * Shows focus events on the top-level window on the AWT side.
      */
     private static class AWTWindowFocusListener implements WindowFocusListener {
@@ -149,27 +178,15 @@
 
     // ------------------------------------------------------------------------
 
-    private static PropertyChangeListener focusDebugListener;
-
     /**
-     * Adds listeners for debugging the fourth kind of focus events,
-     * on the AWT <code>KeyboardFocusManager</code> singleton.
+     * Enables logging of events,
+     * from the AWT <code>KeyboardFocusManager</code> singleton.
      */
-    public static void addFocusDebugListenerOnKeyboardFocusManager() {
-        if (focusDebugListener == null) {
-            final KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
-            focusDebugListener = new PropertyChangeListener() {
-                public void propertyChange(PropertyChangeEvent event) {
-                    System.err.println("@"+System.currentTimeMillis()
-                                       + " AWT KFMPC " + event.getPropertyName()
-                                       + " new=" + event.getNewValue()
-                                       + " old=" + event.getOldValue());
-                    if (event.getPropertyName().equals("focusedWindow")) {
-                        System.err.println("               permanent="+kfm.getPermanentFocusOwner());
-                    }
-                }
-            };
-            kfm.addPropertyChangeListener(focusDebugListener);
-        }
+    public static void enableKeyboardFocusManagerLogging() {
+        Logger logger = LogManager.getLogManager().getLogger("java.awt.focus.KeyboardFocusManager");
+        logger.setLevel(Level.FINEST);
+        ConsoleHandler handler = new ConsoleHandler();
+        handler.setLevel(Level.FINEST);
+        logger.addHandler(handler);
     }
 }
Index: src/org/eclipse/albireo/internal/FocusHandler.java
===================================================================
RCS file: src/org/eclipse/albireo/internal/FocusHandler.java
diff -N src/org/eclipse/albireo/internal/FocusHandler.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/albireo/internal/FocusHandler.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,170 @@
+package org.eclipse.albireo.internal;
+
+import java.awt.EventQueue;
+import java.awt.Frame;
+import java.awt.event.WindowEvent;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.eclipse.albireo.core.SwingControl;
+import org.eclipse.albireo.core.ThreadingHandler;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
+
+public class FocusHandler implements Listener {
+
+    private final Frame frame;
+    private final SwingControl swingControl;
+    private final Display display;
+    
+    // Whether to print debugging information regarding focus events.
+    public static final boolean verboseFocusEvents = true;
+    
+    static private boolean synthesizeMethodInitialized = false;
+    static private Method synthesizeMethod = null;
+
+    public FocusHandler(SwingControl swingControl, Frame frame) {
+        assert Display.getCurrent() != null;     // On SWT event thread
+
+        if (verboseFocusEvents)
+            FocusDebugging.addFocusDebugListeners(swingControl, frame);
+        
+        this.swingControl = swingControl;
+        this.frame = frame;
+        display = swingControl.getDisplay();
+        
+        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);
+    }
+    
+    // On Windows, when the embedded frame loses focus to an ancestor which then assigns focus to 
+    // the embedded composite, the composite receives only a Deactivate event and not an Activate
+    // event. This method schedules an async exec, meant to run after the SWT focus change is complete. 
+    // It sends the missing activation when necessary, so that the frame can get back focus.
+    //
+    // This solves the problem where clicking on an RCP view tab causes an embedded Swing control
+    // to lose focus. 
+    protected void postMissingActivation() {
+        assert Platform.isWin32();               // currently only implemented/needed for win32
+        
+        if (!display.isDisposed()) {
+            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);
+                        }
+                        synthesizeWindowActivation(true);
+                    }
+                }
+            });
+        }
+    }
+
+    // ==== Fix Eclipse bug 216431 on pre-3.4
+    // XEmbeddedFrame does not implement the synthesize method, and the Eclipse bug was fixed only for
+    // win32, so we do the same in the methods below
+    
+    /**
+     * This method duplicates the behavior of recent versions of Windows SWT_AWT 
+     * when the embedded Composite is activated or deactivated. It is used here to
+     * workaround bugs in earlier (pre-3.4) versions of SWT, and to handle cases
+     * where the Composite is not properly activated/deactivated, even today. See
+     * the callers of this method for more information. 
+     * 
+     * @param activate <code>true</code> if the embedded frame whould be activated; 
+     * <code>false</code> otherwise
+     * @return
+     */
+    protected void synthesizeWindowActivation(final boolean activate) {
+        assert Display.getCurrent() != null;     // On SWT event thread
+        assert Platform.isWin32();               // Only done on Windows
+
+        if (synthesizeMethod != null) {
+            // synthesizeWindowActivation() is available. Use it. Normally, this is on
+            // Java 1.5 and higher. 
+            EventQueue.invokeLater(new Runnable() {
+                public void run() {
+                    try {
+                        if (synthesizeMethod != null) {
+                            if (FocusHandler.verboseFocusEvents) {
+                                System.err.println("@" + System.currentTimeMillis() + 
+                                        " Calling synthesizeWindowActivation(" + activate + ")");
+                            }
+                            synthesizeMethod.invoke(frame,
+                                    new Object[] { new Boolean(activate) });
+                        }
+                    } catch (IllegalAccessException e) {
+                        handleSynthesizeException(e);
+                    } catch (InvocationTargetException e) {
+                        handleSynthesizeException(e);
+                    }
+                }
+            });
+        } else {
+            if (activate) {
+                frame.dispatchEvent (new WindowEvent (frame, WindowEvent.WINDOW_ACTIVATED));
+                frame.dispatchEvent (new WindowEvent (frame, WindowEvent.WINDOW_GAINED_FOCUS));
+            } else {
+                frame.dispatchEvent (new WindowEvent (frame, WindowEvent.WINDOW_LOST_FOCUS));
+                frame.dispatchEvent (new WindowEvent (frame, WindowEvent.WINDOW_DEACTIVATED));
+            }
+        }
+    }
+
+    private void getSynthesizeMethod(Class clazz) {
+        if (Platform.isWin32() && !synthesizeMethodInitialized) {
+            synthesizeMethodInitialized = true;
+            try {
+                synthesizeMethod = clazz.getMethod("synthesizeWindowActivation", new Class[]{boolean.class});
+			} catch (NoSuchMethodException e) {
+                handleSynthesizeException(e);
+			}
+        }
+    }
+
+    private void handleSynthesizeException(Exception e) {
+        if (FocusHandler.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;
+            }
+            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);
+                }
+            	event.type = SWT.None;
+            }
+            if (Platform.isWin32()) {
+                postMissingActivation();
+            }
+            break;
+        }
+    }
+}