1 /*******************************************************************************
\r
2 * Copyright (c) 2007 SAS Institute.
\r
3 * All rights reserved. This program and the accompanying materials
\r
4 * are made available under the terms of the Eclipse Public License v1.0
\r
5 * which accompanies this distribution, and is available at
\r
6 * http://www.eclipse.org/legal/epl-v10.html
\r
9 * SAS Institute - initial API and implementation
\r
10 *******************************************************************************/
\r
11 package org.simantics.utils.ui.internal.awt;
\r
13 import java.awt.Container;
\r
14 import java.awt.EventQueue;
\r
15 import java.awt.Frame;
\r
16 import java.awt.Toolkit;
\r
18 import javax.swing.JApplet;
\r
19 import javax.swing.JComponent;
\r
20 import javax.swing.RootPaneContainer;
\r
21 import javax.swing.SwingUtilities;
\r
22 import javax.swing.UIManager;
\r
23 import javax.swing.plaf.FontUIResource;
\r
25 import org.eclipse.swt.SWT;
\r
26 import org.eclipse.swt.SWTException;
\r
27 import org.eclipse.swt.awt.SWT_AWT;
\r
28 import org.eclipse.swt.graphics.Font;
\r
29 import org.eclipse.swt.graphics.FontData;
\r
30 import org.eclipse.swt.layout.FillLayout;
\r
31 import org.eclipse.swt.widgets.Composite;
\r
32 import org.eclipse.swt.widgets.Display;
\r
33 import org.eclipse.swt.widgets.Event;
\r
34 import org.eclipse.swt.widgets.Listener;
\r
35 import org.eclipse.swt.widgets.Widget;
\r
38 * A SWT composite widget for embedding Swing components in a SWT composite within an RCP or standalone-SWT application. The Eclipse platform
\r
39 * provides limited support for embedding Swing components through {@link org.eclipse.swt.awt.SWT_AWT}.
\r
40 * This class extends that support by
\r
42 * <li>Using the platform-specific system Look and Feel.
\r
43 * <li>Ensuring AWT modal dialogs are modal across the SWT application.
\r
44 * <li>Reducing flicker, especially on window resizes
\r
45 * <li>Allowing Tab Traversal to and from the Embedded Frame
\r
46 * <li>Dismissing most Pop-Up Menus when focus leaves the AWT frame.
\r
47 * <li>Synchronizing Font Changes from system settings
\r
48 * <li>Working around various AWT/Swing bugs
\r
51 * If, rather than embedding Swing components, you are integrating with Swing by opening
\r
52 * Swing dialogs, see the {@link AwtEnvironment} class.
\r
54 * This is an abstract that is normally used by extending it and implementing the {@link #createSwingComponent()} method. For example,
\r
56 * embeddedComposite = new EmbeddedSwingComposite(parent, SWT.NONE) {
\r
57 * protected JComponent createSwingComponent() {
\r
58 * scrollPane = new JScrollPane();
\r
59 * table = new JTable();
\r
60 * scrollPane.setViewportView(table);
\r
61 * return scrollPane;
\r
64 * embeddedComposite.populate();
\r
67 * The Swing component is created inside a standard Swing containment hierarchy, rooted in
\r
68 * a {@link javax.swing.RootPaneContainer}. The root pane container is placed inside an AWT frame, as
\r
69 * returned by {@link org.eclipse.swt.awt.SWT_AWT#new_Frame(Composite)}
\r
71 * <b>Note:</b> When you mix components from Swing/AWT and SWT toolkits, there will be two UI event threads,
\r
72 * one for AWT, one for SWT. Most SWT APIs require that you call them from the SWT thread. Swing
\r
73 * has similar restrictions though it does not enforce them as much as SWT.
\r
75 * Applications need to be aware of the current thread, and, where necessary, schedule tasks to run
\r
76 * on another thread. This has always been required in the pure Swing or SWT environments, but when
\r
77 * mixing Swing and SWT, more of this scheduling will be necessary.
\r
79 * To schedule work on the AWT event
\r
80 * thread, you can use:
\r
82 * <li>{@link javax.swing.SwingUtilities#invokeLater(Runnable)}
\r
83 * <li>{@link javax.swing.SwingUtilities#invokeAndWait(Runnable)}
\r
86 * (or similar methods in {@link java.awt.EventQueue})
\r
88 * To schedule work on the SWT event thread, use:
\r
90 * <li>{@link org.eclipse.swt.widgets.Display#asyncExec(Runnable)}
\r
91 * <li>{@link org.eclipse.swt.widgets.Display#syncExec(Runnable)}
\r
94 * Of course, as in single-toolkit environments, long-running tasks should be offloaded from either UI
\r
95 * thread to a background thread. The Eclipse jobs API can be used for this purpose.
\r
97 public abstract class EmbeddedSwingComposite extends Composite {
\r
98 private static class AwtContext {
\r
99 private Frame frame;
\r
100 private JComponent swingComponent;
\r
102 AwtContext(Frame frame) {
\r
103 assert frame != null;
\r
104 this.frame = frame;
\r
111 void setSwingComponent(JComponent swingComponent) {
\r
112 this.swingComponent = swingComponent;
\r
115 JComponent getSwingComponent() {
\r
116 return swingComponent;
\r
120 private Font currentSystemFont;
\r
121 private AwtContext awtContext;
\r
122 private AwtFocusHandler awtHandler;
\r
124 private Listener settingsListener = new Listener() {
\r
125 public void handleEvent(Event event) {
\r
126 handleSettingsChange();
\r
130 // This listener helps ensure that Swing popup menus are properly dismissed when
\r
131 // a menu item off the SWT main menu bar is shown.
\r
132 private final Listener menuListener = new Listener() {
\r
133 public void handleEvent(Event event) {
\r
134 assert awtHandler != null;
\r
136 awtHandler.postHidePopups();
\r
141 * Constructs a new instance of this class given its parent
\r
142 * and a style value describing its behavior and appearance.
\r
144 * This method must be called from the SWT event thread.
\r
146 * The style value is either one of the style constants defined in
\r
147 * class <code>SWT</code> which is applicable to instances of this
\r
148 * class, or must be built by <em>bitwise OR</em>'ing together
\r
149 * (that is, using the <code>int</code> "|" operator) two or more
\r
150 * of those <code>SWT</code> style constants. The class description
\r
151 * lists the style constants that are applicable to the class.
\r
152 * Style bits are also inherited from superclasses.
\r
155 * The styles SWT.EMBEDDED and SWT.NO_BACKGROUND will be added
\r
156 * to the specified style. Usually, no other style bits are needed.
\r
158 * @param parent a widget which will be the parent of the new instance (cannot be null)
\r
159 * @param style the style of widget to construct
\r
161 * @exception IllegalArgumentException <ul>
\r
162 * <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
\r
164 * @exception SWTException <ul>
\r
165 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the SWT event thread
\r
168 * @see Widget#getStyle
\r
170 public EmbeddedSwingComposite(Composite parent, int style) {
\r
171 super(parent, style | SWT.EMBEDDED | SWT.NO_BACKGROUND);
\r
172 getDisplay().addListener(SWT.Settings, settingsListener);
\r
173 setLayout(new FillLayout());
\r
174 currentSystemFont = getFont();
\r
178 * Populates the embedded composite with the Swing component.
\r
180 * This method must be called from the
\r
181 * SWT event thread.
\r
183 * The Swing component will be created by calling {@link #createSwingComponent()}. The creation is
\r
184 * scheduled asynchronously on the AWT event thread. This method does not wait for completion of this
\r
185 * asynchronous task, so it may return before createSwingComponent() is complete.
\r
187 * The Swing component is created inside a standard Swing containment hierarchy, rooted in
\r
188 * a {@link javax.swing.RootPaneContainer}. Clients can override {@link #addRootPaneContainer(Frame)}
\r
189 * to provide their own root pane container implementation.
\r
191 * This method can be called multiple times for a single instance. If an embedded frame exists from
\r
192 * a previous call, it is disposed.
\r
194 * @exception SWTException <ul>
\r
195 * <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
\r
196 * <li>ERROR_THREAD_INVALID_ACCESS - if not called from the SWT event thread
\r
199 public void populate() {
\r
202 scheduleComponentCreation();
\r
206 * Creates the embedded Swing component. This method is called from the AWT event thread.
\r
208 * Implement this method to provide the Swing component that will be shown inside this composite.
\r
209 * The returned component will be added to the Swing content pane. At least one component must
\r
210 * be created by this method; null is not a valid return value.
\r
212 * @return a non-null Swing component
\r
214 protected abstract JComponent createSwingComponent();
\r
217 * Adds a root pane container to the embedded AWT frame. Override this to provide your own
\r
218 * {@link javax.swing.RootPaneContainer} implementation. In most cases, it is not necessary
\r
219 * to override this method.
\r
221 * This method is called from the AWT event thread.
\r
223 * If you are defining your own root pane container, make sure that there is at least one
\r
224 * heavyweight (AWT) component in the frame's containment hierarchy; otherwise, event
\r
225 * processing will not work correctly. See http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522
\r
226 * for more information.
\r
228 * @param frame the frame to which the root pane container is added
\r
229 * @return a non-null Swing component
\r
231 protected RootPaneContainer addRootPaneContainer(Frame frame) {
\r
232 assert EventQueue.isDispatchThread(); // On AWT event thread
\r
233 assert frame != null;
\r
235 // It is important to set up the proper top level components in the frame:
\r
236 // 1) For Swing to work properly, Sun documents that there must be an implementor of
\r
237 // javax.swing.RootPaneContainer at the top of the component hierarchy.
\r
238 // 2) For proper event handling there must be a heavyweight
\r
239 // an AWT frame must contain a heavyweight component (see
\r
240 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4982522)
\r
241 // 3) The Swing implementation further narrows the options by expecting that the
\r
242 // top of the hierarchy be a JFrame, JDialog, JWindow, or JApplet. See javax.swing.PopupFactory.
\r
243 // All this drives the choice of JApplet for the top level Swing component. It is the
\r
244 // only single component that satisfies all the above. This does not imply that
\r
245 // we have a true applet; in particular, there is no notion of an applet lifecycle in this
\r
247 JApplet applet = new JApplet();
\r
249 // In JRE 1.4, the JApplet makes itself a focus cycle root. This
\r
250 // interferes with the focus handling installed on the parent frame, so
\r
251 // change it back to a non-root here.
\r
252 // TODO: consider moving the focus policy from the Frame down to the JApplet
\r
253 applet.setFocusCycleRoot(false);
\r
261 * Performs custom updates to newly set fonts. This method is called whenever a change
\r
262 * to the system font through the system settings (i.e. control panel) is detected.
\r
264 * This method is called from the AWT event thread.
\r
266 * In most cases it is not necessary to override this method. Normally, the implementation
\r
267 * of this class will automatically propogate font changes to the embedded Swing components
\r
268 * through Swing's Look and Feel support. However, if additional
\r
269 * special processing is necessary, it can be done inside this method.
\r
271 * @param newFont New AWT font
\r
273 protected void updateAwtFont(java.awt.Font newFont) {
\r
277 * Returns the embedded AWT frame. The returned frame is the root of the AWT containment
\r
278 * hierarchy for the embedded Swing component. This method can be called from
\r
281 * @return the embedded frame
\r
283 public Frame getFrame() {
\r
284 // Intentionally leaving out checkWidget() call. This may need to be called from within user's
\r
285 // createSwingComponent() method. Accessing from a non-SWT thread is OK, but we still check
\r
287 if (getDisplay() == null || isDisposed()) {
\r
288 SWT.error(SWT.ERROR_WIDGET_DISPOSED);
\r
291 return (awtContext != null) ? awtContext.getFrame() : null;
\r
294 private void createFrame() {
\r
295 assert Display.getCurrent() != null; // On SWT event thread
\r
297 // Make sure Awt environment is initialized.
\r
298 AwtEnvironment.getInstance(getDisplay());
\r
300 if (awtContext != null) {
\r
301 final Frame oldFrame = awtContext.getFrame();
\r
302 // Schedule disposal of old frame on AWT thread so that there are no problems with
\r
303 // already-scheduled operations that have not completed.
\r
304 // Note: the implementation of Frame.dispose() would schedule the use of the AWT
\r
305 // thread even if it was not done here, but it uses invokeAndWait() which is
\r
306 // prone to deadlock (and not necessary for this case).
\r
307 EventQueue.invokeLater(new Runnable() {
\r
308 public void run() {
\r
309 oldFrame.dispose();
\r
313 Frame frame = SWT_AWT.new_Frame(this);
\r
314 awtContext = new AwtContext(frame);
\r
316 // Glue the two frameworks together. Do this before anything is added to the frame
\r
317 // so that all necessary listeners are in place.
\r
318 createFocusHandlers();
\r
320 // This listener clears garbage during resizing, making it looker much cleaner
\r
321 addControlListener(new CleanResizeListener());
\r
324 private void createFocusHandlers() {
\r
325 assert awtContext != null;
\r
326 assert Display.getCurrent() != null; // On SWT event thread
\r
328 Frame frame = awtContext.getFrame();
\r
329 awtHandler = new AwtFocusHandler(frame);
\r
330 SwtFocusHandler swtHandler = new SwtFocusHandler(this);
\r
331 awtHandler.setSwtHandler(swtHandler);
\r
332 swtHandler.setAwtHandler(awtHandler);
\r
334 // Ensure that AWT popups are dimissed whenever a SWT menu is shown
\r
335 getDisplay().addFilter(SWT.Show, menuListener);
\r
337 EmbeddedChildFocusTraversalPolicy policy = new EmbeddedChildFocusTraversalPolicy(awtHandler);
\r
338 frame.setFocusTraversalPolicy(policy);
\r
341 private void scheduleComponentCreation() {
\r
342 assert awtContext != null;
\r
344 // Create AWT/Swing components on the AWT thread. This is
\r
345 // especially necessary to avoid an AWT leak bug (6411042).
\r
346 final AwtContext currentContext = awtContext;
\r
347 EventQueue.invokeLater(new Runnable() {
\r
348 public void run() {
\r
350 RootPaneContainer container = addRootPaneContainer(currentContext.getFrame());
\r
351 JComponent swingComponent = createSwingComponent();
\r
352 currentContext.setSwingComponent(swingComponent);
\r
353 container.getRootPane().getContentPane().add(swingComponent);
\r
354 setComponentFont();
\r
359 private void setComponentFont() {
\r
360 assert currentSystemFont != null;
\r
361 assert EventQueue.isDispatchThread(); // On AWT event thread
\r
363 JComponent swingComponent = (awtContext != null) ? awtContext.getSwingComponent() : null;
\r
364 if ((swingComponent != null) && !currentSystemFont.getDevice().isDisposed()) {
\r
365 FontData fontData = currentSystemFont.getFontData()[0];
\r
367 // AWT font sizes assume a 72 dpi resolution, always. The true screen resolution must be
\r
368 // used to convert the platform font size into an AWT point size that matches when displayed.
\r
369 int resolution = Toolkit.getDefaultToolkit().getScreenResolution();
\r
370 int awtFontSize = (int)Math.round((double)fontData.getHeight() * resolution / 72.0);
\r
372 // The style constants for SWT and AWT map exactly, and since they are int constants, they should
\r
373 // never change. So, the SWT style is passed through as the AWT style.
\r
374 java.awt.Font awtFont = new java.awt.Font(fontData.getName(), fontData.getStyle(), awtFontSize);
\r
376 // Update the look and feel defaults to use new font.
\r
377 updateLookAndFeel(awtFont);
\r
379 // Allow subclasses to react to font change if necessary.
\r
380 updateAwtFont(awtFont);
\r
382 // Allow components to update their UI based on new font
\r
383 // TODO: should the update method be called on the root pane instead?
\r
384 Container contentPane = swingComponent.getRootPane().getContentPane();
\r
385 SwingUtilities.updateComponentTreeUI(contentPane);
\r
389 private void updateLookAndFeel(java.awt.Font awtFont) {
\r
390 assert awtFont != null;
\r
391 assert EventQueue.isDispatchThread(); // On AWT event thread
\r
393 // The FontUIResource class marks the font as replaceable by the look and feel
\r
394 // implementation if font settings are later changed.
\r
395 FontUIResource fontResource = new FontUIResource(awtFont);
\r
397 // Assign the new font to the relevant L&F font properties. These are
\r
398 // the properties that are initially assigned to the system font
\r
399 // under the Windows look and feel.
\r
400 // TODO: It's possible that other platforms will need other assignments.
\r
401 // TODO: This does not handle fonts other than the "system" font.
\r
402 // Other fonts may change, and the Swing L&F may not be adjusting.
\r
404 UIManager.put("Button.font", fontResource); //$NON-NLS-1$
\r
405 UIManager.put("CheckBox.font", fontResource); //$NON-NLS-1$
\r
406 UIManager.put("ComboBox.font", fontResource); //$NON-NLS-1$
\r
407 UIManager.put("EditorPane.font", fontResource); //$NON-NLS-1$
\r
408 UIManager.put("Label.font", fontResource); //$NON-NLS-1$
\r
409 UIManager.put("List.font", fontResource); //$NON-NLS-1$
\r
410 UIManager.put("Panel.font", fontResource); //$NON-NLS-1$
\r
411 UIManager.put("ProgressBar.font", fontResource); //$NON-NLS-1$
\r
412 UIManager.put("RadioButton.font", fontResource); //$NON-NLS-1$
\r
413 UIManager.put("ScrollPane.font", fontResource); //$NON-NLS-1$
\r
414 UIManager.put("TabbedPane.font", fontResource); //$NON-NLS-1$
\r
415 UIManager.put("Table.font", fontResource); //$NON-NLS-1$
\r
416 UIManager.put("TableHeader.font", fontResource); //$NON-NLS-1$
\r
417 UIManager.put("TextField.font", fontResource); //$NON-NLS-1$
\r
418 UIManager.put("TextPane.font", fontResource); //$NON-NLS-1$
\r
419 UIManager.put("TitledBorder.font", fontResource); //$NON-NLS-1$
\r
420 UIManager.put("ToggleButton.font", fontResource); //$NON-NLS-1$
\r
421 UIManager.put("TreeFont.font", fontResource); //$NON-NLS-1$
\r
422 UIManager.put("ViewportFont.font", fontResource); //$NON-NLS-1$
\r
425 private void handleSettingsChange() {
\r
426 Font newFont = getDisplay().getSystemFont();
\r
427 if (!newFont.equals(currentSystemFont)) {
\r
428 currentSystemFont = newFont;
\r
429 EventQueue.invokeLater(new Runnable() {
\r
430 public void run() {
\r
431 setComponentFont();
\r
437 private boolean isFocusable() {
\r
438 if (awtContext == null) {
\r
441 JComponent swingComponent = awtContext.getSwingComponent();
\r
442 return (swingComponent != null) && swingComponent.isFocusable();
\r
446 * @see org.eclipse.swt.widgets.Control#setFocus()
\r
448 public boolean setFocus() {
\r
451 if (!isFocusable()) {
\r
454 return super.setFocus();
\r
458 * @see org.eclipse.swt.widgets.Control#forceFocus()
\r
460 public boolean forceFocus() {
\r
463 if (!isFocusable()) {
\r
466 return super.forceFocus();
\r
470 * @see org.eclipse.swt.widgets.Widget#dispose()
\r
472 public void dispose() {
\r
473 if (!isDisposed()) {
\r
474 getDisplay().removeListener(SWT.Settings, settingsListener);
\r
475 getDisplay().removeFilter(SWT.Show, menuListener);
\r