]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/internal/awt/AwtEnvironment.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics.utils.ui / src / org / simantics / utils / ui / internal / awt / AwtEnvironment.java
1 /*******************************************************************************
2  * Copyright (c) 2007 SAS Institute.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     SAS Institute - initial API and implementation
10  *******************************************************************************/
11 package org.simantics.utils.ui.internal.awt;
12
13 import java.awt.EventQueue;
14 import java.awt.Frame;
15 import java.lang.reflect.InvocationTargetException;
16
17 import javax.swing.UIManager;
18 import javax.swing.UnsupportedLookAndFeelException;
19
20 import org.eclipse.swt.SWT;
21 import org.eclipse.swt.SWTException;
22 import org.eclipse.swt.awt.SWT_AWT;
23 import org.eclipse.swt.widgets.Composite;
24 import org.eclipse.swt.widgets.Display;
25 import org.eclipse.swt.widgets.Shell;
26
27
28
29 /**
30  * An environment to enable the proper display of AWT/Swing windows within a SWT or RCP 
31  * application. This class extends the base {@link org.eclipse.swt.awt.SWT_AWT Eclipse SWT/AWT integration}
32  * support by
33  * <ul>
34  * <li>Using the platform-specific system Look and Feel. 
35  * <li>Ensuring AWT modal dialogs are modal across the SWT application.
36  * <li>Working around various AWT/Swing bugs  
37  * </ul>
38  * <p>
39  * This class is most helpful to applications which create new AWT/Swing windows (e.g. dialogs) rather
40  * than those which embed AWT/Swing components in SWT windows. For support specific to embedding
41  * AWT/Swing components see {@link EmbeddedSwingComposite}.
42  * <p>
43  * There is at most one instance of this class per SWT
44  * {@link org.eclipse.swt.widgets.Display Display}. In almost all applications
45  * this means that there is exactly one instance for the entire application. In fact, the
46  * current implementation always limits the number of instances to exactly one.
47  * <p>
48  * An instance of this class can be obtained with the static
49  * {@link #getInstance(Display)} method.
50 */
51 public final class AwtEnvironment {
52     // TODO: add pop-up dismissal and font synchronization support to this level?
53     
54     private static final String GTK_LOOK_AND_FEEL_NAME = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; //$NON-NLS-1$
55
56     private static AwtEnvironment instance = null;
57     private static boolean isLookAndFeelInitialized = false;
58
59     private final Display display;
60     private final AwtDialogListener dialogListener;
61
62     /**
63      * Returns the single instance of AwtEnvironment for the given display. On
64      * the first call to this method, the necessary initialization to allow
65      * AWT/Swing code to run properly within an Eclipse application is done.
66      * This initialization includes setting the approprite look and feel and
67      * registering the necessary listeners to ensure proper behavior of modal
68      * dialogs.
69      * <p>
70      * The first call to this method must occur before any AWT/Swing APIs are
71      * called. 
72      * <p>
73      * The current implementation limits the number of instances of
74      * AwtEnvironment to one. If this method is called with a display different
75      * to one used on a previous call, {@link UnsupportedOperationException} is
76      * thrown.
77      * 
78      * @param display
79      *            the non-null SWT display
80      * @return the AWT environment
81      * @exception IllegalArgumentException
82      *                <ul>
83      *                <li>ERROR_NULL_ARGUMENT - if the display is null</li>
84      *                </ul>
85      * @exception UnsupportedOperationException -
86      *                on attempt to use multiple displays.
87      */
88     public static AwtEnvironment getInstance(Display display) {
89         // For now assume a single display. If necessary, this implementation
90         // can be changed to create multiple environments for multiple display
91         // applications.
92         // TODO: add multiple display support
93         if (display == null) {
94             SWT.error(SWT.ERROR_NULL_ARGUMENT);
95         }
96         if ((instance != null) && !display.equals(instance.display)) {
97             throw new UnsupportedOperationException("Multiple displays not supported");
98         }
99         synchronized (AwtEnvironment.class) {
100             if (instance == null) {
101                 instance = new AwtEnvironment(display);
102             }
103         }
104         return instance;
105     }
106
107     // Private constructor - clients use getInstance() to obtain instances
108     private AwtEnvironment(Display display) {
109         assert display != null;
110
111         /*
112          * This property removes a large amount of flicker from embedded swing
113          * components. Ideally it would not be set until EmbeddedSwingComposite
114          * is used, but since its value is read once and cached by AWT, it needs
115          * to be set before any AWT/Swing APIs are called.
116          */       
117         // TODO: this is effective only on Windows.
118         System.setProperty("sun.awt.noerasebackground", "true"); //$NON-NLS-1$//$NON-NLS-2$
119
120         /*
121          * RCP apps always want the standard platform look and feel It's
122          * important to wait for the L&F to be set so that any subsequent calls
123          * to createFrame() will be return a frame with the proper L&F (note
124          * that createFrame() happens on the SWT thread).
125          * 
126          * The call to invokeAndWait is safe because
127          * the first call AwtEnvironment.getInstance should happen
128          * before any (potential deadlocking) activity occurs on the 
129          * AWT thread.
130          */
131         try {
132             EventQueue.invokeAndWait(new Runnable() {
133                 public void run() {
134                     setSystemLookAndFeel();
135                 }
136             });
137         } catch (InterruptedException e) {
138             SWT.error(SWT.ERROR_FAILED_EXEC, e);
139         } catch (InvocationTargetException e) {
140             SWT.error(SWT.ERROR_FAILED_EXEC, e.getCause());
141         }
142
143         this.display = display;
144
145         // Listen for AWT modal dialogs to make them modal application-wide
146         dialogListener = new AwtDialogListener(display);
147     }
148
149     /**
150      * Invokes the given runnable in the AWT event thread while blocking user
151      * input on the SWT event thread. The SWT event thread will remain blocked
152      * until the runnable task completes, at which point this method will
153      * return.
154      * <p>
155      * This method is useful for displayng modal AWT/Swing dialogs from the SWT
156      * event thread. The modal AWT/Swing dialog will always block input across
157      * the whole application, but not until it appears. By calling this method,
158      * it is guaranteed that SWT input is blocked immediately, even before the
159      * AWT/Swing dialog appears.
160      * <p>
161      * To avoid unnecessary flicker, AWT/Swing dialogs should have their parent
162      * set to a frame returned by {@link #createDialogParentFrame()}.
163      * <p>
164      * This method must be called from the SWT event thread.
165      * 
166      * @param runnable
167      *            the code to schedule on the AWT event thread
168      * @exception IllegalArgumentException
169      *                <ul>
170      *                <li>ERROR_NULL_ARGUMENT - if the runnable is null</li>
171      *                </ul>
172      * @exception SWTException
173      *                <ul>
174      *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
175      *                SWT event thread
176      *                </ul>
177      */
178     public void invokeAndBlockSwt(final Runnable runnable) {
179         assert display != null;
180
181         /*
182          * This code snippet is based on the following thread on
183          * news.eclipse.platform.swt:
184          * http://dev.eclipse.org/newslists/news.eclipse.platform.swt/msg24234.html
185          */
186         if (runnable == null) {
187             SWT.error(SWT.ERROR_NULL_ARGUMENT);
188         }
189         if (display != Display.getCurrent()) {
190             SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
191         }
192
193         // Switch to the AWT thread...
194         EventQueue.invokeLater(new Runnable() {
195             public void run() {
196                 try {
197                     // do swing work...
198                     runnable.run();
199                 } finally {
200                     display.asyncExec(new Runnable() {
201                         public void run() {
202                             // Unblock SWT
203                             SwtInputBlocker.unblock();
204                         }
205                     });
206                 }
207             }
208         });
209
210         // Prevent user input on SWT components
211         SwtInputBlocker.block();
212     }
213
214     /**
215      * Creates an AWT frame suitable as a parent for AWT/Swing dialogs. 
216      * <p>
217      * This method must be called from the SWT event thread. There must be an active
218      * shell associated with the environment's display.  
219      * <p>
220      * The created frame is a non-visible child of the active shell and will be disposed when that shell
221      * is disposed.
222      * <p>
223      * See {@link #createDialogParentFrame(Shell)} for more details. 
224      * 
225      * @return a {@link java.awt.Frame} to be used for parenting dialogs
226      * @exception SWTException
227      *                <ul>
228      *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
229      *                SWT event thread
230      *                </ul>
231      * @exception IllegalStateException
232      *                if the current display has no shells
233      */
234     public Frame createDialogParentFrame() {
235         if (display != Display.getCurrent()) {
236             SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
237         }
238         Shell parent = display.getActiveShell();
239         if (parent == null) {
240             throw new IllegalStateException("No Active Shell");
241         }
242         return createDialogParentFrame(parent);
243     }
244     
245     /**
246      * Creates an AWT frame suitable as a parent for AWT/Swing dialogs. 
247      * <p>
248      * This method must be called from the SWT event thread. There must be an active
249      * shell associated with the environment's display.
250      * <p>
251      * The created frame is a non-visible child of the given shell and will be disposed when that shell
252      * is disposed.
253      * <p>
254      * This method is useful for creating a frame to parent any AWT/Swing
255      * dialogs created for use inside a SWT application. A modal AWT/Swing
256      * dialogs will flicker less if its parent is set to the returned frame
257      * rather than to null or to an independently created {@link java.awt.Frame}.  
258      * 
259      * @return a {@link java.awt.Frame} to be used for parenting dialogs
260      * @exception SWTException
261      *                <ul>
262      *                <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
263      *                SWT event thread
264      *                </ul>
265      * @exception IllegalStateException
266      *                if the current display has no shells
267      */
268     public Frame createDialogParentFrame(Shell parent) {
269         if (parent == null) {
270             SWT.error(SWT.ERROR_NULL_ARGUMENT);
271         }
272         if (display != Display.getCurrent()) {
273             SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
274         }
275         Shell shell = new Shell(parent);
276         shell.setVisible(false);
277         Composite composite = new Composite(shell, SWT.EMBEDDED);
278         return SWT_AWT.new_Frame(composite);
279     }
280
281     // Find a shell to use, giving preference to the active shell.
282     Shell getShell() {
283         Shell shell = display.getActiveShell();
284         if (shell == null) {
285             Shell[] allShells = display.getShells();
286             if (allShells.length > 0) {
287                 shell = allShells[0];
288             }
289         }
290         return shell;
291     }
292
293     void requestAwtDialogFocus() {
294         assert dialogListener != null;
295
296         dialogListener.requestFocus();
297     }
298
299     private void setSystemLookAndFeel() {
300         assert EventQueue.isDispatchThread(); // On AWT event thread
301
302         if (!isLookAndFeelInitialized) {
303             isLookAndFeelInitialized = true;
304             try {
305                 String systemLaf = UIManager.getSystemLookAndFeelClassName();
306                 String xplatLaf = UIManager.getCrossPlatformLookAndFeelClassName();
307
308                 // Java makes metal the system look and feel if running under a
309                 // non-gnome Linux desktop. Fix that here, if the RCP itself is
310                 // running
311                 // with the GTK windowing system set.
312                 if (xplatLaf.equals(systemLaf) && Platform.isGtk()) {
313                     systemLaf = GTK_LOOK_AND_FEEL_NAME;
314                 }
315                 UIManager.setLookAndFeel(systemLaf);
316             } catch (ClassNotFoundException e) {
317                 // TODO Auto-generated catch block
318                 e.printStackTrace();
319             } catch (InstantiationException e) {
320                 // TODO Auto-generated catch block
321                 e.printStackTrace();
322             } catch (IllegalAccessException e) {
323                 // TODO Auto-generated catch block
324                 e.printStackTrace();
325             } catch (UnsupportedLookAndFeelException e) {
326                 // TODO Auto-generated catch block
327                 e.printStackTrace();
328             }
329         }
330     }
331     
332     // This method is called by unit tests
333     static void reset() {
334         instance = null;
335     }
336
337 }