--- /dev/null
+/*******************************************************************************\r
+ * Copyright (c) 2007 SAS Institute.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * SAS Institute - initial API and implementation\r
+ *******************************************************************************/\r
+package org.simantics.utils.ui.internal.awt;\r
+\r
+import java.awt.EventQueue;\r
+import java.awt.Frame;\r
+import java.lang.reflect.InvocationTargetException;\r
+\r
+import javax.swing.UIManager;\r
+import javax.swing.UnsupportedLookAndFeelException;\r
+\r
+import org.eclipse.swt.SWT;\r
+import org.eclipse.swt.SWTException;\r
+import org.eclipse.swt.awt.SWT_AWT;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Display;\r
+import org.eclipse.swt.widgets.Shell;\r
+\r
+\r
+\r
+/**\r
+ * An environment to enable the proper display of AWT/Swing windows within a SWT or RCP \r
+ * application. This class extends the base {@link org.eclipse.swt.awt.SWT_AWT Eclipse SWT/AWT integration}\r
+ * support by\r
+ * <ul>\r
+ * <li>Using the platform-specific system Look and Feel. \r
+ * <li>Ensuring AWT modal dialogs are modal across the SWT application.\r
+ * <li>Working around various AWT/Swing bugs \r
+ * </ul>\r
+ * <p>\r
+ * This class is most helpful to applications which create new AWT/Swing windows (e.g. dialogs) rather\r
+ * than those which embed AWT/Swing components in SWT windows. For support specific to embedding\r
+ * AWT/Swing components see {@link EmbeddedSwingComposite}.\r
+ * <p>\r
+ * There is at most one instance of this class per SWT\r
+ * {@link org.eclipse.swt.widgets.Display Display}. In almost all applications\r
+ * this means that there is exactly one instance for the entire application. In fact, the\r
+ * current implementation always limits the number of instances to exactly one.\r
+ * <p>\r
+ * An instance of this class can be obtained with the static\r
+ * {@link #getInstance(Display)} method.\r
+*/\r
+public final class AwtEnvironment {\r
+ // TODO: add pop-up dismissal and font synchronization support to this level?\r
+ \r
+ private static final String GTK_LOOK_AND_FEEL_NAME = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; //$NON-NLS-1$\r
+\r
+ private static AwtEnvironment instance = null;\r
+ private static boolean isLookAndFeelInitialized = false;\r
+\r
+ private final Display display;\r
+ private final AwtDialogListener dialogListener;\r
+\r
+ /**\r
+ * Returns the single instance of AwtEnvironment for the given display. On\r
+ * the first call to this method, the necessary initialization to allow\r
+ * AWT/Swing code to run properly within an Eclipse application is done.\r
+ * This initialization includes setting the approprite look and feel and\r
+ * registering the necessary listeners to ensure proper behavior of modal\r
+ * dialogs.\r
+ * <p>\r
+ * The first call to this method must occur before any AWT/Swing APIs are\r
+ * called. \r
+ * <p>\r
+ * The current implementation limits the number of instances of\r
+ * AwtEnvironment to one. If this method is called with a display different\r
+ * to one used on a previous call, {@link UnsupportedOperationException} is\r
+ * thrown.\r
+ * \r
+ * @param display\r
+ * the non-null SWT display\r
+ * @return the AWT environment\r
+ * @exception IllegalArgumentException\r
+ * <ul>\r
+ * <li>ERROR_NULL_ARGUMENT - if the display is null</li>\r
+ * </ul>\r
+ * @exception UnsupportedOperationException -\r
+ * on attempt to use multiple displays.\r
+ */\r
+ public static AwtEnvironment getInstance(Display display) {\r
+ // For now assume a single display. If necessary, this implementation\r
+ // can be changed to create multiple environments for multiple display\r
+ // applications.\r
+ // TODO: add multiple display support\r
+ if (display == null) {\r
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);\r
+ }\r
+ if ((instance != null) && !display.equals(instance.display)) {\r
+ throw new UnsupportedOperationException("Multiple displays not supported");\r
+ }\r
+ synchronized (AwtEnvironment.class) {\r
+ if (instance == null) {\r
+ instance = new AwtEnvironment(display);\r
+ }\r
+ }\r
+ return instance;\r
+ }\r
+\r
+ // Private constructor - clients use getInstance() to obtain instances\r
+ private AwtEnvironment(Display display) {\r
+ assert display != null;\r
+\r
+ /*\r
+ * This property removes a large amount of flicker from embedded swing\r
+ * components. Ideally it would not be set until EmbeddedSwingComposite\r
+ * is used, but since its value is read once and cached by AWT, it needs\r
+ * to be set before any AWT/Swing APIs are called.\r
+ */ \r
+ // TODO: this is effective only on Windows.\r
+ System.setProperty("sun.awt.noerasebackground", "true"); //$NON-NLS-1$//$NON-NLS-2$\r
+\r
+ /*\r
+ * RCP apps always want the standard platform look and feel It's\r
+ * important to wait for the L&F to be set so that any subsequent calls\r
+ * to createFrame() will be return a frame with the proper L&F (note\r
+ * that createFrame() happens on the SWT thread).\r
+ * \r
+ * The call to invokeAndWait is safe because\r
+ * the first call AwtEnvironment.getInstance should happen\r
+ * before any (potential deadlocking) activity occurs on the \r
+ * AWT thread.\r
+ */\r
+ try {\r
+ EventQueue.invokeAndWait(new Runnable() {\r
+ public void run() {\r
+ setSystemLookAndFeel();\r
+ }\r
+ });\r
+ } catch (InterruptedException e) {\r
+ SWT.error(SWT.ERROR_FAILED_EXEC, e);\r
+ } catch (InvocationTargetException e) {\r
+ SWT.error(SWT.ERROR_FAILED_EXEC, e.getCause());\r
+ }\r
+\r
+ this.display = display;\r
+\r
+ // Listen for AWT modal dialogs to make them modal application-wide\r
+ dialogListener = new AwtDialogListener(display);\r
+ }\r
+\r
+ /**\r
+ * Invokes the given runnable in the AWT event thread while blocking user\r
+ * input on the SWT event thread. The SWT event thread will remain blocked\r
+ * until the runnable task completes, at which point this method will\r
+ * return.\r
+ * <p>\r
+ * This method is useful for displayng modal AWT/Swing dialogs from the SWT\r
+ * event thread. The modal AWT/Swing dialog will always block input across\r
+ * the whole application, but not until it appears. By calling this method,\r
+ * it is guaranteed that SWT input is blocked immediately, even before the\r
+ * AWT/Swing dialog appears.\r
+ * <p>\r
+ * To avoid unnecessary flicker, AWT/Swing dialogs should have their parent\r
+ * set to a frame returned by {@link #createDialogParentFrame()}.\r
+ * <p>\r
+ * This method must be called from the SWT event thread.\r
+ * \r
+ * @param runnable\r
+ * the code to schedule on the AWT event thread\r
+ * @exception IllegalArgumentException\r
+ * <ul>\r
+ * <li>ERROR_NULL_ARGUMENT - if the runnable is null</li>\r
+ * </ul>\r
+ * @exception SWTException\r
+ * <ul>\r
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the\r
+ * SWT event thread\r
+ * </ul>\r
+ */\r
+ public void invokeAndBlockSwt(final Runnable runnable) {\r
+ assert display != null;\r
+\r
+ /*\r
+ * This code snippet is based on the following thread on\r
+ * news.eclipse.platform.swt:\r
+ * http://dev.eclipse.org/newslists/news.eclipse.platform.swt/msg24234.html\r
+ */\r
+ if (runnable == null) {\r
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);\r
+ }\r
+ if (display != Display.getCurrent()) {\r
+ SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);\r
+ }\r
+\r
+ // Switch to the AWT thread...\r
+ EventQueue.invokeLater(new Runnable() {\r
+ public void run() {\r
+ try {\r
+ // do swing work...\r
+ runnable.run();\r
+ } finally {\r
+ display.asyncExec(new Runnable() {\r
+ public void run() {\r
+ // Unblock SWT\r
+ SwtInputBlocker.unblock();\r
+ }\r
+ });\r
+ }\r
+ }\r
+ });\r
+\r
+ // Prevent user input on SWT components\r
+ SwtInputBlocker.block();\r
+ }\r
+\r
+ /**\r
+ * Creates an AWT frame suitable as a parent for AWT/Swing dialogs. \r
+ * <p>\r
+ * This method must be called from the SWT event thread. There must be an active\r
+ * shell associated with the environment's display. \r
+ * <p>\r
+ * The created frame is a non-visible child of the active shell and will be disposed when that shell\r
+ * is disposed.\r
+ * <p>\r
+ * See {@link #createDialogParentFrame(Shell)} for more details. \r
+ * \r
+ * @return a {@link java.awt.Frame} to be used for parenting dialogs\r
+ * @exception SWTException\r
+ * <ul>\r
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the\r
+ * SWT event thread\r
+ * </ul>\r
+ * @exception IllegalStateException\r
+ * if the current display has no shells\r
+ */\r
+ public Frame createDialogParentFrame() {\r
+ if (display != Display.getCurrent()) {\r
+ SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);\r
+ }\r
+ Shell parent = display.getActiveShell();\r
+ if (parent == null) {\r
+ throw new IllegalStateException("No Active Shell");\r
+ }\r
+ return createDialogParentFrame(parent);\r
+ }\r
+ \r
+ /**\r
+ * Creates an AWT frame suitable as a parent for AWT/Swing dialogs. \r
+ * <p>\r
+ * This method must be called from the SWT event thread. There must be an active\r
+ * shell associated with the environment's display.\r
+ * <p>\r
+ * The created frame is a non-visible child of the given shell and will be disposed when that shell\r
+ * is disposed.\r
+ * <p>\r
+ * This method is useful for creating a frame to parent any AWT/Swing\r
+ * dialogs created for use inside a SWT application. A modal AWT/Swing\r
+ * dialogs will flicker less if its parent is set to the returned frame\r
+ * rather than to null or to an independently created {@link java.awt.Frame}. \r
+ * \r
+ * @return a {@link java.awt.Frame} to be used for parenting dialogs\r
+ * @exception SWTException\r
+ * <ul>\r
+ * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the\r
+ * SWT event thread\r
+ * </ul>\r
+ * @exception IllegalStateException\r
+ * if the current display has no shells\r
+ */\r
+ public Frame createDialogParentFrame(Shell parent) {\r
+ if (parent == null) {\r
+ SWT.error(SWT.ERROR_NULL_ARGUMENT);\r
+ }\r
+ if (display != Display.getCurrent()) {\r
+ SWT.error(SWT.ERROR_THREAD_INVALID_ACCESS);\r
+ }\r
+ Shell shell = new Shell(parent);\r
+ shell.setVisible(false);\r
+ Composite composite = new Composite(shell, SWT.EMBEDDED);\r
+ return SWT_AWT.new_Frame(composite);\r
+ }\r
+\r
+ // Find a shell to use, giving preference to the active shell.\r
+ Shell getShell() {\r
+ Shell shell = display.getActiveShell();\r
+ if (shell == null) {\r
+ Shell[] allShells = display.getShells();\r
+ if (allShells.length > 0) {\r
+ shell = allShells[0];\r
+ }\r
+ }\r
+ return shell;\r
+ }\r
+\r
+ void requestAwtDialogFocus() {\r
+ assert dialogListener != null;\r
+\r
+ dialogListener.requestFocus();\r
+ }\r
+\r
+ private void setSystemLookAndFeel() {\r
+ assert EventQueue.isDispatchThread(); // On AWT event thread\r
+\r
+ if (!isLookAndFeelInitialized) {\r
+ isLookAndFeelInitialized = true;\r
+ try {\r
+ String systemLaf = UIManager.getSystemLookAndFeelClassName();\r
+ String xplatLaf = UIManager.getCrossPlatformLookAndFeelClassName();\r
+\r
+ // Java makes metal the system look and feel if running under a\r
+ // non-gnome Linux desktop. Fix that here, if the RCP itself is\r
+ // running\r
+ // with the GTK windowing system set.\r
+ if (xplatLaf.equals(systemLaf) && Platform.isGtk()) {\r
+ systemLaf = GTK_LOOK_AND_FEEL_NAME;\r
+ }\r
+ UIManager.setLookAndFeel(systemLaf);\r
+ } catch (ClassNotFoundException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (InstantiationException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (IllegalAccessException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ } catch (UnsupportedLookAndFeelException e) {\r
+ // TODO Auto-generated catch block\r
+ e.printStackTrace();\r
+ }\r
+ }\r
+ }\r
+ \r
+ // This method is called by unit tests\r
+ static void reset() {\r
+ instance = null;\r
+ }\r
+\r
+}\r