Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[albireo-dev] AWT Modal Dialog handling changes

I've committed a fix to AwtEnvironment.createDialogParentFrame() to prevent child dialogs from remaining on top of the z-order when windows from other applications. The parent frame was mistakenly embedded inside a SWT.ON_TOP shell.

While testing, I noticed that AWT modal dialogs are getting hidden under the SWT shell quite frequently under Gtk. The only solution I've found so far is to selectively use java.awt.Window.setAlwaysOnTop(true) to force the dialog above the SWT shell. This property is set back to false when focus is lost so that the dialog does not appear above other application windows. I've committed this change as well.

setAlwaysOnTop() is new to JDK 1.5, so if/when we test on 1.4, another solution (if any) will be needed.
### Eclipse Workspace Patch 1.0
#P org.eclipse.albireo.core
Index: src/org/eclipse/albireo/internal/SwtInputBlocker.java
===================================================================
RCS file: /cvsroot/technology/org.eclipse.albireo/org.eclipse.albireo.core/src/org/eclipse/albireo/internal/SwtInputBlocker.java,v
retrieving revision 1.5
diff -u -r1.5 SwtInputBlocker.java
--- src/org/eclipse/albireo/internal/SwtInputBlocker.java	28 Apr 2008 19:40:55 -0000	1.5
+++ src/org/eclipse/albireo/internal/SwtInputBlocker.java	2 May 2008 18:36:55 -0000
@@ -46,6 +46,7 @@
                     // On some platforms (e.g. Linux/GTK), the 0x0 shell still appears as a dot 
                     // on the screen, so make it invisible by moving it below other windows. This
                     // is unnecessary under Windows and causes a flash, so only make the call when necessary.
+                    // note: would like to do this too: parentShell.moveBelow(null);, but see bug 170774
                     shell.moveBelow(null);
                     dialogListener.requestFocus();
                 }
@@ -69,6 +70,7 @@
         assert Display.getCurrent() != null;     // On SWT event thread
         
         // TODO: Will SWT.NO_FOCUS help in any way here?
+        // TODO: Another shell is not necessary here if AwtEnvironment.createDialogParentFrame is used. 
         // Construct with the current display, rather than parent. This reduces problems where
         // the AWT dialog gets covered or does not have focus when opened. 
         // Use ON_TOP to prevent a Windows task bar button
Index: src/org/eclipse/albireo/internal/AwtDialogListener.java
===================================================================
RCS file: /cvsroot/technology/org.eclipse.albireo/org.eclipse.albireo.core/src/org/eclipse/albireo/internal/AwtDialogListener.java,v
retrieving revision 1.4
diff -u -r1.4 AwtDialogListener.java
--- src/org/eclipse/albireo/internal/AwtDialogListener.java	28 Apr 2008 19:40:55 -0000	1.4
+++ src/org/eclipse/albireo/internal/AwtDialogListener.java	2 May 2008 18:36:55 -0000
@@ -21,9 +21,13 @@
 import java.awt.event.ComponentEvent;
 import java.awt.event.ComponentListener;
 import java.awt.event.WindowEvent;
+import java.awt.event.WindowFocusListener;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
+import org.eclipse.albireo.core.SwingControl;
 import org.eclipse.albireo.core.ThreadingHandler;
 import org.eclipse.swt.widgets.Display;
 
@@ -34,7 +38,15 @@
  *
  * @see SwtInputBlocker
  */
