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