X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.utils.ui%2Fsrc%2Forg%2Fsimantics%2Futils%2Fui%2FSWTAWTComponent.java;fp=bundles%2Forg.simantics.utils.ui%2Fsrc%2Forg%2Fsimantics%2Futils%2Fui%2FSWTAWTComponent.java;h=8031a93abb4f6728feb6a1afbfee586feece68ea;hp=06d8939114accc76a1993f6edca6f5bd851f6371;hb=0ae2b770234dfc3cbb18bd38f324125cf0faca07;hpb=24e2b34260f219f0d1644ca7a138894980e25b14 diff --git a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java index 06d893911..8031a93ab 100644 --- a/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java +++ b/bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/SWTAWTComponent.java @@ -1,597 +1,597 @@ -/******************************************************************************* - * Copyright (c) 2007, 2013 Association for Decentralized Information Management - * in Industry THTH ry. - * 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: - * VTT Technical Research Centre of Finland - initial API and implementation - * Semantum Oy - workaround for Simantics issue #3518 - *******************************************************************************/ -package org.simantics.utils.ui; - -import java.awt.AWTEvent; -import java.awt.Component; -import java.awt.Container; -import java.awt.EventQueue; -import java.awt.Frame; -import java.awt.GridLayout; -import java.awt.Toolkit; -import java.awt.event.AWTEventListener; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; - -import javax.swing.JApplet; -import javax.swing.SwingUtilities; -import javax.swing.UIManager; -import javax.swing.plaf.FontUIResource; - -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Status; -import org.eclipse.swt.SWT; -import org.eclipse.swt.awt.SWT_AWT; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.FontData; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Shell; -import org.simantics.utils.threads.AWTThread; -import org.simantics.utils.threads.ThreadUtils; -import org.simantics.utils.threads.logger.ITask; -import org.simantics.utils.threads.logger.ThreadLogger; -import org.simantics.utils.ui.internal.Activator; -import org.simantics.utils.ui.internal.awt.AwtEnvironment; -import org.simantics.utils.ui.internal.awt.AwtFocusHandler; -import org.simantics.utils.ui.internal.awt.CleanResizeListener; -import org.simantics.utils.ui.internal.awt.EmbeddedChildFocusTraversalPolicy; -import org.simantics.utils.ui.internal.awt.SwtFocusHandler; - - -/** - *
- *        embeddedComposite = new SWTAWTComposite(parent, SWT.NONE) {
- *            protected JComponent createSwingComponent() {
- *                scrollPane = new JScrollPane();
- *                table = new JTable();
- *                scrollPane.setViewportView(table);
- *                return scrollPane;
- *            }
- *        };
- *        // For asynchronous AWT UI population of the swing components:
- *        embeddedComposite.populate();
- *        // and optionally you can wait until the AWT UI population
- *        // has finished:
- *        embeddedComposite.waitUntilPopulated();
- *
- *        // OR:
- *
- *        // Do both things above in one call to block until the
- *        // AWT UI population is complete:
- *        embeddedComposite.syncPopulate();
- *
- *        // OR:
- *
- *        // Set a callback for asynchronous completion in the AWT thread:
- *        embeddedComposite.populate(component -> {
-              // AWT components have been created for component
- *        });
- *
- *        // All methods assume all invocations are made from the SWT display thread.
- * 
- *

- * - * @author Tuukka Lehtonen - */ -public abstract class SWTAWTComponent extends Composite { - - private static class AwtContext { - private Frame frame; - private Component swingComponent; - - AwtContext(Frame frame) { - assert frame != null; - this.frame = frame; - } - - Frame getFrame() { - return frame; - } - - void setSwingComponent(Component swingComponent) { - this.swingComponent = swingComponent; - } - - Component getSwingComponent() { - return swingComponent; - } - - } - - private Font currentSystemFont; - private AwtContext awtContext; - private AwtFocusHandler awtHandler; - - private JApplet panel; - - private final AtomicBoolean populationStarted = new AtomicBoolean(false); - - private final AtomicBoolean populated = new AtomicBoolean(false); - - private final Semaphore populationSemaphore = new Semaphore(0); - - private Consumer populatedCallback; - - private static AWTEventListener awtListener = null; - - private Listener settingsListener = new Listener() { - public void handleEvent(Event event) { - handleSettingsChange(); - } - }; - - // This listener helps ensure that Swing popup menus are properly dismissed when - // a menu item off the SWT main menu bar is shown. - private final Listener menuListener = new Listener() { - public void handleEvent(Event event) { - assert awtHandler != null; - awtHandler.postHidePopups(); - } - }; - - public SWTAWTComponent(Composite parent, int style) { - super(parent, style | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE | SWT.EMBEDDED); - getDisplay().addListener(SWT.Settings, settingsListener); - setLayout(new FillLayout()); - currentSystemFont = getFont(); - this.addDisposeListener(new DisposeListener() { - @Override - public void widgetDisposed(DisposeEvent e) { - doDispose(); - } - }); - } - - protected void doDispose() { - getDisplay().removeListener(SWT.Settings, settingsListener); - getDisplay().removeFilter(SWT.Show, menuListener); - - ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { - @Override - public void run() { - AwtContext ctx = awtContext; - if (ctx != null) { - ctx.frame.dispose(); - } - awtContext = null; - if (panel != null) { - panel.removeAll(); - panel = null; - } - } - }); - } - - static class FocusRepairListener implements AWTEventListener { - @Override - public void eventDispatched(AWTEvent e) { - if (e.getID() == MouseEvent.MOUSE_PRESSED) { - Object src = e.getSource(); - if (src instanceof Component) { - ((Component) src).requestFocus(); - } - } - } - } - - /** - * Create a global AWTEventListener for focus management. - * This helps at least with Linux/GTK problems of transferring focus - * to workbench parts when clicking on AWT screen territory. - * - * NOTE: There is really no need to dispose this once it's been initialized. - * - * NOTE: must be invoked from AWT thread. - */ - private static synchronized void initAWTEventListener() { - if (!AWTThread.getThreadAccess().currentThreadAccess()) - throw new AssertionError("not invoked from AWT thread"); - if (awtListener == null) { - awtListener = new FocusRepairListener(); - Toolkit.getDefaultToolkit().addAWTEventListener(awtListener, AWTEvent.MOUSE_EVENT_MASK); - } - } - - protected Container getContainer() { - return panel; - } - - public Component getAWTComponent() { - assert awtContext != null; - return awtContext.getSwingComponent(); - } - - /** - * This method must always be called from SWT thread. This method should be - * used with extreme care since it will block the calling thread (i.e. the - * SWT thread) while the AWT thread initializes itself by spinning and - * dispatching SWT events. This diminishes the possibility of deadlock - * (reported between AWT and SWT) but still all UI's are recommended to use - * the asynchronous non-blocking UI population offered by - * {@link #populate(Consumer)} - * - * @see #populate(Consumer) - */ - public void syncPopulate() { - populate(); - waitUntilPopulated(); - } - - /** - * This method must always be called from SWT thread. This will schedule the - * real AWT component creation into the AWT thread and call the provided - * asynchronous callback after the UI population is complete. - * This prevents the possibility of deadlocking. - */ - public void populate(Consumer callback) { - populate(); - this.populatedCallback = callback; - } - - /** - * This method will create an AWT {@link Frame} through {@link SWT_AWT} and - * schedule AWT canvas initialization into the AWT thread. It will not wait - * for AWT initialization to complete. - */ - public void populate() { - if (!populationStarted.compareAndSet(false, true)) - throw new IllegalStateException(this + ".populate was invoked multiple times"); - - checkWidget(); - ITask task = ThreadLogger.getInstance().begin("createFrame"); - createFrame(); - task.finish(); - scheduleComponentCreation(); - } - - public void waitUntilPopulated() { - if (populated.get()) - return; - - try { - boolean done = false; - while (!done) { - done = populationSemaphore.tryAcquire(10, TimeUnit.MILLISECONDS); - while (!done && getDisplay().readAndDispatch()) { - /* - * Note: readAndDispatch can cause this to be disposed. - */ - if(isDisposed()) return; - done = populationSemaphore.tryAcquire(); - } - } - } catch (InterruptedException e) { - throw new Error("EmbeddedSwingComposite population interrupted for class " + this, e); - } - } - - /** - * Returns the embedded AWT frame. The returned frame is the root of the AWT containment - * hierarchy for the embedded Swing component. This method can be called from - * any thread. - * - * @return the embedded frame - */ - public Frame getFrame() { - // Intentionally leaving out checkWidget() call. This may need to be called from within user's - // createSwingComponent() method. Accessing from a non-SWT thread is OK, but we still check - // for disposal - if (getDisplay() == null || isDisposed()) { - SWT.error(SWT.ERROR_WIDGET_DISPOSED); - } - AwtContext ctx = awtContext; - return (ctx != null) ? ctx.getFrame() : null; - } - - private void createFrame() { - assert Display.getCurrent() != null; // On SWT event thread - - // Make sure Awt environment is initialized. - AwtEnvironment.getInstance(getDisplay()); - - if (awtContext != null) { - final Frame oldFrame = awtContext.getFrame(); - // Schedule disposal of old frame on AWT thread so that there are no problems with - // already-scheduled operations that have not completed. - // Note: the implementation of Frame.dispose() would schedule the use of the AWT - // thread even if it was not done here, but it uses invokeAndWait() which is - // prone to deadlock (and not necessary for this case). - EventQueue.invokeLater(new Runnable() { - public void run() { - oldFrame.dispose(); - } - }); - } - Frame frame = SWT_AWT.new_Frame(this); - awtContext = new AwtContext(frame); - - // See Simantics issue #3518 - workaroundJava7FocusProblem(frame); - - // Glue the two frameworks together. Do this before anything is added to the frame - // so that all necessary listeners are in place. - createFocusHandlers(); - - // This listener clears garbage during resizing, making it looker much cleaner - addControlListener(new CleanResizeListener()); - } - - private void workaroundJava7FocusProblem(Frame frame) { - String ver = System.getProperty("java.version"); - if (ver.startsWith("1.7") || ver.startsWith("1.8")) { - try { - frame.addWindowListener(new Java7FocusFixListener(this, frame)); - } catch (SecurityException e) { - Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); - } catch (NoSuchMethodException e) { - Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); - } - } - } - - static class Java7FocusFixListener extends WindowAdapter { - - Method shellSetActiveControl; - Control control; - Frame frame; - - public Java7FocusFixListener(Control control, Frame frame) throws NoSuchMethodException, SecurityException { - this.shellSetActiveControl = Shell.class.getDeclaredMethod("setActiveControl", Control.class); - this.frame = frame; - this.control = control; - } - - @Override - public void windowActivated(WindowEvent e) { - SWTUtils.asyncExec(control, new Runnable() { - @Override - public void run() { - if (control.isDisposed()) - return; - if (control.getDisplay().getFocusControl() == control) { - try { - boolean accessible = shellSetActiveControl.isAccessible(); - if (!accessible) - shellSetActiveControl.setAccessible(true); - shellSetActiveControl.invoke(control.getShell(), control); - if (!accessible) - shellSetActiveControl.setAccessible(false); - } catch (SecurityException e) { - Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); - } catch (IllegalArgumentException e) { - Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); - } catch (IllegalAccessException e) { - Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); - } catch (InvocationTargetException e) { - Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getCause().getMessage(), e.getCause())); - } - } - } - }); - } - - } - - private void createFocusHandlers() { - assert awtContext != null; - assert Display.getCurrent() != null; // On SWT event thread - - Frame frame = awtContext.getFrame(); - awtHandler = new AwtFocusHandler(frame); - SwtFocusHandler swtHandler = new SwtFocusHandler(this); - awtHandler.setSwtHandler(swtHandler); - swtHandler.setAwtHandler(awtHandler); - - // Ensure that AWT pop-ups are dismissed whenever a SWT menu is shown - getDisplay().addFilter(SWT.Show, menuListener); - - EmbeddedChildFocusTraversalPolicy policy = new EmbeddedChildFocusTraversalPolicy(awtHandler); - frame.setFocusTraversalPolicy(policy); - } - - private void scheduleComponentCreation() { - assert awtContext != null; - - // Create AWT/Swing components on the AWT thread. This is - // especially necessary to avoid an AWT leak bug (6411042). - final AwtContext currentContext = awtContext; - ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { - @Override - public void run() { - // Make sure AWT focus fix is in place. - initAWTEventListener(); - - panel = addRootPaneContainer(currentContext.getFrame()); - panel.setLayout(new GridLayout(1,1,0,0)); - try { - Component swingComponent = createSwingComponent(); - currentContext.setSwingComponent(swingComponent); - panel.getRootPane().getContentPane().add(swingComponent); - //panel.add(swingComponent); - setComponentFont(); - } finally { - // Needed to support #waitUntilPopulated - populated.set(true); - if (populationSemaphore != null) - populationSemaphore.release(); - if (populatedCallback != null) { - populatedCallback.accept(SWTAWTComponent.this); - populatedCallback = null; - } - } - } - }); - } - - /** - * Adds a root pane container to the embedded AWT frame. Override this to provide your own - * {@link javax.swing.RootPaneContainer} implementation. In most cases, it is not necessary - * to override this method. - *

- * This method is called from the AWT event thread. - *

- * If you are defining your own root pane container, make sure that there is at least one - * heavyweight (AWT) component in the frame's containment hierarchy; otherwise, event - * processing will not work correctly. See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522 - * for more information. - * - * @param frame the frame to which the root pane container is added - * @return a non-null Swing component - */ - protected JApplet addRootPaneContainer(Frame frame) { - assert EventQueue.isDispatchThread(); // On AWT event thread - assert frame != null; - - // It is important to set up the proper top level components in the frame: - // 1) For Swing to work properly, Sun documents that there must be an implementor of - // javax.swing.RootPaneContainer at the top of the component hierarchy. - // 2) For proper event handling there must be a heavyweight - // an AWT frame must contain a heavyweight component (see - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522) - // 3) The Swing implementation further narrows the options by expecting that the - // top of the hierarchy be a JFrame, JDialog, JWindow, or JApplet. See javax.swing.PopupFactory. - // All this drives the choice of JApplet for the top level Swing component. It is the - // only single component that satisfies all the above. This does not imply that - // we have a true applet; in particular, there is no notion of an applet lifecycle in this - // context. - JApplet applet = new JApplet(); - - // In JRE 1.4, the JApplet makes itself a focus cycle root. This - // interferes with the focus handling installed on the parent frame, so - // change it back to a non-root here. - // TODO: consider moving the focus policy from the Frame down to the JApplet - applet.setFocusCycleRoot(false); - - frame.add(applet); - - return applet; - } - - /** - * Override this to customize what kind of AWT/Swing UI is created by this - * {@link SWTAWTComponent}. - * - * @return the AWT/Swing component created by this SWTAWT bridging control - * @thread AWT - */ - protected abstract Component createSwingComponent(); - - private void setComponentFont() { - assert currentSystemFont != null; - assert EventQueue.isDispatchThread(); // On AWT event thread - - Component swingComponent = (awtContext != null) ? awtContext.getSwingComponent() : null; - if ((swingComponent != null) && !currentSystemFont.getDevice().isDisposed()) { - FontData fontData = currentSystemFont.getFontData()[0]; - - // AWT font sizes assume a 72 dpi resolution, always. The true screen resolution must be - // used to convert the platform font size into an AWT point size that matches when displayed. - int resolution = Toolkit.getDefaultToolkit().getScreenResolution(); - int awtFontSize = (int)Math.round((double)fontData.getHeight() * resolution / 72.0); - - // The style constants for SWT and AWT map exactly, and since they are int constants, they should - // never change. So, the SWT style is passed through as the AWT style. - java.awt.Font awtFont = new java.awt.Font(fontData.getName(), fontData.getStyle(), awtFontSize); - - // Update the look and feel defaults to use new font. - updateLookAndFeel(awtFont); - - // Allow subclasses to react to font change if necessary. - updateAwtFont(awtFont); - - // Allow components to update their UI based on new font - // TODO: should the update method be called on the root pane instead? - Container contentPane = SwingUtilities.getRootPane(swingComponent).getContentPane(); - SwingUtilities.updateComponentTreeUI(contentPane); - } - } - - private void updateLookAndFeel(java.awt.Font awtFont) { - assert awtFont != null; - assert EventQueue.isDispatchThread(); // On AWT event thread - - // The FontUIResource class marks the font as replaceable by the look and feel - // implementation if font settings are later changed. - FontUIResource fontResource = new FontUIResource(awtFont); - - // Assign the new font to the relevant L&F font properties. These are - // the properties that are initially assigned to the system font - // under the Windows look and feel. - // TODO: It's possible that other platforms will need other assignments. - // TODO: This does not handle fonts other than the "system" font. - // Other fonts may change, and the Swing L&F may not be adjusting. - - UIManager.put("Button.font", fontResource); //$NON-NLS-1$ - UIManager.put("CheckBox.font", fontResource); //$NON-NLS-1$ - UIManager.put("ComboBox.font", fontResource); //$NON-NLS-1$ - UIManager.put("EditorPane.font", fontResource); //$NON-NLS-1$ - UIManager.put("Label.font", fontResource); //$NON-NLS-1$ - UIManager.put("List.font", fontResource); //$NON-NLS-1$ - UIManager.put("Panel.font", fontResource); //$NON-NLS-1$ - UIManager.put("ProgressBar.font", fontResource); //$NON-NLS-1$ - UIManager.put("RadioButton.font", fontResource); //$NON-NLS-1$ - UIManager.put("ScrollPane.font", fontResource); //$NON-NLS-1$ - UIManager.put("TabbedPane.font", fontResource); //$NON-NLS-1$ - UIManager.put("Table.font", fontResource); //$NON-NLS-1$ - UIManager.put("TableHeader.font", fontResource); //$NON-NLS-1$ - UIManager.put("TextField.font", fontResource); //$NON-NLS-1$ - UIManager.put("TextPane.font", fontResource); //$NON-NLS-1$ - UIManager.put("TitledBorder.font", fontResource); //$NON-NLS-1$ - UIManager.put("ToggleButton.font", fontResource); //$NON-NLS-1$ - UIManager.put("TreeFont.font", fontResource); //$NON-NLS-1$ - UIManager.put("ViewportFont.font", fontResource); //$NON-NLS-1$ - } - - /** - * Performs custom updates to newly set fonts. This method is called whenever a change - * to the system font through the system settings (i.e. control panel) is detected. - *

- * This method is called from the AWT event thread. - *

- * In most cases it is not necessary to override this method. Normally, the implementation - * of this class will automatically propogate font changes to the embedded Swing components - * through Swing's Look and Feel support. However, if additional - * special processing is necessary, it can be done inside this method. - * - * @param newFont New AWT font - */ - protected void updateAwtFont(java.awt.Font newFont) { - } - - private void handleSettingsChange() { - Font newFont = getDisplay().getSystemFont(); - if (!newFont.equals(currentSystemFont)) { - currentSystemFont = newFont; - EventQueue.invokeLater(new Runnable() { - public void run() { - setComponentFont(); - } - }); - } - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2013 Association for Decentralized Information Management + * in Industry THTH ry. + * 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: + * VTT Technical Research Centre of Finland - initial API and implementation + * Semantum Oy - workaround for Simantics issue #3518 + *******************************************************************************/ +package org.simantics.utils.ui; + +import java.awt.AWTEvent; +import java.awt.Component; +import java.awt.Container; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.Toolkit; +import java.awt.event.AWTEventListener; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import javax.swing.JApplet; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.FontUIResource; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.swt.SWT; +import org.eclipse.swt.awt.SWT_AWT; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.simantics.utils.threads.AWTThread; +import org.simantics.utils.threads.ThreadUtils; +import org.simantics.utils.threads.logger.ITask; +import org.simantics.utils.threads.logger.ThreadLogger; +import org.simantics.utils.ui.internal.Activator; +import org.simantics.utils.ui.internal.awt.AwtEnvironment; +import org.simantics.utils.ui.internal.awt.AwtFocusHandler; +import org.simantics.utils.ui.internal.awt.CleanResizeListener; +import org.simantics.utils.ui.internal.awt.EmbeddedChildFocusTraversalPolicy; +import org.simantics.utils.ui.internal.awt.SwtFocusHandler; + + +/** + *

+ *        embeddedComposite = new SWTAWTComposite(parent, SWT.NONE) {
+ *            protected JComponent createSwingComponent() {
+ *                scrollPane = new JScrollPane();
+ *                table = new JTable();
+ *                scrollPane.setViewportView(table);
+ *                return scrollPane;
+ *            }
+ *        };
+ *        // For asynchronous AWT UI population of the swing components:
+ *        embeddedComposite.populate();
+ *        // and optionally you can wait until the AWT UI population
+ *        // has finished:
+ *        embeddedComposite.waitUntilPopulated();
+ *
+ *        // OR:
+ *
+ *        // Do both things above in one call to block until the
+ *        // AWT UI population is complete:
+ *        embeddedComposite.syncPopulate();
+ *
+ *        // OR:
+ *
+ *        // Set a callback for asynchronous completion in the AWT thread:
+ *        embeddedComposite.populate(component -> {
+              // AWT components have been created for component
+ *        });
+ *
+ *        // All methods assume all invocations are made from the SWT display thread.
+ * 
+ *

+ * + * @author Tuukka Lehtonen + */ +public abstract class SWTAWTComponent extends Composite { + + private static class AwtContext { + private Frame frame; + private Component swingComponent; + + AwtContext(Frame frame) { + assert frame != null; + this.frame = frame; + } + + Frame getFrame() { + return frame; + } + + void setSwingComponent(Component swingComponent) { + this.swingComponent = swingComponent; + } + + Component getSwingComponent() { + return swingComponent; + } + + } + + private Font currentSystemFont; + private AwtContext awtContext; + private AwtFocusHandler awtHandler; + + private JApplet panel; + + private final AtomicBoolean populationStarted = new AtomicBoolean(false); + + private final AtomicBoolean populated = new AtomicBoolean(false); + + private final Semaphore populationSemaphore = new Semaphore(0); + + private Consumer populatedCallback; + + private static AWTEventListener awtListener = null; + + private Listener settingsListener = new Listener() { + public void handleEvent(Event event) { + handleSettingsChange(); + } + }; + + // This listener helps ensure that Swing popup menus are properly dismissed when + // a menu item off the SWT main menu bar is shown. + private final Listener menuListener = new Listener() { + public void handleEvent(Event event) { + assert awtHandler != null; + awtHandler.postHidePopups(); + } + }; + + public SWTAWTComponent(Composite parent, int style) { + super(parent, style | SWT.NO_BACKGROUND | SWT.NO_REDRAW_RESIZE | SWT.EMBEDDED); + getDisplay().addListener(SWT.Settings, settingsListener); + setLayout(new FillLayout()); + currentSystemFont = getFont(); + this.addDisposeListener(new DisposeListener() { + @Override + public void widgetDisposed(DisposeEvent e) { + doDispose(); + } + }); + } + + protected void doDispose() { + getDisplay().removeListener(SWT.Settings, settingsListener); + getDisplay().removeFilter(SWT.Show, menuListener); + + ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { + @Override + public void run() { + AwtContext ctx = awtContext; + if (ctx != null) { + ctx.frame.dispose(); + } + awtContext = null; + if (panel != null) { + panel.removeAll(); + panel = null; + } + } + }); + } + + static class FocusRepairListener implements AWTEventListener { + @Override + public void eventDispatched(AWTEvent e) { + if (e.getID() == MouseEvent.MOUSE_PRESSED) { + Object src = e.getSource(); + if (src instanceof Component) { + ((Component) src).requestFocus(); + } + } + } + } + + /** + * Create a global AWTEventListener for focus management. + * This helps at least with Linux/GTK problems of transferring focus + * to workbench parts when clicking on AWT screen territory. + * + * NOTE: There is really no need to dispose this once it's been initialized. + * + * NOTE: must be invoked from AWT thread. + */ + private static synchronized void initAWTEventListener() { + if (!AWTThread.getThreadAccess().currentThreadAccess()) + throw new AssertionError("not invoked from AWT thread"); + if (awtListener == null) { + awtListener = new FocusRepairListener(); + Toolkit.getDefaultToolkit().addAWTEventListener(awtListener, AWTEvent.MOUSE_EVENT_MASK); + } + } + + protected Container getContainer() { + return panel; + } + + public Component getAWTComponent() { + assert awtContext != null; + return awtContext.getSwingComponent(); + } + + /** + * This method must always be called from SWT thread. This method should be + * used with extreme care since it will block the calling thread (i.e. the + * SWT thread) while the AWT thread initializes itself by spinning and + * dispatching SWT events. This diminishes the possibility of deadlock + * (reported between AWT and SWT) but still all UI's are recommended to use + * the asynchronous non-blocking UI population offered by + * {@link #populate(Consumer)} + * + * @see #populate(Consumer) + */ + public void syncPopulate() { + populate(); + waitUntilPopulated(); + } + + /** + * This method must always be called from SWT thread. This will schedule the + * real AWT component creation into the AWT thread and call the provided + * asynchronous callback after the UI population is complete. + * This prevents the possibility of deadlocking. + */ + public void populate(Consumer callback) { + populate(); + this.populatedCallback = callback; + } + + /** + * This method will create an AWT {@link Frame} through {@link SWT_AWT} and + * schedule AWT canvas initialization into the AWT thread. It will not wait + * for AWT initialization to complete. + */ + public void populate() { + if (!populationStarted.compareAndSet(false, true)) + throw new IllegalStateException(this + ".populate was invoked multiple times"); + + checkWidget(); + ITask task = ThreadLogger.getInstance().begin("createFrame"); + createFrame(); + task.finish(); + scheduleComponentCreation(); + } + + public void waitUntilPopulated() { + if (populated.get()) + return; + + try { + boolean done = false; + while (!done) { + done = populationSemaphore.tryAcquire(10, TimeUnit.MILLISECONDS); + while (!done && getDisplay().readAndDispatch()) { + /* + * Note: readAndDispatch can cause this to be disposed. + */ + if(isDisposed()) return; + done = populationSemaphore.tryAcquire(); + } + } + } catch (InterruptedException e) { + throw new Error("EmbeddedSwingComposite population interrupted for class " + this, e); + } + } + + /** + * Returns the embedded AWT frame. The returned frame is the root of the AWT containment + * hierarchy for the embedded Swing component. This method can be called from + * any thread. + * + * @return the embedded frame + */ + public Frame getFrame() { + // Intentionally leaving out checkWidget() call. This may need to be called from within user's + // createSwingComponent() method. Accessing from a non-SWT thread is OK, but we still check + // for disposal + if (getDisplay() == null || isDisposed()) { + SWT.error(SWT.ERROR_WIDGET_DISPOSED); + } + AwtContext ctx = awtContext; + return (ctx != null) ? ctx.getFrame() : null; + } + + private void createFrame() { + assert Display.getCurrent() != null; // On SWT event thread + + // Make sure Awt environment is initialized. + AwtEnvironment.getInstance(getDisplay()); + + if (awtContext != null) { + final Frame oldFrame = awtContext.getFrame(); + // Schedule disposal of old frame on AWT thread so that there are no problems with + // already-scheduled operations that have not completed. + // Note: the implementation of Frame.dispose() would schedule the use of the AWT + // thread even if it was not done here, but it uses invokeAndWait() which is + // prone to deadlock (and not necessary for this case). + EventQueue.invokeLater(new Runnable() { + public void run() { + oldFrame.dispose(); + } + }); + } + Frame frame = SWT_AWT.new_Frame(this); + awtContext = new AwtContext(frame); + + // See Simantics issue #3518 + workaroundJava7FocusProblem(frame); + + // Glue the two frameworks together. Do this before anything is added to the frame + // so that all necessary listeners are in place. + createFocusHandlers(); + + // This listener clears garbage during resizing, making it looker much cleaner + addControlListener(new CleanResizeListener()); + } + + private void workaroundJava7FocusProblem(Frame frame) { + String ver = System.getProperty("java.version"); + if (ver.startsWith("1.7") || ver.startsWith("1.8")) { + try { + frame.addWindowListener(new Java7FocusFixListener(this, frame)); + } catch (SecurityException e) { + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); + } catch (NoSuchMethodException e) { + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); + } + } + } + + static class Java7FocusFixListener extends WindowAdapter { + + Method shellSetActiveControl; + Control control; + Frame frame; + + public Java7FocusFixListener(Control control, Frame frame) throws NoSuchMethodException, SecurityException { + this.shellSetActiveControl = Shell.class.getDeclaredMethod("setActiveControl", Control.class); + this.frame = frame; + this.control = control; + } + + @Override + public void windowActivated(WindowEvent e) { + SWTUtils.asyncExec(control, new Runnable() { + @Override + public void run() { + if (control.isDisposed()) + return; + if (control.getDisplay().getFocusControl() == control) { + try { + boolean accessible = shellSetActiveControl.isAccessible(); + if (!accessible) + shellSetActiveControl.setAccessible(true); + shellSetActiveControl.invoke(control.getShell(), control); + if (!accessible) + shellSetActiveControl.setAccessible(false); + } catch (SecurityException e) { + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); + } catch (IllegalArgumentException e) { + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); + } catch (IllegalAccessException e) { + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e)); + } catch (InvocationTargetException e) { + Activator.getDefault().getLog().log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getCause().getMessage(), e.getCause())); + } + } + } + }); + } + + } + + private void createFocusHandlers() { + assert awtContext != null; + assert Display.getCurrent() != null; // On SWT event thread + + Frame frame = awtContext.getFrame(); + awtHandler = new AwtFocusHandler(frame); + SwtFocusHandler swtHandler = new SwtFocusHandler(this); + awtHandler.setSwtHandler(swtHandler); + swtHandler.setAwtHandler(awtHandler); + + // Ensure that AWT pop-ups are dismissed whenever a SWT menu is shown + getDisplay().addFilter(SWT.Show, menuListener); + + EmbeddedChildFocusTraversalPolicy policy = new EmbeddedChildFocusTraversalPolicy(awtHandler); + frame.setFocusTraversalPolicy(policy); + } + + private void scheduleComponentCreation() { + assert awtContext != null; + + // Create AWT/Swing components on the AWT thread. This is + // especially necessary to avoid an AWT leak bug (6411042). + final AwtContext currentContext = awtContext; + ThreadUtils.asyncExec(AWTThread.getThreadAccess(), new Runnable() { + @Override + public void run() { + // Make sure AWT focus fix is in place. + initAWTEventListener(); + + panel = addRootPaneContainer(currentContext.getFrame()); + panel.setLayout(new GridLayout(1,1,0,0)); + try { + Component swingComponent = createSwingComponent(); + currentContext.setSwingComponent(swingComponent); + panel.getRootPane().getContentPane().add(swingComponent); + //panel.add(swingComponent); + setComponentFont(); + } finally { + // Needed to support #waitUntilPopulated + populated.set(true); + if (populationSemaphore != null) + populationSemaphore.release(); + if (populatedCallback != null) { + populatedCallback.accept(SWTAWTComponent.this); + populatedCallback = null; + } + } + } + }); + } + + /** + * Adds a root pane container to the embedded AWT frame. Override this to provide your own + * {@link javax.swing.RootPaneContainer} implementation. In most cases, it is not necessary + * to override this method. + *

+ * This method is called from the AWT event thread. + *

+ * If you are defining your own root pane container, make sure that there is at least one + * heavyweight (AWT) component in the frame's containment hierarchy; otherwise, event + * processing will not work correctly. See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522 + * for more information. + * + * @param frame the frame to which the root pane container is added + * @return a non-null Swing component + */ + protected JApplet addRootPaneContainer(Frame frame) { + assert EventQueue.isDispatchThread(); // On AWT event thread + assert frame != null; + + // It is important to set up the proper top level components in the frame: + // 1) For Swing to work properly, Sun documents that there must be an implementor of + // javax.swing.RootPaneContainer at the top of the component hierarchy. + // 2) For proper event handling there must be a heavyweight + // an AWT frame must contain a heavyweight component (see + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522) + // 3) The Swing implementation further narrows the options by expecting that the + // top of the hierarchy be a JFrame, JDialog, JWindow, or JApplet. See javax.swing.PopupFactory. + // All this drives the choice of JApplet for the top level Swing component. It is the + // only single component that satisfies all the above. This does not imply that + // we have a true applet; in particular, there is no notion of an applet lifecycle in this + // context. + JApplet applet = new JApplet(); + + // In JRE 1.4, the JApplet makes itself a focus cycle root. This + // interferes with the focus handling installed on the parent frame, so + // change it back to a non-root here. + // TODO: consider moving the focus policy from the Frame down to the JApplet + applet.setFocusCycleRoot(false); + + frame.add(applet); + + return applet; + } + + /** + * Override this to customize what kind of AWT/Swing UI is created by this + * {@link SWTAWTComponent}. + * + * @return the AWT/Swing component created by this SWTAWT bridging control + * @thread AWT + */ + protected abstract Component createSwingComponent(); + + private void setComponentFont() { + assert currentSystemFont != null; + assert EventQueue.isDispatchThread(); // On AWT event thread + + Component swingComponent = (awtContext != null) ? awtContext.getSwingComponent() : null; + if ((swingComponent != null) && !currentSystemFont.getDevice().isDisposed()) { + FontData fontData = currentSystemFont.getFontData()[0]; + + // AWT font sizes assume a 72 dpi resolution, always. The true screen resolution must be + // used to convert the platform font size into an AWT point size that matches when displayed. + int resolution = Toolkit.getDefaultToolkit().getScreenResolution(); + int awtFontSize = (int)Math.round((double)fontData.getHeight() * resolution / 72.0); + + // The style constants for SWT and AWT map exactly, and since they are int constants, they should + // never change. So, the SWT style is passed through as the AWT style. + java.awt.Font awtFont = new java.awt.Font(fontData.getName(), fontData.getStyle(), awtFontSize); + + // Update the look and feel defaults to use new font. + updateLookAndFeel(awtFont); + + // Allow subclasses to react to font change if necessary. + updateAwtFont(awtFont); + + // Allow components to update their UI based on new font + // TODO: should the update method be called on the root pane instead? + Container contentPane = SwingUtilities.getRootPane(swingComponent).getContentPane(); + SwingUtilities.updateComponentTreeUI(contentPane); + } + } + + private void updateLookAndFeel(java.awt.Font awtFont) { + assert awtFont != null; + assert EventQueue.isDispatchThread(); // On AWT event thread + + // The FontUIResource class marks the font as replaceable by the look and feel + // implementation if font settings are later changed. + FontUIResource fontResource = new FontUIResource(awtFont); + + // Assign the new font to the relevant L&F font properties. These are + // the properties that are initially assigned to the system font + // under the Windows look and feel. + // TODO: It's possible that other platforms will need other assignments. + // TODO: This does not handle fonts other than the "system" font. + // Other fonts may change, and the Swing L&F may not be adjusting. + + UIManager.put("Button.font", fontResource); //$NON-NLS-1$ + UIManager.put("CheckBox.font", fontResource); //$NON-NLS-1$ + UIManager.put("ComboBox.font", fontResource); //$NON-NLS-1$ + UIManager.put("EditorPane.font", fontResource); //$NON-NLS-1$ + UIManager.put("Label.font", fontResource); //$NON-NLS-1$ + UIManager.put("List.font", fontResource); //$NON-NLS-1$ + UIManager.put("Panel.font", fontResource); //$NON-NLS-1$ + UIManager.put("ProgressBar.font", fontResource); //$NON-NLS-1$ + UIManager.put("RadioButton.font", fontResource); //$NON-NLS-1$ + UIManager.put("ScrollPane.font", fontResource); //$NON-NLS-1$ + UIManager.put("TabbedPane.font", fontResource); //$NON-NLS-1$ + UIManager.put("Table.font", fontResource); //$NON-NLS-1$ + UIManager.put("TableHeader.font", fontResource); //$NON-NLS-1$ + UIManager.put("TextField.font", fontResource); //$NON-NLS-1$ + UIManager.put("TextPane.font", fontResource); //$NON-NLS-1$ + UIManager.put("TitledBorder.font", fontResource); //$NON-NLS-1$ + UIManager.put("ToggleButton.font", fontResource); //$NON-NLS-1$ + UIManager.put("TreeFont.font", fontResource); //$NON-NLS-1$ + UIManager.put("ViewportFont.font", fontResource); //$NON-NLS-1$ + } + + /** + * Performs custom updates to newly set fonts. This method is called whenever a change + * to the system font through the system settings (i.e. control panel) is detected. + *

+ * This method is called from the AWT event thread. + *

+ * In most cases it is not necessary to override this method. Normally, the implementation + * of this class will automatically propogate font changes to the embedded Swing components + * through Swing's Look and Feel support. However, if additional + * special processing is necessary, it can be done inside this method. + * + * @param newFont New AWT font + */ + protected void updateAwtFont(java.awt.Font newFont) { + } + + private void handleSettingsChange() { + Font newFont = getDisplay().getSystemFont(); + if (!newFont.equals(currentSystemFont)) { + currentSystemFont = newFont; + EventQueue.invokeLater(new Runnable() { + public void run() { + setComponentFont(); + } + }); + } + } + +}