/******************************************************************************* * Copyright (c) 2007 SAS Institute. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * SAS Institute - initial API and implementation *******************************************************************************/ package org.simantics.utils.ui.internal.awt; import java.awt.EventQueue; import java.awt.Frame; import java.lang.reflect.InvocationTargetException; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.awt.SWT_AWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; /** * An environment to enable the proper display of AWT/Swing windows within a SWT or RCP * application. This class extends the base {@link org.eclipse.swt.awt.SWT_AWT Eclipse SWT/AWT integration} * support by * *

* This class is most helpful to applications which create new AWT/Swing windows (e.g. dialogs) rather * than those which embed AWT/Swing components in SWT windows. For support specific to embedding * AWT/Swing components see {@link EmbeddedSwingComposite}. *

* There is at most one instance of this class per SWT * {@link org.eclipse.swt.widgets.Display Display}. In almost all applications * this means that there is exactly one instance for the entire application. In fact, the * current implementation always limits the number of instances to exactly one. *

* An instance of this class can be obtained with the static * {@link #getInstance(Display)} method. */ public final class AwtEnvironment { // TODO: add pop-up dismissal and font synchronization support to this level? private static final String GTK_LOOK_AND_FEEL_NAME = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; //$NON-NLS-1$ private static AwtEnvironment instance = null; private static boolean isLookAndFeelInitialized = false; private final Display display; private final AwtDialogListener dialogListener; /** * Returns the single instance of AwtEnvironment for the given display. On * the first call to this method, the necessary initialization to allow * AWT/Swing code to run properly within an Eclipse application is done. * This initialization includes setting the approprite look and feel and * registering the necessary listeners to ensure proper behavior of modal * dialogs. *

* The first call to this method must occur before any AWT/Swing APIs are * called. *

* The current implementation limits the number of instances of * AwtEnvironment to one. If this method is called with a display different * to one used on a previous call, {@link UnsupportedOperationException} is * thrown. * * @param display * the non-null SWT display * @return the AWT environment * @exception IllegalArgumentException *

* @exception UnsupportedOperationException - * on attempt to use multiple displays. */ public static AwtEnvironment getInstance(Display display) { // For now assume a single display. If necessary, this implementation // can be changed to create multiple environments for multiple display // applications. // TODO: add multiple display support if (display == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } if ((instance != null) && !display.equals(instance.display)) { throw new UnsupportedOperationException("Multiple displays not supported"); } synchronized (AwtEnvironment.class) { if (instance == null) { instance = new AwtEnvironment(display); } } return instance; } // Private constructor - clients use getInstance() to obtain instances private AwtEnvironment(Display display) { assert display != null; /* * This property removes a large amount of flicker from embedded swing * components. Ideally it would not be set until EmbeddedSwingComposite * is used, but since its value is read once and cached by AWT, it needs * to be set before any AWT/Swing APIs are called. */ // TODO: this is effective only on Windows. System.setProperty("sun.awt.noerasebackground", "true"); //$NON-NLS-1$//$NON-NLS-2$ /* * RCP apps always want the standard platform look and feel It's * important to wait for the L&F to be set so that any subsequent calls * to createFrame() will be return a frame with the proper L&F (note * that createFrame() happens on the SWT thread). * * The call to invokeAndWait is safe because * the first call AwtEnvironment.getInstance should happen * before any (potential deadlocking) activity occurs on the * AWT thread. */ try { EventQueue.invokeAndWait(new Runnable() { public void run() { setSystemLookAndFeel(); } }); } catch (InterruptedException e) { SWT.error(SWT.ERROR_FAILED_EXEC, e); } catch (InvocationTargetException e) { SWT.error(SWT.ERROR_FAILED_EXEC, e.getCause()); } this.display = display; // Listen for AWT modal dialogs to make them modal application-wide dialogListener = new AwtDialogListener(display); } /** * Invokes the given runnable in the AWT event thread while blocking user * input on the SWT event thread. The SWT event thread will remain blocked * until the runnable task completes, at which point this method will * return. *

* This method is useful for displayng modal AWT/Swing dialogs from the SWT * event thread. The modal AWT/Swing dialog will always block input across * the whole application, but not until it appears. By calling this method, * it is guaranteed that SWT input is blocked immediately, even before the * AWT/Swing dialog appears. *

* To avoid unnecessary flicker, AWT/Swing dialogs should have their parent * set to a frame returned by {@link #createDialogParentFrame()}. *

* This method must be called from the SWT event thread. * * @param runnable * the code to schedule on the AWT event thread * @exception IllegalArgumentException *

* @exception SWTException * */ public void invokeAndBlockSwt(final Runnable runnable) { assert display != null; /* * This code snippet is based on the following thread on * news.eclipse.platform.swt: * http://dev.eclipse.org/newslists/news.eclipse.platform.swt/msg24234.html */ if (runnable == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } if (display != Display.getCurrent()) { SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS); } // Switch to the AWT thread... EventQueue.invokeLater(new Runnable() { public void run() { try { // do swing work... runnable.run(); } finally { display.asyncExec(new Runnable() { public void run() { // Unblock SWT SwtInputBlocker.unblock(); } }); } } }); // Prevent user input on SWT components SwtInputBlocker.block(); } /** * Creates an AWT frame suitable as a parent for AWT/Swing dialogs. *

* This method must be called from the SWT event thread. There must be an active * shell associated with the environment's display. *

* The created frame is a non-visible child of the active shell and will be disposed when that shell * is disposed. *

* See {@link #createDialogParentFrame(Shell)} for more details. * * @return a {@link java.awt.Frame} to be used for parenting dialogs * @exception SWTException *

* @exception IllegalStateException * if the current display has no shells */ public Frame createDialogParentFrame() { if (display != Display.getCurrent()) { SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS); } Shell parent = display.getActiveShell(); if (parent == null) { throw new IllegalStateException("No Active Shell"); } return createDialogParentFrame(parent); } /** * Creates an AWT frame suitable as a parent for AWT/Swing dialogs. *

* This method must be called from the SWT event thread. There must be an active * shell associated with the environment's display. *

* The created frame is a non-visible child of the given shell and will be disposed when that shell * is disposed. *

* This method is useful for creating a frame to parent any AWT/Swing * dialogs created for use inside a SWT application. A modal AWT/Swing * dialogs will flicker less if its parent is set to the returned frame * rather than to null or to an independently created {@link java.awt.Frame}. * * @return a {@link java.awt.Frame} to be used for parenting dialogs * @exception SWTException *

* @exception IllegalStateException * if the current display has no shells */ public Frame createDialogParentFrame(Shell parent) { if (parent == null) { SWT.error(SWT.ERROR_NULL_ARGUMENT); } if (display != Display.getCurrent()) { SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS); } Shell shell = new Shell(parent); shell.setVisible(false); Composite composite = new Composite(shell, SWT.EMBEDDED); return SWT_AWT.new_Frame(composite); } // Find a shell to use, giving preference to the active shell. Shell getShell() { Shell shell = display.getActiveShell(); if (shell == null) { Shell[] allShells = display.getShells(); if (allShells.length > 0) { shell = allShells[0]; } } return shell; } void requestAwtDialogFocus() { assert dialogListener != null; dialogListener.requestFocus(); } private void setSystemLookAndFeel() { assert EventQueue.isDispatchThread(); // On AWT event thread if (!isLookAndFeelInitialized) { isLookAndFeelInitialized = true; try { String systemLaf = UIManager.getSystemLookAndFeelClassName(); String xplatLaf = UIManager.getCrossPlatformLookAndFeelClassName(); // Java makes metal the system look and feel if running under a // non-gnome Linux desktop. Fix that here, if the RCP itself is // running // with the GTK windowing system set. if (xplatLaf.equals(systemLaf) && Platform.isGtk()) { systemLaf = GTK_LOOK_AND_FEEL_NAME; } UIManager.setLookAndFeel(systemLaf); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedLookAndFeelException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } // This method is called by unit tests static void reset() { instance = null; } }