-public class AwtDialogListener implements AWTEventListener, ComponentListener {
+public class AwtDialogListener implements AWTEventListener, ComponentListener, WindowFocusListener {
+    
+    private static boolean verboseModalityHandling = false;
+
+    protected static boolean USING_ALWAYS_ON_TOP = Platform.isGtk() && 
+                        (Platform.JAVA_VERSION >= Platform.javaVersion(1, 5, 0));
+    private static boolean alwaysOnTopMethodsInitialized = false;
+    private static Method setAlwaysOnTopMethod = null;
+    private static Method isAlwaysOnTopMethod = null;
     
     // modalDialogs should be accessed only from the AWT thread, so no
     // synchronization is needed. 
@@ -51,19 +63,80 @@
     public AwtDialogListener(Display display) {
         assert display != null;
         
+        // In some cases, we use Window.setAlwaysOnTop to keep modal AWT dialogs visible
+        if (USING_ALWAYS_ON_TOP) {
+            getAlwaysOnTopMethods();
+        }
+        
         this.display = display;
         Toolkit.getDefaultToolkit().addAWTEventListener(this, AWTEvent.WINDOW_EVENT_MASK);
     }
     
+    protected void getAlwaysOnTopMethods() {
+        // These methods is only needed for Gtk, Reflection is used to allow compilation
+        // against JDK 1.4
+        if (!alwaysOnTopMethodsInitialized) {
+            alwaysOnTopMethodsInitialized = true;
+            try {
+                setAlwaysOnTopMethod = Window.class.getMethod("setAlwaysOnTop", new Class[]{boolean.class});
+                isAlwaysOnTopMethod = Window.class.getMethod("isAlwaysOnTop", new Class[]{});
+            } catch (NoSuchMethodException e) {
+                handleAlwaysOnTopException(e);
+            }
+        }
+    }
+
+    protected void setAlwaysOnTop(Window window, boolean onTop) {
+        assert setAlwaysOnTopMethod != null;
+        try {
+            if (verboseModalityHandling) {
+                System.err.println("Calling setAlwaysOnTop(" + onTop + ") for " + window);
+            }
+            setAlwaysOnTopMethod.invoke(window, new Object[] { new Boolean(onTop) });
+        } catch (IllegalAccessException e) {
+            handleAlwaysOnTopException(e);
+        } catch (InvocationTargetException e) {
+            handleAlwaysOnTopException(e);
+        }
+    }
+
+    protected boolean isAlwaysOnTop(Window window) {
+        assert isAlwaysOnTopMethod != null;
+        try {
+            if (verboseModalityHandling) {
+                System.err.println("Calling isAlwaysOnTop() for " + window);
+            }
+            Object result = isAlwaysOnTopMethod.invoke(window, new Object[] { });
+            return ((Boolean)result).booleanValue();
+        } catch (IllegalAccessException e) {
+            handleAlwaysOnTopException(e);
+            return false;
+        } catch (InvocationTargetException e) {
+            handleAlwaysOnTopException(e);
+            return false;
+        }
+    }
+
+    protected void handleAlwaysOnTopException(Exception e) {
+        if (verboseModalityHandling) {
+            e.printStackTrace();
+        }
+    }
+    
     private void handleRemovedDialog(Dialog awtDialog, boolean removeListener) {
         assert awtDialog != null;
         assert modalDialogs != null;
         assert display != null;
         assert EventQueue.isDispatchThread();    // On AWT event thread
         
-        // System.out.println("Remove dialog: " + awtDialog);
+        if (verboseModalityHandling) {
+            System.err.println("Remove dialog: " + awtDialog);
+        }
         if (removeListener) {
             awtDialog.removeComponentListener(this);
+            if (USING_ALWAYS_ON_TOP) {
+                awtDialog.removeWindowFocusListener(this);
+            }
         }
         // Note: there is no isModal() check here because the dialog might 
         // have been changed from modal to non-modal after it was opened. In this case
@@ -83,7 +156,9 @@
         assert modalDialogs != null;
         assert EventQueue.isDispatchThread();    // On AWT event thread
         
-        // System.out.println("Add dialog: " + awtDialog);
+        if (verboseModalityHandling) {
+            System.err.println("Add dialog: " + awtDialog);
+        }
         
         // Don't block if 
         // 1) the the dialog has already triggered a block
@@ -96,6 +171,21 @@
         }
         modalDialogs.add(awtDialog);
         awtDialog.addComponentListener(this);
+        
+        // In some cases (e.g. GTK), we need to use the Window.setAlwaysOnTop
+        // method to force modal AWT dialogs in front of any SWT shells. Otherwise, they
+        // are easily hidden when clicking on the parent shell. It might be possible to
+        // remove this code if we could successfully move the SWT shell back in the z-order, 
+        // but there is an open bug (on GTK) on Shell.moveBelow. 
+        // See note in SwtInputBlocker.activateListener
+        // We use a listener to keep the always-on-top behavior enabled only while the 
+        // dialog has focus. If the dialog is already always-on-top, we don't add a listener. 
+        // TODO: we don't handle the case where always-on-top status is changed while the dialog
+        // is visible. 
+        if (USING_ALWAYS_ON_TOP && !isAlwaysOnTop(awtDialog)) {
+            awtDialog.addWindowFocusListener(this);
+        }
+        
         ThreadingHandler.getInstance().asyncExec(display, new Runnable() {
             public void run() {
                 SwtInputBlocker.block(AwtDialogListener.this);
@@ -114,20 +204,20 @@
                 int size = modalDialogs.size();
                 if (size > 0) {
                     final Dialog awtDialog = (Dialog)modalDialogs.get(size - 1);
-
-                    // In one case, a call to requestFocus() alone does not 
-                    // bring the AWT dialog to the top. This happens if the 
-                    // dialog is given a null parent frame. When opened, the dialog
-                    // can be hidden by the SWT window even when it obtains focus.
-                    // Calling toFront() solves the problem.
-                    
-                    // System.out.println("Bringing to front");
-
                     Component focusOwner = awtDialog.getMostRecentFocusOwner();
+                    if (verboseModalityHandling) {
+                        System.err.println("Bringing to front, focusOwner=" + focusOwner);
+                    }
+
                     if (focusOwner == null) {
                         focusOwner = awtDialog; // try the dialog itself in this case
                     }
                     try {
+                        // In one case, a call to requestFocus() alone does not 
+                        // bring the AWT dialog to the top. This happens if the 
+                        // dialog is given a null parent frame. When opened, the dialog
+                        // can be hidden by the SWT window even when it obtains focus.
+                        // Calling toFront() solves the problem.
                         focusOwner.requestFocus();
                         awtDialog.toFront();
                     } catch (NullPointerException e) {
@@ -212,7 +302,9 @@
         assert e != null;
         assert EventQueue.isDispatchThread();    // On AWT event thread
         
-        // System.out.println("Component hidden");
+        if (verboseModalityHandling) {
+            System.err.println("Component hidden");
+        }
         Object obj = e.getSource();
         if (obj instanceof Dialog) {
             // Remove dialog but keep listener in place so that we know if/when it is set visible
@@ -224,7 +316,9 @@
         assert e != null;
         assert EventQueue.isDispatchThread();    // On AWT event thread
         
-        // System.out.println("Component shown");
+        if (verboseModalityHandling) {
+            System.err.println("Component shown");
+        }
         Object obj = e.getSource();
         if (obj instanceof Dialog) {
             handleAddedDialog((Dialog)obj);
@@ -238,5 +332,15 @@
     public void componentMoved(ComponentEvent e) {
         // Ignore event
     }
+    
+    public void windowGainedFocus(WindowEvent e) {
+        assert USING_ALWAYS_ON_TOP;
+        setAlwaysOnTop(e.getWindow(), true);
+    }
+
+    public void windowLostFocus(WindowEvent e) {
+        assert USING_ALWAYS_ON_TOP;
+        setAlwaysOnTop(e.getWindow(), false);
+    }
         
 }
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.23
diff -u -r1.23 AwtEnvironment.java
--- src/org/eclipse/albireo/core/AwtEnvironment.java	30 Apr 2008 17:45:31 -0000	1.23
+++ src/org/eclipse/albireo/core/AwtEnvironment.java	2 May 2008 18:36:55 -0000
@@ -460,8 +460,10 @@
             SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
         }
         
-        
-        final Shell shell = new Shell(parent, SWT.NO_TRIM | SWT.APPLICATION_MODAL | SWT.ON_TOP);
+
+        // SWT.ON_TOP worked great for AWT print/page setup dialogs, but not for 
+        // other dialogs, so it has been removed. 
+        final Shell shell = new Shell(parent, SWT.NO_TRIM | SWT.APPLICATION_MODAL);
         
         Composite composite = new Composite(shell, SWT.EMBEDDED);
         Frame frame = SWT_AWT.new_Frame(composite);
@@ -490,11 +492,13 @@
         // Clean up the shell that was created above on dispose of the frame 
         frame.addWindowListener(new WindowAdapter() {
             public void windowClosed(WindowEvent e) {
-                ThreadingHandler.getInstance().asyncExec(display, new Runnable() {
-                    public void run() {
-                        shell.dispose();
-                    }
-                });
+                if (!display.isDisposed()) {
+                    ThreadingHandler.getInstance().asyncExec(display, new Runnable() {
+                        public void run() {
+                            shell.dispose();
+                        }
+                    });
+                }
             }
         });
         

Back to the top