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
9 * SAS Institute - initial API and implementation
\r
10 *******************************************************************************/
\r
11 package org.simantics.utils.ui.awt;
\r
13 import java.awt.EventQueue;
\r
14 import java.awt.Frame;
\r
15 import java.lang.reflect.InvocationTargetException;
\r
17 import javax.swing.UIManager;
\r
18 import javax.swing.UnsupportedLookAndFeelException;
\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
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
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
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
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
48 * An instance of this class can be obtained with the static
\r
49 * {@link #getInstance(Display)} method.
\r
51 public final class AwtEnvironment {
\r
52 // TODO: add pop-up dismissal and font synchronization support to this level?
\r
54 private static final String GTK_LOOK_AND_FEEL_NAME = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; //$NON-NLS-1$
\r
56 private static AwtEnvironment instance = null;
\r
57 private static boolean isLookAndFeelInitialized = false;
\r
59 private final Display display;
\r
60 private final AwtDialogListener dialogListener;
\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
70 * The first call to this method must occur before any AWT/Swing APIs are
\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
79 * the non-null SWT display
\r
80 * @return the AWT environment
\r
81 * @exception IllegalArgumentException
\r
83 * <li>ERROR_NULL_ARGUMENT - if the display is null</li>
\r
85 * @exception UnsupportedOperationException -
\r
86 * on attempt to use multiple displays.
\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
92 // TODO: add multiple display support
\r
93 if (display == null) {
\r
94 SWT.error(SWT.ERROR_NULL_ARGUMENT);
\r
96 if ((instance != null) && !display.equals(instance.display)) {
\r
97 throw new UnsupportedOperationException("Multiple displays not supported");
\r
99 synchronized (AwtEnvironment.class) {
\r
100 if (instance == null) {
\r
101 instance = new AwtEnvironment(display);
\r
107 // Private constructor - clients use getInstance() to obtain instances
\r
108 private AwtEnvironment(Display display) {
\r
109 assert display != null;
\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
117 // TODO: this is effective only on Windows.
\r
118 System.setProperty("sun.awt.noerasebackground", "true"); //$NON-NLS-1$//$NON-NLS-2$
\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
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
132 EventQueue.invokeAndWait(new Runnable() {
\r
133 public void run() {
\r
134 setSystemLookAndFeel();
\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);
\r
143 this.display = display;
\r
145 // Listen for AWT modal dialogs to make them modal application-wide
\r
146 dialogListener = new AwtDialogListener(display);
\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
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
161 * To avoid unnecessary flicker, AWT/Swing dialogs should have their parent
\r
162 * set to a frame returned by {@link #createDialogParentFrame()}.
\r
164 * This method must be called from the SWT event thread.
\r
167 * the code to schedule on the AWT event thread
\r
168 * @exception IllegalArgumentException
\r
170 * <li>ERROR_NULL_ARGUMENT - if the runnable is null</li>
\r
172 * @exception SWTException
\r
174 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
\r
178 public void invokeAndBlockSwt(final Runnable runnable) {
\r
179 assert display != null;
\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
186 if (runnable == null) {
\r
187 SWT.error(SWT.ERROR_NULL_ARGUMENT);
\r
189 if (display != Display.getCurrent()) {
\r
190 SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
\r
193 // Switch to the AWT thread...
\r
194 EventQueue.invokeLater(new Runnable() {
\r
195 public void run() {
\r
197 // do swing work...
\r
200 display.asyncExec(new Runnable() {
\r
201 public void run() {
\r
203 SwtInputBlocker.unblock();
\r
210 // Prevent user input on SWT components
\r
211 SwtInputBlocker.block();
\r
215 * Creates an AWT frame suitable as a parent for AWT/Swing dialogs.
\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
220 * The created frame is a non-visible child of the active shell and will be disposed when that shell
\r
223 * See {@link #createDialogParentFrame(Shell)} for more details.
\r
225 * @return a {@link java.awt.Frame} to be used for parenting dialogs
\r
226 * @exception SWTException
\r
228 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
\r
231 * @exception IllegalStateException
\r
232 * if the current display has no shells
\r
234 public Frame createDialogParentFrame() {
\r
235 if (display != Display.getCurrent()) {
\r
236 SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
\r
238 Shell parent = display.getActiveShell();
\r
239 if (parent == null) {
\r
240 throw new IllegalStateException("No Active Shell");
\r
242 return createDialogParentFrame(parent);
\r
246 * Creates an AWT frame suitable as a parent for AWT/Swing dialogs.
\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
251 * The created frame is a non-visible child of the given shell and will be disposed when that shell
\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
259 * @return a {@link java.awt.Frame} to be used for parenting dialogs
\r
260 * @exception SWTException
\r
262 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
\r
265 * @exception IllegalStateException
\r
266 * if the current display has no shells
\r
268 public Frame createDialogParentFrame(Shell parent) {
\r
269 if (parent == null) {
\r
270 SWT.error(SWT.ERROR_NULL_ARGUMENT);
\r
272 if (display != Display.getCurrent()) {
\r
273 SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);
\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
281 // Find a shell to use, giving preference to the active shell.
\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
293 void requestAwtDialogFocus() {
\r
294 assert dialogListener != null;
\r
296 dialogListener.requestFocus();
\r
299 private void setSystemLookAndFeel() {
\r
300 assert EventQueue.isDispatchThread(); // On AWT event thread
\r
302 if (!isLookAndFeelInitialized) {
\r
303 isLookAndFeelInitialized = true;
\r
305 String systemLaf = UIManager.getSystemLookAndFeelClassName();
\r
306 String xplatLaf = UIManager.getCrossPlatformLookAndFeelClassName();
\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
311 // with the GTK windowing system set.
\r
312 if (xplatLaf.equals(systemLaf) && Platform.isGtk()) {
\r
313 systemLaf = GTK_LOOK_AND_FEEL_NAME;
\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
332 // This method is called by unit tests
\r
333 static void reset() {
\r