Skip to main content

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] [List Home]
[albireo-dev] GTK AWT modal dialog fixes

The code to handle AWT modal dialogs has been updated to work better in GTK. The events we can use to redirect focus from SWT to the modal dialog are different in GTK than in Windows, and I have made the necessary changes. Modal dialogs generally behave better now, but there is probably more work to be done. I believe there are still problems under KDE that I'll be debugging soon, and there may be other problems certain window managers.

I've also added more example AWT dialogs. In particular AWT Print and Page Setup dialogs are now available in the Dialogs menu of the example app. These dialogs are not well-behaved, at least under Windows, so its important to test them.
### 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.1
diff -u -r1.1 SwtInputBlocker.java
--- src/org/eclipse/albireo/internal/SwtInputBlocker.java	27 Jan 2008 17:42:58 -0000	1.1
+++ src/org/eclipse/albireo/internal/SwtInputBlocker.java	6 Mar 2008 22:39:17 -0000
@@ -14,19 +14,46 @@
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.FocusAdapter;
 import org.eclipse.swt.events.FocusEvent;
-import org.eclipse.swt.widgets.Dialog;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.widgets.Composite;
 import org.eclipse.swt.widgets.Display;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Listener;
 import org.eclipse.swt.widgets.Shell;
 
 
-public class SwtInputBlocker extends Dialog {
+public class SwtInputBlocker {
     static private SwtInputBlocker instance = null;
     static private int blockCount = 0;
     private Shell shell;
     private final AwtDialogListener dialogListener;
+    private Shell parentShell;
+    
+    private Listener activateListener = new Listener() {
+        public void handleEvent(Event event) {
+            // Schedule the AWT focus request so that activation is completed first. Otherwise the focus
+            // request can happen before the AWT window is deactivated.
+            shell.getDisplay().asyncExec(new Runnable() {
+                public void run() {
+                    // 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.
+                    shell.moveBelow(null);
+                    dialogListener.requestFocus();
+                }
+            });
+        }
+    };
+    
+    private FocusListener focusListener = new FocusAdapter() {
+        public void focusGained(FocusEvent e) {
+            dialogListener.requestFocus();
+        }
+    };
+    
 
     private SwtInputBlocker(Shell parent, AwtDialogListener dialogListener) {
-        super(parent, SWT.NONE);
+        this.parentShell = parent;
         this.dialogListener = dialogListener; 
     }
     
@@ -38,23 +65,27 @@
         // the AWT dialog gets covered or does not have focus when opened. 
         // Use ON_TOP to prevent a Windows task bar button
         shell = new Shell(Display.getCurrent(), SWT.APPLICATION_MODAL | SWT.NO_TRIM | SWT.ON_TOP);
-        // shouldn't be necesssary: shell.setMinimumSize(0, 0);
         shell.setSize(0, 0);
-        shell.addFocusListener(new FocusAdapter() {
-            public void focusGained(FocusEvent e) {
-                // 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. 
-                if (Platform.isGtk()) {
-                    shell.moveBelow(null);
-                }
-                dialogListener.requestFocus();
+        
+        // Add listener(s) to force focus back to the AWT dialog if SWT gets control
+        if (Platform.isGtk()) {
+            // Under GTK, focus events are not available to detect this condition, 
+            // so use the activate event. 
+            // TODO: is it necessary to do this for all parents?
+            Shell shell = parentShell;
+            while (shell != null) {
+                shell.addListener(SWT.Activate, activateListener);
+                Composite composite = shell.getParent();
+                shell = (composite != null) ? composite.getShell() : null;
             }
-        });
+        } else {
+            // Otherwise, restore focus to awt if the shell gets focus  
+            // TODO: test on MacOS and Motif
+            shell.addFocusListener(focusListener);
+        }
         shell.open();
         
-        Shell parent = getParent();
-        Display display = parent.getDisplay();
+        Display display = shell.getDisplay();
         while (!shell.isDisposed()) {
             if (!display.readAndDispatch()) {
                 display.sleep();
@@ -64,7 +95,7 @@
         // If windows from other applications have been opened while SWT was being blocked, 
         // the original parent shell can get lost under those windows after the blocking
         // is stopped. Force the parent shell back to the front here. 
-        parent.forceActive();
+        parentShell.forceActive();
         
         return null;
     }
@@ -72,6 +103,14 @@
     private void close() {
         assert shell != null;
         
+        if (Platform.isGtk()) {
+            Shell shell = parentShell;
+            while (shell != null) {
+                shell.removeListener(SWT.Activate, activateListener);
+                Composite composite = shell.getParent();
+                shell = (composite != null) ? composite.getShell() : null;
+            }
+        }
         shell.dispose();
     }
 
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.13
diff -u -r1.13 AwtEnvironment.java
--- src/org/eclipse/albireo/core/AwtEnvironment.java	28 Feb 2008 05:04:57 -0000	1.13
+++ src/org/eclipse/albireo/core/AwtEnvironment.java	6 Mar 2008 22:39:17 -0000
@@ -79,7 +79,6 @@
     private final AwtDialogListener dialogListener;
     private Shell popupParent;
 
-
     /**
      * Returns the single instance of AwtEnvironment for the given display. On
      * the first call to this method, the necessary initialization to allow
@@ -305,19 +304,34 @@
         if (!display.equals(Display.getCurrent())) {
             SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
         }
-        final Shell shell = new Shell(parent);
+        
+        
+        final Shell shell = new Shell(parent, SWT.NO_TRIM | SWT.APPLICATION_MODAL | SWT.ON_TOP);
+        
+        Composite composite = new Composite(shell, SWT.EMBEDDED);
+        Frame frame = SWT_AWT.new_Frame(composite);
         
         // Position and size the shell and embedded composite. This ensures that 
         // any child dialogs will be shown in the proper position, relative to the 
         // parent shell. 
-        shell.setBounds(parent.getBounds());
-        Composite composite = new Composite(shell, SWT.EMBEDDED);
+        shell.setLocation(parent.getLocation());
+        
+        // On Gtk, if the embedded frame is never made visible, its child dialog
+        // will not be positioned correctly on the screen. (The frame's 
+        // getLocationOnScreen() method will always return 0,0). To work around
+        // this problem, temporarily make the shell (and frame) visible. To 
+        // avoid flicker, temporarily set the size to 0.
+        // (Note: the shell location must be correctly set before this will work)
+        if (Platform.isGtk()) {
+            shell.setSize(0, 0);
+            shell.setVisible(true);
+            shell.setVisible(false);
+        }
+
+        shell.setSize(parent.getSize());
         shell.setLayout(new FillLayout());
         shell.layout();
         
-        
-        Frame frame = SWT_AWT.new_Frame(composite);
-        
         // Clean up the shell that was created above on dispose of the frame 
         frame.addWindowListener(new WindowAdapter() {
             public void windowClosed(WindowEvent e) {
#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.3
diff -u -r1.3 AwtPopupView.java
--- src/org/eclipse/albireo/examples/plugin/views/AwtPopupView.java	29 Feb 2008 22:58:18 -0000	1.3
+++ src/org/eclipse/albireo/examples/plugin/views/AwtPopupView.java	6 Mar 2008 22:39:17 -0000
@@ -7,6 +7,8 @@
 import java.awt.Point;
 import java.awt.PopupMenu;
 import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
 import java.awt.event.KeyAdapter;
 import java.awt.event.KeyEvent;
 import java.awt.event.MouseAdapter;
@@ -15,6 +17,7 @@
 import javax.swing.BorderFactory;
 import javax.swing.JComponent;
 import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JPopupMenu;
 import javax.swing.JTextField;
@@ -112,11 +115,18 @@
     }
 
     private void addAwtPopup(final JComponent c) {
+        ActionListener listener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                JOptionPane.showMessageDialog(swingControl.getAWTHierarchyRoot(), "Item selected");
+            }
+        };
         final PopupMenu popup = new PopupMenu();
         MenuItem item = new MenuItem("AWT Item 1");
+        item.addActionListener(listener);
         popup.add(item);
 
         item = new MenuItem("AWT Item 2");
+        item.addActionListener(listener);
         popup.add(item);
 
         c.add(popup);
@@ -152,10 +162,17 @@
     }
 
     private void addSwingPopup(final JComponent c) {
+        ActionListener listener = new ActionListener() {
+            public void actionPerformed(ActionEvent e) {
+                JOptionPane.showMessageDialog(swingControl.getAWTHierarchyRoot(), "Item selected");
+            }
+        };
         final JPopupMenu popup = new JPopupMenu();
         JMenuItem item = new JMenuItem("Swing Item 1");
+        item.addActionListener(listener);
         popup.add(item);
         item = new JMenuItem("Swing Item 2");
+        item.addActionListener(listener);
         popup.add(item);
         
         // We still run on Java 1.4, so can't use the new JComponent.setComponentPopupMenu
Index: plugin.xml
===================================================================
RCS file: /cvsroot/technology/org.eclipse.albireo/org.eclipse.albireo.examples.plugin/plugin.xml,v
retrieving revision 1.16
diff -u -r1.16 plugin.xml
--- plugin.xml	27 Feb 2008 17:07:56 -0000	1.16
+++ plugin.xml	6 Mar 2008 22:39:17 -0000
@@ -131,11 +131,25 @@
             </separator>
          </menu>
          <action
+               class="org.eclipse.albireo.examples.plugin.actions.AwtPrintDialogAction"
+               id="org.eclipse.albireo.examples.plugin.actions.AwtPrintDialogAction"
+               label="Open AWT Print Dialog"
+               menubarPath="dialogMenu/group"/>
+         <action
+               class="org.eclipse.albireo.examples.plugin.actions.AwtPageSetupDialogAction"
+               id="org.eclipse.albireo.examples.plugin.actions.AwtPageSetupDialogAction"
+               label="Open AWT Page Setup Dialog"
+               menubarPath="dialogMenu/group"/>
+         <action
+               class="org.eclipse.albireo.examples.plugin.actions.NullParentDialogAction"
+               id="org.eclipse.albireo.examples.plugin.actions.NullParentDialogAction"
+               label="Open Null Parent Swing Dialog"
+               menubarPath="dialogMenu/group"/>
+         <action
                class="org.eclipse.albireo.examples.plugin.actions.SimpleDialogAction"
                id="org.eclipse.albireo.examples.plugin.actions.SimpleDialogAction"
                label="Open Simple Swing Dialog"
-               menubarPath="dialogMenu/group">
-         </action>
+               menubarPath="dialogMenu/group"/>
       </actionSet>
    </extension>
 
Index: src/org/eclipse/albireo/examples/plugin/actions/AwtPrintDialogAction.java
===================================================================
RCS file: src/org/eclipse/albireo/examples/plugin/actions/AwtPrintDialogAction.java
diff -N src/org/eclipse/albireo/examples/plugin/actions/AwtPrintDialogAction.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/albireo/examples/plugin/actions/AwtPrintDialogAction.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,65 @@
+package org.eclipse.albireo.examples.plugin.actions;
+
+import java.awt.Graphics;
+import java.awt.HeadlessException;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+
+import org.eclipse.albireo.core.AwtEnvironment;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+
+public class AwtPrintDialogAction implements IWorkbenchWindowActionDelegate {
+
+    private IWorkbenchWindow window;
+
+    /**
+     * The constructor.
+     */
+    public AwtPrintDialogAction() {
+    }
+
+    public void run(IAction action) {
+        AwtEnvironment env = AwtEnvironment.getInstance(window.getShell().getDisplay());
+        env.invokeAndBlockSwt(new Runnable() {
+            public void run() {
+                try {
+                    PrinterJob job = PrinterJob.getPrinterJob();
+                    job.setJobName("A Printer Job");
+                    job.setCopies(1);
+                    job.setPrintable(new Printable() {
+                        public int print(Graphics pg, PageFormat pf, int pageNum) {
+                            if (pageNum > 0) {
+                                return Printable.NO_SUCH_PAGE; 
+                            }
+                            pg.drawString("Dummy Print Job String", 100, 100);
+                            return Printable.PAGE_EXISTS;
+                        }
+                    });
+
+                    if (job.printDialog()) {
+                        job.print();
+                    }
+                } catch (PrinterException e) {
+                    // TODO Auto-generated catch block
+                    e.printStackTrace();
+                } 
+            }
+        });
+    }
+
+    public void selectionChanged(IAction action, ISelection selection) {
+    }
+
+    public void dispose() {
+    }
+
+    public void init(IWorkbenchWindow window) {
+        this.window = window;
+    }
+
+}
Index: src/org/eclipse/albireo/examples/plugin/actions/NullParentDialogAction.java
===================================================================
RCS file: src/org/eclipse/albireo/examples/plugin/actions/NullParentDialogAction.java
diff -N src/org/eclipse/albireo/examples/plugin/actions/NullParentDialogAction.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/albireo/examples/plugin/actions/NullParentDialogAction.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,41 @@
+package org.eclipse.albireo.examples.plugin.actions;
+
+import java.awt.Frame;
+
+import javax.swing.JOptionPane;
+
+import org.eclipse.albireo.core.AwtEnvironment;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+
+public class NullParentDialogAction implements IWorkbenchWindowActionDelegate {
+
+    private IWorkbenchWindow window;
+
+    /**
+     * The constructor.
+     */
+    public NullParentDialogAction() {
+    }
+
+    public void run(IAction action) {
+        AwtEnvironment env = AwtEnvironment.getInstance(window.getShell().getDisplay());
+        env.invokeAndBlockSwt(new Runnable() {
+            public void run() {
+                JOptionPane.showMessageDialog(null, "A Null Parent Message Dialog");
+            }
+        });
+    }
+
+    public void selectionChanged(IAction action, ISelection selection) {
+    }
+
+    public void dispose() {
+    }
+
+    public void init(IWorkbenchWindow window) {
+        this.window = window;
+    }
+}
Index: src/org/eclipse/albireo/examples/plugin/actions/AwtPageSetupDialogAction.java
===================================================================
RCS file: src/org/eclipse/albireo/examples/plugin/actions/AwtPageSetupDialogAction.java
diff -N src/org/eclipse/albireo/examples/plugin/actions/AwtPageSetupDialogAction.java
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/albireo/examples/plugin/actions/AwtPageSetupDialogAction.java	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,57 @@
+package org.eclipse.albireo.examples.plugin.actions;
+
+import java.awt.Graphics;
+import java.awt.print.PageFormat;
+import java.awt.print.Printable;
+import java.awt.print.PrinterException;
+import java.awt.print.PrinterJob;
+
+import org.eclipse.albireo.core.AwtEnvironment;
+import org.eclipse.jface.action.IAction;
+import org.eclipse.jface.viewers.ISelection;
+import org.eclipse.ui.IWorkbenchWindow;
+import org.eclipse.ui.IWorkbenchWindowActionDelegate;
+
+public class AwtPageSetupDialogAction implements IWorkbenchWindowActionDelegate {
+
+    private IWorkbenchWindow window;
+
+    /**
+     * The constructor.
+     */
+    public AwtPageSetupDialogAction() {
+    }
+
+    public void run(IAction action) {
+        AwtEnvironment env = AwtEnvironment.getInstance(window.getShell().getDisplay());
+        env.invokeAndBlockSwt(new Runnable() {
+            public void run() {
+                PrinterJob job = PrinterJob.getPrinterJob();
+                job.setJobName("A Printer Job");
+                job.setCopies(1);
+                job.setPrintable(new Printable() {
+                    public int print(Graphics pg, PageFormat pf, int pageNum) {
+                        if (pageNum > 0) {
+                            return Printable.NO_SUCH_PAGE; 
+                        }
+                        pg.drawString("Dummy Print Job String", 100, 100);
+                        return Printable.PAGE_EXISTS;
+                    }
+                });
+
+                job.pageDialog(job.defaultPage()); 
+            }
+        });
+    }
+
+    public void selectionChanged(IAction action, ISelection selection) {
+    }
+
+    public void dispose() {
+    }
+
+    public void init(IWorkbenchWindow window) {
+        this.window = window;
+    }
+
+}

Back to the top