X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.utils.ui%2Fsrc%2Forg%2Fsimantics%2Futils%2Fui%2Fawt%2FAwtEnvironment.java;fp=bundles%2Forg.simantics.utils.ui%2Fsrc%2Forg%2Fsimantics%2Futils%2Fui%2Fawt%2FAwtEnvironment.java;h=3979d7cb80f92e0a573d2ab1d5bbb51a651e6a35;hb=969bd23cab98a79ca9101af33334000879fb60c5;hp=0000000000000000000000000000000000000000;hpb=866dba5cd5a3929bbeae85991796acb212338a08;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/AwtEnvironment.java b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/AwtEnvironment.java new file mode 100644 index 000000000..3979d7cb8 --- /dev/null +++ b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/AwtEnvironment.java @@ -0,0 +1,337 @@ +/******************************************************************************* + * 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.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); + } + + 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; + } + +}