[
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;
+ }
+ }
+}