/******************************************************************************* * 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; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** *
* 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 final Logger LOGGER = LoggerFactory.getLogger(SWTAWTComponent.class);
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
* 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();
}
});
}
}
}