/*******************************************************************************
* Copyright (c) 2007-2008 SAS Institute Inc., ILOG S.A.
* 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 Inc. - initial API and implementation
* ILOG S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.albireo.core;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FocusTraversalPolicy;
import java.awt.Frame;
import java.awt.KeyboardFocusManager;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.JApplet;
import javax.swing.JComponent;
import javax.swing.JRootPane;
import javax.swing.LayoutFocusTraversalPolicy;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.text.JTextComponent;
import org.eclipse.albireo.internal.CleanResizeListener;
import org.eclipse.albireo.internal.ComponentDebugging;
import org.eclipse.albireo.internal.FocusHandler;
import org.eclipse.albireo.internal.GlobalFocusHandler;
import org.eclipse.albireo.internal.Platform;
import org.eclipse.albireo.internal.RunnableWithResult;
import org.eclipse.albireo.internal.SwtPopupHandler;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.awt.SWT_AWT;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
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.Menu;
import org.eclipse.swt.widgets.Widget;
public abstract class SwingControl extends Composite
{
// Whether to print debugging information regarding size propagation
// and layout.
static final boolean verboseSizeLayout = false;
public static final String SWT_PARENT_PROPERTY_KEY = "org.eclipse.albireo.swtParent";
private final Listener settingsListener = new Listener () {
public void handleEvent ( final Event event )
{
handleSettingsChange ();
}
};
final/* private */Display display;
private final Composite layoutDeferredAncestor;
// The width of the border to keep around the embedded AWT frame.
private final int borderWidth;
// The immediate holder of the embedded AWT frame. It is == this if
// borderWidth == 0, or a different Composite if borderWidth != 0.
private Composite borderlessChild;
private Frame frame;
private RootPaneContainer rootPaneContainer;
private JComponent swingComponent;
private boolean populated = false;
// ========================================================================
// Constructors
/**
* Constructs a new embedded Swing control, given its parent and a style
* value describing its behavior and appearance.
* <p>
* This method must be called from the SWT event thread.
* <p>
* The style value is either one of the style constants defined in class
* <code>SWT</code> which is applicable to instances of this class, or must
* be built by <em>bitwise OR</em>'ing together (that is, using the
* <code>int</code> "|" operator) two or more of those <code>SWT</code>
* style constants. The class description lists the style constants that are
* applicable to the class. Style bits are also inherited from superclasses.
* </p>
* <p>
* The styles SWT.EMBEDDED and SWT.NO_BACKGROUND will be added to the
* specified style. Usually, no other style bits are needed.
*
* @param parent
* a widget which will be the parent of the new instance (cannot
* be null)
* @param style
* the style of widget to construct
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* SWT event thread
* </ul>
*
* @see Widget#getStyle
*/
public SwingControl ( final Composite parent, final int style )
{
super ( parent, style | ( ( style & SWT.BORDER ) == 0 ? SWT.EMBEDDED : 0 ) | SWT.NO_BACKGROUND );
setLayout ( new FillLayout () );
this.display = getDisplay ();
this.display.addListener ( SWT.Settings, this.settingsListener );
// Avoid a layout() on the outer Controls until we have been able to
// determine our preferred size - which takes a roundtrip to the AWT
// thread and back.
this.layoutDeferredAncestor = getLayoutAncestor ();
if ( this.layoutDeferredAncestor != null )
{
this.layoutDeferredAncestor.setLayoutDeferred ( true );
// Ensure populate() is called nevertheless.
ThreadingHandler.getInstance ().asyncExec ( this.display, new Runnable () {
public void run ()
{
populate ();
}
} );
}
// Get the width of the border, i.e. the margins of this Composite.
// If style contains SWT.BORDER, it is platform dependent (2 pixels on
// Linux/gtk); otherwise it must be 0.
this.borderWidth = getBorderWidth ();
if ( ( style & SWT.BORDER ) != 0 )
{
// Fix for BR #91896
// <https://bugs.eclipse.org/bugs/show_bug.cgi?id=91896>:
// Since the SWT_AWT.new_Frame creates a low-level connection
// between the handle of its argument Composite and the Frame
// it creates, and this low-level connection propagates size
// from the Composite to the Frame automatically, it ignores
// the border. Work around it by creating an intermediate
// Composite.
this.borderlessChild = new Composite ( this, style & ~SWT.BORDER | SWT.EMBEDDED | SWT.NO_BACKGROUND ) {
/**
* Overridden.
*/
@Override
public Rectangle getClientArea ()
{
assert Display.getCurrent () != null; // On SWT event thread
final Rectangle rect = super.getClientArea ();
SwingControl.this.assignInitialClientArea ( rect );
return rect;
}
/**
* Overridden to return false and prevent any focus change if
* the embedded Swing component is not focusable.
*/
@Override
public boolean forceFocus ()
{
checkWidget ();
return handleFocusOperation ( new RunnableWithResult () {
@Override
public void run ()
{
boolean success;
if ( isDisposed () )
{
success = false;
}
else
{
success = superForceFocus ();
success = postProcessForceFocus ( success );
}
setResult ( new Boolean ( success ) );
}
} );
}
/**
* Overridden to return false and prevent any focus change if
* the embedded Swing component is not focusable.
*/
@Override
public boolean setFocus ()
{
checkWidget ();
return handleFocusOperation ( new RunnableWithResult () {
@Override
public void run ()
{
final boolean success = isDisposed () ? false : superSetFocus ();
setResult ( new Boolean ( success ) );
}
} );
}
private boolean superSetFocus ()
{
return super.setFocus ();
}
private boolean superForceFocus ()
{
return super.forceFocus ();
}
};
}
else
{
// If no border is needed, there is no need to create another
// Composite.
assert this.borderWidth == 0;
this.borderlessChild = this;
}
// Clean up on dispose
addListener ( SWT.Dispose, new Listener () {
public void handleEvent ( final Event event )
{
handleDispose ();
}
} );
initCleanResizeListener ();
}
// ========================================================================
// Initialization
/**
* Populates the embedded composite with the Swing component.
* <p>
* This method must be called from the SWT event thread.
* <p>
* The Swing component will be created by calling
* {@link #createSwingComponent()}. The creation is scheduled asynchronously
* on the AWT event thread. This method does not wait for completion of this
* asynchronous task, so it may return before createSwingComponent() is
* complete.
* <p>
* The Swing component is created inside a standard Swing containment
* hierarchy, rooted in a {@link javax.swing.RootPaneContainer}. Clients can
* override {@link #addRootPaneContainer(Frame)} to provide their own root
* pane container implementation.
* <p>
* The steps above happen only on the first call to this method; subsequent
* calls have no effect.
*
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li> <li>ERROR_THREAD_INVALID_ACCESS - if not
* called from the SWT event thread
* </ul>
*/
protected void populate ()
{
if ( isDisposed () )
{
return;
}
if ( !this.populated )
{
this.populated = true;
createFrame ();
scheduleComponentCreation ();
}
}
protected void createFrame ()
{
assert Display.getCurrent () != null; // On SWT event thread
// Make sure Awt environment is initialized.
AwtEnvironment.getInstance ( this.display );
this.frame = SWT_AWT.new_Frame ( this.borderlessChild );
if ( verboseSizeLayout )
{
ComponentDebugging.addComponentSizeDebugListeners ( this.frame );
}
initializeFocusManagement ();
initKeystrokeManagement ();
initFirstResizeActions ();
if ( HIDE_SWING_POPUPS_ON_SWT_SHELL_BOUNDS_CHANGE )
{
getShell ().addControlListener ( this.shellControlListener );
}
}
protected void scheduleComponentCreation ()
{
assert this.frame != null;
final Color foreground = getForeground ();
final Color background = getBackground ();
final Font font = getFont ();
final FontData[] fontData = font.getFontData ();
// Create AWT/Swing components on the AWT thread. This is
// especially necessary to avoid an AWT leak bug (Sun bug 6411042).
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
// If the client is using the now-obsolete
// javax.swing.DefaultFocusManager
// class under Windows, the default focus traversal policy may
// be changed from
// LayoutFocusTraversalPolicy (set by the L&F) to
// LegacyGlueFocusTraversalPolicy.
// The latter policy causes stack overflow errors when setting
// focus on a JApplet
// in an embedded frame, so we force the policy to
// LayoutFocusTraversalPolicy here
// and later when the JApplet is created. It is especially
// important to do this since
// the Eclipse workbench code itself uses DefaultFocusManager
// (see
// org.eclipse.ui.internal.handlers.WidgetMethodHandler).
// TODO: can this be queried from the L&F?
final KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager ();
if ( kfm.getDefaultFocusTraversalPolicy ().getClass ().getName () == "javax.swing.LegacyGlueFocusTraversalPolicy" )
{
kfm.setDefaultFocusTraversalPolicy ( new LayoutFocusTraversalPolicy () );
}
if ( SwingControl.this.frame.getFocusTraversalPolicy () != null && SwingControl.this.frame.getFocusTraversalPolicy ().getClass ().getName () == "javax.swing.LegacyGlueFocusTraversalPolicy" )
{
SwingControl.this.frame.setFocusTraversalPolicy ( new LayoutFocusTraversalPolicy () );
}
SwingControl.this.rootPaneContainer = addRootPaneContainer ( SwingControl.this.frame );
initPopupMenuSupport ( SwingControl.this.rootPaneContainer.getRootPane () );
// The color of the frame is visible during redraws. Use the
// same color, to reduce flickering, and set it as soon as
// possible
setComponentBackground ( SwingControl.this.frame, background, true );
SwingControl.this.swingComponent = createSwingComponent ();
if ( SwingControl.this.swingComponent != null )
{
// Pass on color and font values
// The color of the content Pane is visible permanently.
setComponentForeground ( SwingControl.this.rootPaneContainer.getContentPane (), foreground, true );
setComponentBackground ( SwingControl.this.rootPaneContainer.getContentPane (), background, true );
setComponentFont ( font, fontData, true );
SwingControl.this.rootPaneContainer.getRootPane ().getContentPane ().add ( SwingControl.this.swingComponent );
SwingControl.this.swingComponent.putClientProperty ( SWT_PARENT_PROPERTY_KEY, SwingControl.this );
}
// Invoke hooks, for use by the application.
afterComponentCreatedAWTThread ();
try
{
ThreadingHandler.getInstance ().asyncExec ( SwingControl.this.display, new Runnable () {
public void run ()
{
// Propagate focus to Swing, if necesssary
SwingControl.this.focusHandler.activateEmbeddedFrame ();
// Now that the preferred size is known,
// enable
// the layout on the layoutable ancestor.
if ( SwingControl.this.layoutDeferredAncestor != null && !SwingControl.this.layoutDeferredAncestor.isDisposed () )
{
SwingControl.this.layoutDeferredAncestor.layout ();
SwingControl.this.layoutDeferredAncestor.setLayoutDeferred ( false );
}
// Invoke hooks, for use by the application.
afterComponentCreatedSWTThread ();
}
} );
}
catch ( final SWTException e )
{
if ( e.code == SWT.ERROR_WIDGET_DISPOSED )
{
return;
}
else
{
throw e;
}
}
}
} );
}
/**
* 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.
* <p>
* This method is called from the AWT event thread.
* <p>
* 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 RootPaneContainer addRootPaneContainer ( final 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
// 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 or javax.swing.SwingUtilities.convertPoint,
// for example.
// 4) Trying to add a JFrame, JDialog, or JWindow as child to a Frame
// yields an exception "adding a window to a container". However,
// JApplet is lucky since it inherits from Panel, not from Window.
//
// 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.
final JApplet applet = new ToplevelPanel ();
if ( Platform.isWin32 () )
{
// Avoid stack overflows by ensuring correct focus traversal policy
// (see comments in scheduleComponentCreation() for details)
applet.setFocusTraversalPolicy ( new LayoutFocusTraversalPolicy () );
}
frame.add ( applet );
return applet;
}
/**
* The top-level java.awt.Panel, added as child of the frame.
*/
private class ToplevelPanel extends JApplet
{
// Overridden from JApplet.
@Override
protected JRootPane createRootPane ()
{
final JRootPane rootPane = new ToplevelRootPane ();
rootPane.setOpaque ( true );
return rootPane;
}
}
/**
* The top-level javax.swing.JRootPane, added as child of the toplevel
* Panel.
*/
private class ToplevelRootPane extends JRootPane
{
// Keep the sizes cache up to date.
// The JRootPane, not the JApplet, is the "validation root",
// as determined by RepaintManager.addInvalidComponent().
// It is here that we can intercept the relevant
// invalidate()/validateTree() calls.
@Override
protected void validateTree ()
{
super.validateTree ();
// JRootPane returns a wrong value for getMaximumSize(),
// namely [0,2147483647] instead of [2147483647,2147483647],
// so we use the content pane's maximum size instead.
updateCachedAWTSizes ( getMinimumSize (), getPreferredSize (), getContentPane ().getMaximumSize () );
}
}
/**
* Creates the embedded Swing component. This method is called from the AWT
* event thread.
* <p>
* Implement this method to provide the Swing component that will be shown
* inside this composite. The returned component will be added to the Swing
* content pane. At least one component must be created by this method; null
* is not a valid return value.
*
* @return a non-null Swing component
*/
protected abstract JComponent createSwingComponent ();
/**
* This callback is invoked after the embedded Swing component has been
* added to this control.
* <p>
* This method is executed on the AWT thread.
*/
protected void afterComponentCreatedAWTThread ()
{
}
/**
* This callback is invoked after the embedded Swing component has been
* added to this control.
* <p>
* This method is executed on the SWT thread.
*/
protected void afterComponentCreatedSWTThread ()
{
}
// ========================================================================
// Accessors
/**
* Returns the Swing component contained in this control. This method may be
* called from any thread.
*
* @return The embedded Swing component, or <code>null</code> if it has not
* yet been initialized.
*/
public/* final */JComponent getSwingComponent ()
{
return this.swingComponent;
}
/**
* Returns the root of the AWT component hierarchy; this is the top-level
* parent of the embedded Swing component. This method may be called from
* any thread.
*
* @return An AWT container, usually a Window, or <code>null</code> if the
* initialization is not yet complete.
*/
public/* final */Container getAWTHierarchyRoot ()
{
// Intentionally leaving out checkWidget() call. This method may be
// called from the
// AWT thread. We still check for disposal, however
if ( isDisposed () )
{
SWT.error ( SWT.ERROR_WIDGET_DISPOSED );
}
return this.frame;
}
// ========================================================================
// Size management
// Outside this control (on the SWT side) the size management protocol
// consists of the computeSize() method (bottom-up size propagation)
// and of the two setBounds() methods (top-down size propagation).
//
// Inside this control (on the AWT side) the size management protocol
// consists of the getPreferredSize(), getMinimumSize(), getMaximumSize()
// methods (bottom-up size propagation) and of the layout() method
// (top-down size propagation).
//
// We connect these two protocols.
//
// One cannot call swingComponent.getPreferredSize()/getMinimumSize()/
// getMaximumSize() outside the AWT event thread - this would lead to
// deadlocks. Therefore we use a cache of their values; this cache
// can be accessed from any thread (with 'synchronized', of course).
//
// We change and access the three sizes atomically at once, so that
// they are consistent with each other.
private Dimension cachedMinSize = new Dimension ( 0, 0 );
private Dimension cachedPrefSize = new Dimension ( 0, 0 );
private Dimension cachedMaxSize = new Dimension ( 0, 0 );
// Since the swingComponent is not already initialized in the constructor,
// this control initially has no notion of what its preferred size could
// be. This variable is
// - 0 initially,
// - 1 after the sizes have been set from the AWT side,
// - 2 after these sizes have been taken into account by the SWT side.
private int cachedSizesInitialized = 0;
// Work around against a bug observed with the RelayoutExampleView on
// Windows with JDK 1.6: The SWT_AWT.new_Frame method executes this code:
// parent.getDisplay().asyncExec(new Runnable() {
// public void run () {
// if (parent.isDisposed()) return;
// final Rectangle clientArea = parent.getClientArea();
// EventQueue.invokeLater(new Runnable () {
// public void run () {
// frame.setSize (clientArea.width, clientArea.height);
// frame.validate ();
// }
// });
// }
// });
// This code overwrites the size of the frame with a size that is not
// valid any more!
static final boolean INITIAL_CLIENT_AREA_WORKAROUND =
// This code is found in SWT_AWT.new_Frame for gtk, motif, win32.
Platform.isGtk () || Platform.isMotif () || Platform.isWin32 ();
private Rectangle initialClientArea;
/*
* Overridden (javadoc inherited).
*/
@Override
public Rectangle getClientArea ()
{
checkWidget ();
assert Display.getCurrent () != null; // On SWT event thread
final Rectangle rect = super.getClientArea ();
if ( this.borderlessChild == this )
{
assignInitialClientArea ( rect );
}
return rect;
}
/**
* Invoked from borderlessChild's override of getClientArea().
*/
void assignInitialClientArea ( final Rectangle rect )
{
if ( INITIAL_CLIENT_AREA_WORKAROUND && this.initialClientArea == null )
{
synchronized ( this )
{
if ( this.cachedSizesInitialized >= 1 )
{
rect.width = this.cachedPrefSize.width;
rect.height = this.cachedPrefSize.height;
}
}
// We don't want to clobber arbitrary Rectangle objects, only the
// one use by the SWT_AWT inner class.
final Exception e = new Exception ();
e.fillInStackTrace ();
final StackTraceElement[] stack = e.getStackTrace ();
if ( stack.length >= 3 && stack[2].getClassName ().startsWith ( "org.eclipse.swt.awt.SWT_AWT$" ) )
{
this.initialClientArea = rect;
}
}
}
// We have bidirectional size propagation, from AWT to SWT, and from
// SWT to AWT. To minimize pointless notification, we inhibit propagation
// in this situation:
// AWT rootpane.validate() ---> SWT layout() -|-> AWT frame.setBounds.
//
// When more than one SwingControl is involved, the situation is more
// complicated:
// AWT rootpane1.validate() ---> SWT layout() -|-> AWT frame1.setBounds.
// ---> AWT frame2.setBounds.
// The notification from SWT to the AWT frame that triggered the layout is
// inhibited. The notification from SWT to another AWT frame frame2 is
// passed through, however - except if frame2 has been validated since
// then. In the latter case, the SWT layout potentially used outdated
// sizes from frame2; it *must*not* clobber the new size of frame2.
//
// In order to determine the "since then", we need a clock that runs in the
// AWT thread.
/**
* The current "time" of the AWT thread. It is incremented when any
* SwingControl is validated. It's an integer modulo 2^32 (wraps around).
* Accessed only from the AWT thread. Therefore copies of this integer have
* to be compared with <code>a - b < 0</code> rather than <code>a < b</code>
* .
*/
private static int currentAWTTime;
/**
* The time at which this component's JRootPane was last validated. Accessed
* from the AWT thread and the SWT thread, therefore 'volatile' (could also
* be protected by a lock).
*/
volatile int lastValidatedAWTTime;
/**
* When running in the SWT thread on behalf of a notification from the AWT
* thread, this variable keeps track of the AWT-time that was in effect when
* this notification was sent. The map key is an SWT thread, belonging to a
* Display. There can be several of them, therefore a Map. Accessed only
* from the SWT thread(s).
*/
static Map /* <Thread,Integer> */onBehalfAWTTimes = Collections.synchronizedMap ( new HashMap /* <Thread,Integer> */() );
/**
* Given the minimum, preferred, and maximum sizes of the Swing component,
* this method stores them in the cache and updates this control
* accordingly.
*/
protected void updateCachedAWTSizes ( final Dimension min, final Dimension pref, final Dimension max )
{
assert EventQueue.isDispatchThread (); // On AWT event thread
if ( verboseSizeLayout )
{
System.err.println ( "AWT thread: updated component sizes: " + min + " <= " + pref + " <= " + max );
}
// Increment and memoize the current AWT time.
this.lastValidatedAWTTime = ++currentAWTTime;
boolean mustNotify;
synchronized ( this )
{
mustNotify = this.cachedSizesInitialized == 0;
if ( !mustNotify )
{
mustNotify = ! ( min.equals ( this.cachedMinSize ) && pref.equals ( this.cachedPrefSize ) && max.equals ( this.cachedMaxSize ) );
}
if ( this.cachedSizesInitialized == 0 )
{
this.cachedSizesInitialized = 1;
}
this.cachedMinSize = min;
this.cachedPrefSize = pref;
this.cachedMaxSize = max;
/**
* Part of a workaround, see {@link #getClientArea()}.
*/
if ( INITIAL_CLIENT_AREA_WORKAROUND && this.initialClientArea != null )
{
this.initialClientArea.width = this.cachedPrefSize.width;
this.initialClientArea.height = this.cachedPrefSize.height;
}
}
if ( mustNotify )
{
// Preferred (and min/max) sizes are available for the AWT
// component for the first time. Layout the composite so that those
// sizes can be taken into account.
final int onBehalfAWTTime = this.lastValidatedAWTTime;
ThreadingHandler.getInstance ().asyncExec ( this.display, new Runnable () {
public void run ()
{
if ( verboseSizeLayout )
{
System.err.println ( "AWT->SWT thread: Laying out after size update" );
}
if ( !isDisposed () )
{
try
{
onBehalfAWTTimes.put ( Thread.currentThread (), new Integer ( onBehalfAWTTime ) );
// Augment the three sizes by 2*borderWidth,
// avoiding
// integer overflow.
final Point minSize = new Point ( Math.min ( min.width, Integer.MAX_VALUE - 2 * SwingControl.this.borderWidth ) + 2 * SwingControl.this.borderWidth, Math.min ( min.height, Integer.MAX_VALUE - 2 * SwingControl.this.borderWidth ) + 2 * SwingControl.this.borderWidth );
final Point prefSize = new Point ( Math.min ( pref.width, Integer.MAX_VALUE - 2 * SwingControl.this.borderWidth ) + 2 * SwingControl.this.borderWidth, Math.min ( pref.height, Integer.MAX_VALUE - 2 * SwingControl.this.borderWidth ) + 2 * SwingControl.this.borderWidth );
final Point maxSize = new Point ( Math.min ( max.width, Integer.MAX_VALUE - 2 * SwingControl.this.borderWidth ) + 2 * SwingControl.this.borderWidth, Math.min ( max.height, Integer.MAX_VALUE - 2 * SwingControl.this.borderWidth ) + 2 * SwingControl.this.borderWidth );
// Augment the three sizes, avoiding integer
// overflow.
notePreferredSizeChanged ( minSize, prefSize, maxSize );
}
finally
{
onBehalfAWTTimes.remove ( Thread.currentThread () );
}
}
}
} );
}
}
/**
* Retrieves the minimum, preferred, and maximum sizes of the Swing
* component, if they are already available.
*
* @param min
* Output parameter for the Swing component's minimum size.
* @param pref
* Output parameter for the Swing component's preferred size.
* @param max
* Output parameter for the Swing component's maximum size.
* @return true if the sizes were available and the output parameters are
* filled
*/
protected boolean getCachedAWTSizes ( final Dimension min, final Dimension pref, final Dimension max )
{
synchronized ( this )
{
if ( this.cachedSizesInitialized >= 1 )
{
min.setSize ( this.cachedMinSize );
pref.setSize ( this.cachedPrefSize );
max.setSize ( this.cachedMaxSize );
return true;
}
else
{
return false;
}
}
}
/**
* True if setting the size of this control on the SWT side automatically
* resizes the frame and posts a COMPONENT_RESIZED event for the frame to
* the AWT event queue. On platforms where you are not sure, set this
* constant to false.
*/
static final boolean AUTOMATIC_SET_AWT_SIZE = Platform.isGtk () || Platform.isCarbon ();
/**
* This class represents a queue of requests to set the AWT frame's size.
* Multiple requests are automatically merged, by using the last among the
* specified sizes, and the OR of the preconditions.
*/
class SetAWTSizeQueue implements Runnable
{
// True while a request is pending.
private boolean pending /* = false */;
// If pending:
// The AWT time of the notification that triggered this request
// (null means unknown, i.e. execute the request unconditionally).
private Integer onBehalfAWTTime;
// If pending:
// The size to which the frame shall be resized.
private int width;
private int height;
// True while processing the last request (after it was dequeued).
private boolean processing /* = false */;
/**
* Creates an empty queue.
*/
SetAWTSizeQueue ()
{
this.pending = false;
}
/**
* Enqueues a request to this queue.
*
* @param onBehalfAWTTime
* The AWT time of the notification that triggered this
* request, or null.
* @param width
* The size to which the frame shall be resized.
* @param height
* The size to which the frame shall be resized.
* @return true if this queue needs to be started as a Runnable
*/
synchronized boolean enqueue ( Integer onBehalfAWTTime, final int width, final int height )
{
assert Display.getCurrent () != null; // On SWT event thread
if ( verboseSizeLayout )
{
System.err.println ( "SWT thread: Preparing to set size: " + width + " x " + height + " for " + SwingControl.this.swingComponent );
}
// During the processing of a request, lastValidatedAWTTime is
// unreliable: it gets incremented to a value past the
// currentAWTTime, but this does not mean that we should drop this
// request.
if ( this.processing )
{
onBehalfAWTTime = null;
}
final boolean wasPending = this.pending;
// Shortcut to avoid posting a Runnable that has no effect.
// (lastValidatedAWTTime can only increase until the Runnable is
// actually run.)
final boolean effective = onBehalfAWTTime == null || SwingControl.this.lastValidatedAWTTime - onBehalfAWTTime.intValue () < 0;
if ( wasPending || effective )
{
// Use the last specified size.
this.width = width;
this.height = height;
// Use the OR of the old onBehalfAWTTime and the new
// onBehalfAWTTime.
if ( wasPending )
{
this.onBehalfAWTTime = this.onBehalfAWTTime == null || onBehalfAWTTime == null ? null : this.onBehalfAWTTime.intValue () - onBehalfAWTTime.intValue () < 0 ? onBehalfAWTTime : this.onBehalfAWTTime;
}
else
{
this.onBehalfAWTTime = onBehalfAWTTime;
}
this.pending = true;
return !wasPending;
}
else
{
// Avoid posting a Runnable that has no effect.
return false;
}
}
/**
* Returns the enqueued request and removes it from the queue.
*
* @return The size to which the frame shall be resized, or null if if
* does not need to be resized after all.
*/
private synchronized Dimension dequeue ()
{
assert EventQueue.isDispatchThread (); // On AWT event thread
if ( this.pending )
{
this.pending = false;
// Compare the AWT time of the notification with the
// time at which the rootpane was last validated.
if ( this.onBehalfAWTTime == null || SwingControl.this.lastValidatedAWTTime - this.onBehalfAWTTime.intValue () < 0 )
{
this.processing = true;
return new Dimension ( this.width, this.height );
}
}
return null;
}
// Implementation of Runnable.
public void run ()
{
assert EventQueue.isDispatchThread (); // On AWT event thread
assert !this.processing;
for ( ;; )
{
final Dimension size = dequeue ();
if ( size == null )
{
break;
}
// Set the frame's (and thus also the rootpane's) size.
if ( verboseSizeLayout )
{
System.err.println ( "SWT->AWT thread: Setting size: " + size.width + " x " + size.height + " for " + SwingControl.this.swingComponent );
}
if ( SwingControl.this.frame != null )
{
SwingControl.this.frame.setBounds ( 0, 0, Math.max ( size.width, 0 ), Math.max ( size.height, 0 ) );
SwingControl.this.frame.validate ();
}
// Test if another request was enqueued (from the SWT thread)
// while we were processing this one.
synchronized ( this )
{
this.processing = false;
if ( !this.pending )
{
break;
}
}
// While this thread was resizing the frame, the SWT thread
// enqueued another request.
}
assert !this.processing;
}
}
private final SetAWTSizeQueue setAWTSizeQueue = new SetAWTSizeQueue ();
/**
* Propagate the width and height from SWT to the AWT frame. Only used if
* !AUTOMATIC_SET_AWT_SIZE.
*/
private void setAWTSize ( final int width, final int height )
{
assert Display.getCurrent () != null; // On SWT event thread
// Get the AWT time of the notification that triggered this processing.
final Integer onBehalfAWTTime = (Integer)onBehalfAWTTimes.get ( Thread.currentThread () );
if ( this.setAWTSizeQueue.enqueue ( onBehalfAWTTime, width, height ) )
{
// Switch to the AWT thread.
EventQueue.invokeLater ( this.setAWTSizeQueue );
}
}
/**
* Called when the SWT layout or client code assigns a size and position to
* this Control.
*/
protected void handleSetBounds ( final int width, final int height )
{
assert Display.getCurrent () != null; // On SWT event thread
populate ();
if ( verboseSizeLayout )
{
System.err.println ( "SWT thread: setBounds called: " + width + " x " + height );
}
// If the size of the frame automatically tracks the size on the SWT
// side,
// we don't need to do it explicitly.
// But it's nevertheless needed for the initial display sometimes, see
// below.
// TODO: research the initial content display problem further
if ( !AUTOMATIC_SET_AWT_SIZE || Platform.isGtk () && Platform.JAVA_VERSION < Platform.javaVersion ( 1, 6, 0 ) )
{
// Pass on the desired size to the embedded component, but only if
// it could
// be reasonably calculated (i.e. we have cached preferred sizes)
// and if
// computeSize took it into account.
// If, however, the SWT side specifies a size for the component
// while
// cachedSizesInitialized < 2 (i.e. usually, before the Swing
// component
// is created), it is tempting to store the size given here and use
// it
// when the Swing component is created later. But this leads to
// flickering: If the size could not been reasonably calculated or
// if
// computeSize did not take it into account, the values are always
// derived from the default size 64x64 of SWT Composites. Ignore
// these
// values then! It is better than to use them.
//
// Do not optimize with the cachedSizesInitialized flag on GTK/Java5
// or
// win32 since this may prevent the initial contents of the control
// from being displayed. Testcases: EmbeddedJTableView,
// TestResizeView.
// TODO: research the initial content display problem further
synchronized ( this )
{
if ( this.cachedSizesInitialized >= 2 || Platform.isGtk () && Platform.JAVA_VERSION < Platform.javaVersion ( 1, 6, 0 ) || Platform.isWin32 () )
{
setAWTSize ( Math.max ( width - 2 * this.borderWidth, 0 ), Math.max ( height - 2 * this.borderWidth, 0 ) );
}
}
}
}
// This is called by the layout when it wants to know the size preferences
// of this control.
/**
* {@inheritDoc}
* <p>
* Overridden to use the preferred, minimum, and maximum sizes of the
* embedded Swing component.
* <p>
* This method is part of the size propagation from AWT to SWT.
*/
@Override
public Point computeSize ( final int widthHint, final int heightHint, final boolean changed )
{
checkWidget ();
final Dimension min = new Dimension ();
final Dimension pref = new Dimension ();
final Dimension max = new Dimension ();
final boolean initialized = getCachedAWTSizes ( min, pref, max );
if ( !initialized )
{
if ( verboseSizeLayout )
{
System.err.println ( "SWT thread: Uninitialized AWT sizes for " + this.swingComponent );
}
return super.computeSize ( widthHint, heightHint, changed );
}
else
{
synchronized ( this )
{
assert this.cachedSizesInitialized >= 1;
this.cachedSizesInitialized = 2;
}
int width = widthHint == SWT.DEFAULT ? pref.width : widthHint < min.width ? min.width : widthHint > max.width ? max.width : widthHint;
// Augment by 2*borderWidth, avoiding integer overflow.
width = Math.min ( width, Integer.MAX_VALUE - 2 * this.borderWidth ) + 2 * this.borderWidth;
int height = heightHint == SWT.DEFAULT ? pref.height : heightHint < min.width ? min.height : heightHint > max.width ? max.height : heightHint;
// Augment by 2*borderWidth, avoiding integer overflow.
height = Math.min ( height, Integer.MAX_VALUE - 2 * this.borderWidth ) + 2 * this.borderWidth;
if ( verboseSizeLayout )
{
System.err.println ( "SWT thread: Computed size: " + width + " x " + height + " for " + this.swingComponent );
}
return new Point ( width, height );
}
}
/**
* Returns the uppermost parent of this control that is influenced by size
* changes of this control. It is usually on this ancestor control that you
* want to call <code>layout()</code> when the preferred size of this
* control has changed.
*
* @return the parent, grandparent, or other ancestor of this control, or
* <code>null</code>
* @see #preferredSizeChanged(Point, Point, Point)
*/
public abstract Composite getLayoutAncestor ();
/**
* Called when the preferred sizes of this control, as computed by AWT, have
* changed.
*/
/* private */void notePreferredSizeChanged ( final Point minSize, final Point prefSize, final Point maxSize )
{
preferredSizeChanged ( minSize, prefSize, maxSize );
firePreferredSizeChangedEvent ( minSize, prefSize, maxSize );
}
// TODO: remove this method and just leave the listener for advanced users?
/**
* Called when the preferred sizes of this control, as computed by AWT, have
* changed. This method should update the size of this SWT control and of
* other SWT controls in the window (as appropriate).
* <p>
* This method is a more flexible alternative to
* {@link #getLayoutAncestor()}. You should implement that method to return
* null if you are overriding this method. A still more flexible way to be
* informed of preferred size changes is to install a {@link SizeListener}
* with {@link #addSizeListener(SizeListener)}.
* <p>
* This method is part of the size propagation from AWT to SWT. It is called
* on the SWT event thread.
* <p>
* The default implementation of this method calls
* <code>getLayoutableAncestor().layout()</code>, if getLayoutableAncestor()
* returns a non-null. Otherwise, it does nothing.
* <p>
* The parameters <var>minPoint</var>, <var>prefPoint</var>,
* <var>maxPoint</var> can usually be ignored: It is often enough to rely on
* the {@link #layout()} method.
*
* @param minSize
* The new minimum size for this control, as reported by AWT,
* plus the border width on each side.
* @param prefSize
* The new preferred size for this control, as reported by AWT,
* plus the border width on each side.
* @param maxSize
* The new maximum size for this control, as reported by AWT,
* plus the border width on each side.
*/
protected void preferredSizeChanged ( final Point minSize, final Point prefSize, final Point maxSize )
{
final Composite ancestor = getLayoutAncestor ();
if ( ancestor != null )
{
// Not just ancestor.layout().
// It is important to tell the Layout that the preferences have
// changed. Objects such as org.eclipse.swt.layout.GridData (for
// GridLayout) cache the last width and height. We must flush this
// cached geometry.
ancestor.layout ( new Control[] { this.borderlessChild } );
}
}
// This is called by the layout when it assigns a size and position to this
// Control.
/**
* {@inheritDoc}
* <p>
* Overridden to propagate the size to the embedded Swing component.
* <p>
* This method is part of the size propagation from SWT to AWT.
*/
@Override
public void setBounds ( final Rectangle rect )
{
checkWidget ();
handleSetBounds ( rect.width, rect.height );
super.setBounds ( rect );
}
/**
* {@inheritDoc}
* <p>
* Overridden to propagate the size to the embedded Swing component.
* <p>
* This method is part of the size propagation from SWT to AWT.
*/
@Override
public void setBounds ( final int x, final int y, final int width, final int height )
{
checkWidget ();
handleSetBounds ( width, height );
super.setBounds ( x, y, width, height );
}
// ========================================================================
// Resizing with less flickering
// This listener clears garbage during resizing, making it look much
// cleaner. The garbage is due to use of the sun.awt.noerasebackground
// property, so this is done only under Windows.
private CleanResizeListener cleanResizeListener /* = null */;
/**
* Returns true if a particular mechanism for resizing with less flicker is
* enabled.
*/
public boolean isCleanResizeEnabled ()
{
return this.cleanResizeListener != null;
}
/**
* Specifies whether the particular mechanism for resizing with less flicker
* should be enabled or not.
* <p>
* For this setting to be useful, a background colour should have been � *
* set that approximately matches the window's contents.
* <p>
* By default, this setting is enabled on Windows with JDK 1.5 or older, and
* disabled otherwise.
*/
public void setCleanResizeEnabled ( final boolean enabled )
{
if ( enabled != isCleanResizeEnabled () )
{
if ( enabled )
{
this.cleanResizeListener = new CleanResizeListener ();
this.borderlessChild.addControlListener ( this.cleanResizeListener );
}
else
{
this.borderlessChild.removeControlListener ( this.cleanResizeListener );
this.cleanResizeListener = null;
}
}
}
private void initCleanResizeListener ()
{
// On Windows:
// - In JDK 1.4 and 1.5: It indeed avoids most of the "garbage". But
// if the background colour is not aligned with the contents of the
// window (like here: background grey, contents dark green), the
// cleaning is visually more disturbing than the garbage. This is
// especially noticeable when you click with the mouse in the above
// test view.
// - In JDK 1.6: There is much less "garbage"; the repaint is quicker.
// The CleanResizeListener's effect is mostly visible as flickering.
if ( Platform.isWin32 () && Platform.JAVA_VERSION < Platform.javaVersion ( 1, 6, 0 ) )
{
setCleanResizeEnabled ( true );
}
}
// ========================================================================
// Font management
/**
* {@inheritDoc}
* <p>
* Overridden to propagate the font to the embedded Swing component.
*/
@Override
public void setFont ( final Font font )
{
super.setFont ( font );
final FontData[] fontData = font.getFontData ();
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
setComponentFont ( font, fontData, false );
}
} );
}
private void updateDefaultFont ( final Font swtFont, final FontData[] swtFontData )
{
assert EventQueue.isDispatchThread (); // On AWT event thread
final java.awt.Font awtFont = LookAndFeelHandler.getInstance ().propagateSwtFont ( swtFont, swtFontData );
if ( awtFont == null )
{
return;
}
// Allow subclasses to react to font change if necessary.
updateAwtFont ( awtFont );
if ( this.swingComponent != null )
{
// Allow components to update their UI based on new font
// TODO: should the update method be called on the root pane
// instead?
final Container contentPane = this.swingComponent.getRootPane ().getContentPane ();
SwingUtilities.updateComponentTreeUI ( contentPane );
}
}
protected void setComponentFont ( final Font swtFont, final FontData[] swtFontData, final boolean preserveDefaults )
{
assert EventQueue.isDispatchThread ();
final ResourceConverter converter = ResourceConverter.getInstance ();
final java.awt.Font awtFont = converter.convertFont ( swtFont, swtFontData );
// Allow subclasses to react to font change if necessary.
updateAwtFont ( awtFont );
if ( this.rootPaneContainer != null )
{
final Container contentPane = this.rootPaneContainer.getContentPane ();
if ( !contentPane.getFont ().equals ( awtFont ) || !preserveDefaults )
{
contentPane.setFont ( awtFont );
}
}
}
/**
* 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.
* <p>
* This method is called from the AWT event thread.
* <p>
* 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 ( final java.awt.Font newFont )
{
// Do nothing by default; subclasses can override to insert behavior
}
private void handleSettingsChange ()
{
final Font newFont = getDisplay ().getSystemFont ();
final FontData[] newFontData = newFont.getFontData ();
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
updateDefaultFont ( newFont, newFontData );
}
} );
}
private void handleDispose ()
{
if ( this.focusHandler != null )
{
this.focusHandler.dispose ();
}
if ( this.borderlessChild != this )
{
this.borderlessChild.dispose ();
}
this.borderlessChild = null;
if ( this.frame != null )
{
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
SwingControl.this.frame.dispose ();
}
} );
}
this.display.removeListener ( SWT.Settings, this.settingsListener );
getShell ().removeControlListener ( this.shellControlListener );
}
// ============================= Painting =============================
/**
* {@inheritDoc}
* <p>
* Overridden to propagate the background color change to the embedded AWT
* component.
*/
@Override
public void setBackground ( final Color background )
{
super.setBackground ( background );
if ( this.rootPaneContainer != null )
{
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
setComponentBackground ( SwingControl.this.rootPaneContainer.getContentPane (), background, false );
}
} );
}
}
/**
* {@inheritDoc}
* <p>
* Overridden to propagate the foreground color change to the embedded AWT
* component.
*/
@Override
public void setForeground ( final Color foreground )
{
super.setForeground ( foreground );
if ( this.rootPaneContainer != null )
{
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
setComponentForeground ( SwingControl.this.rootPaneContainer.getContentPane (), foreground, false );
}
} );
}
}
protected void setComponentForeground ( final Component component, final Color foreground, final boolean preserveDefaults )
{
assert EventQueue.isDispatchThread ();
assert component != null;
LookAndFeelHandler.getInstance ().propagateSwtForeground ( component, foreground, preserveDefaults );
}
protected void setComponentBackground ( final Component component, final Color background, final boolean preserveDefaults )
{
assert EventQueue.isDispatchThread ();
assert component != null;
LookAndFeelHandler.getInstance ().propagateSwtBackground ( component, background, preserveDefaults );
}
// ============================= Focus Management
// =============================
private FocusHandler focusHandler;
private boolean isSwtTabOrderExtended = true;
private boolean isAWTPermanentFocusLossForced = true;
protected void initializeFocusManagement ()
{
assert this.frame != null;
assert Display.getCurrent () != null; // On SWT event thread
final GlobalFocusHandler handler = AwtEnvironment.getInstance ( this.display ).getGlobalFocusHandler ();
this.focusHandler = new FocusHandler ( this, handler, this.borderlessChild, this.frame );
}
/**
* Configures the SwingControl's participation in SWT traversals. See
* {@link #isSwtTabOrderExtended} for more information.
*
* @param isSwtTabOrderExtended
*/
public void setSwtTabOrderExtended ( final boolean isSwtTabOrderExtended )
{
this.isSwtTabOrderExtended = isSwtTabOrderExtended;
}
/**
* Returns whether the SWT tab order is configured to extend to the child
* Swing components inside this SwingControl.
* <p>
* If this method returns true, then when traversing (e.g. with the tab key)
* forward into this SwingControl, AWT focus will be set to the first
* component in the frame, as determined by the AWT
* {@link FocusTraversalPolicy}. Similarly, when traversing backward into
* this SwingControl, AWT focus will be set to the last component in the
* frame.
* <p>
* If this method returns false, then the SwingControl participates in the
* tab order only as a single opaque element. Focus on a child component
* will then be determined completely by the AWT focus subsystem,
* independent of any current SWT traversal state. This normally means that
* focus will move to the most recently focused Swing component within the
* embedded frame.
*
* @return true if the child componets are SWT traversal participants. false
* otherwise.
*/
public boolean isSwtTabOrderExtended ()
{
return this.isSwtTabOrderExtended;
}
/**
* Returns whether a permanent focus lost event is forced on a SwingControl
* when focus moves to another SWT component within the same shell. See
* {@link #setAWTPermanentFocusLossForced(boolean)} for more information.
*
* @return boolean
*/
public boolean isAWTPermanentFocusLossForced ()
{
return this.isAWTPermanentFocusLossForced;
}
/**
* Controls whether a permanent focus lost event is forced on a SwingControl
* when focus moves to another SWT component within the same shell.
* Normally, when an AWT frame loses focus to another window, it only
* receives a temporary focus lost event. This can cause unexpected results
* when the AWT window is embedded in a SWT shell and should really act more
* like a composite widget within that shell. If this property is set to
* <code>true</code> (the default), then permanent focus loss is
* synthesized.
* <p>
* For more information on permanent/temporary focus loss, see the <a href=
* "http://java.sun.com/j2se/1.4.2/docs/api/java/awt/doc-files/FocusSpec.html"
* >AWT Focus Subsystem</a> spec. For an example of the type of problem
* solved by keeping this property <code>true</code>, see <a
* href="http://bugs.eclipse.org/60967">bug 60967</a>.
*
* @param isAWTPermanentFocusLossForced
* - <code>true</code> to enable the forcing of permanent focus
* loss. <code>false</code> to disable it.
*/
public void setAWTPermanentFocusLossForced ( final boolean isAWTPermanentFocusLossForced )
{
this.isAWTPermanentFocusLossForced = isAWTPermanentFocusLossForced;
}
/**
* {@inheritDoc}
* <p>
* Overridden to return false and prevent any focus change if the embedded
* Swing component is not focusable.
* <p>
* Note: If the Swing component has not yet been created, then this method
* will temporarily set focus on its parent SWT {@link Composite}. After the
* Swing component is created, and if it is focusable, focus will be
* transferred to it.
*/
@Override
public boolean setFocus ()
{
checkWidget ();
if ( this.borderlessChild == this )
{
return handleFocusOperation ( new RunnableWithResult () {
@Override
public void run ()
{
final boolean success = isDisposed () ? false : superSetFocus ();
setResult ( new Boolean ( success ) );
}
} );
}
else if ( this.borderlessChild != null )
{
return this.borderlessChild.forceFocus ();
}
else
{
return false;
}
}
/**
* {@inheritDoc}
* <p>
* Overridden to return false and prevent any focus change if the embedded
* Swing component is not focusable.
* <p>
* Note: If the Swing component has not yet been created, then this method
* will temporarily set focus on its parent SWT {@link Composite}. After the
* Swing component is created, and if it is focusable, focus will be
* transferred to it.
*/
@Override
public boolean forceFocus ()
{
checkWidget ();
if ( this.borderlessChild == this )
{
return handleFocusOperation ( new RunnableWithResult () {
@Override
public void run ()
{
boolean success;
if ( isDisposed () )
{
success = false;
}
else
{
success = superForceFocus ();
// Handle the return value
success = postProcessForceFocus ( success );
}
setResult ( new Boolean ( success ) );
}
} );
}
else if ( this.borderlessChild != null )
{
return this.borderlessChild.forceFocus ();
}
else
{
return false;
}
}
/**
* Postprocess the super.forceFocus() result.
*/
protected boolean postProcessForceFocus ( boolean result )
{
if ( this.focusHandler != null )
{
result = this.focusHandler.handleForceFocus ( result );
}
return result;
}
/**
* Common focus setting/forcing code. Since this may be called for the
* SwingControl or a borderless child component, and it may be called for
* setting or forcing focus, the actual code to change focus is passed in a
* runnable
*
* @param focusSetter
* - invoked to set or force focus
* @return the result of running the focus setter, or true if it was
* deferred
*/
protected boolean handleFocusOperation ( final RunnableWithResult focusSetter )
{
assert Display.getCurrent () != null; // On SWT event thread
// TODO: find a reasonable way to return false when nothing is focusable
// in the swingComponent.
// It needs to be done without transferring to the AWT thread.
if ( this.swingComponent != null )
{
focusSetter.run ();
return ( (Boolean)focusSetter.getResult () ).booleanValue ();
}
else
{
// Fail if there is no underlying swing component
return false;
}
}
private boolean superSetFocus ()
{
return super.setFocus ();
}
private boolean superForceFocus ()
{
return super.forceFocus ();
}
// ============================= Events and Listeners
// =============================
private final List sizeListeners = new ArrayList ();
/**
* Adds the listener to the collection of listeners who will be notified
* when the embedded Swing control has changed its size preferences.
*
* @param listener
* the listener which should be notified
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*
* @see SizeListener
* @see #removeSizeListener(SizeListener)
*/
public void addSizeListener ( final SizeListener listener )
{
checkWidget ();
if ( listener == null )
{
SWT.error ( SWT.ERROR_NULL_ARGUMENT );
}
this.sizeListeners.add ( listener );
}
/**
* Removes the listener from the collection of listeners who will be
* notified when the embedded swing control has changed its size
* preferences.
*
* @param listener
* the listener which should no longer be notified
*
* @exception IllegalArgumentException
* <ul>
* <li>ERROR_NULL_ARGUMENT - if the listener is null</li>
* </ul>
* @exception SWTException
* <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been
* disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the
* thread that created the receiver</li>
* </ul>
*
* @see SizeListener
* @see #addSizeListener(SizeListener)
*/
public void removeSizeListener ( final SizeListener listener )
{
checkWidget ();
if ( listener == null )
{
SWT.error ( SWT.ERROR_NULL_ARGUMENT );
}
this.sizeListeners.remove ( listener );
}
protected void firePreferredSizeChangedEvent ( final Point minSize, final Point prefSize, final Point maxSize )
{
assert Display.getCurrent () != null; // On SWT event thread
final SizeEvent event = new SizeEvent ( this, minSize, prefSize, maxSize );
for ( final Iterator iterator = this.sizeListeners.iterator (); iterator.hasNext (); )
{
final SizeListener listener = (SizeListener)iterator.next ();
listener.preferredSizeChanged ( event );
}
}
// ==================== Swing Popup Management
// ================================
private static final boolean HIDE_SWING_POPUPS_ON_SWT_SHELL_BOUNDS_CHANGE = Platform.isWin32 (); // Win32: all JDKs
// Dismiss Swing popups when the main window is moved or resized. (It would
// be
// better to dismiss popups whenever the titlebar or trim is clicked, but
// there does not seem to be a way. This is the best we can do)
//
// This is particularly important when the Swing popup overlaps an edge of
// the
// containing SWT shell. If (on win32) the shell is moved, the overlapping
// popup will not move which looks very strange.
private final ControlListener shellControlListener = new ControlListener () {
public void controlMoved ( final ControlEvent e )
{
scheduleHide ();
}
public void controlResized ( final ControlEvent e )
{
scheduleHide ();
}
private void scheduleHide ()
{
EventQueue.invokeLater ( new Runnable () {
public void run ()
{
AwtEnvironment.getInstance ( SwingControl.this.display ).hidePopups ();
}
} );
}
};
// ============================= SWT Popup Management
// =============================
// ------------------------ Displaying a popup menu ------------------------
/**
* Returns the popup menu to be used on a given component.
* <p>
* The default implementation walks up the component hierarchy, looking for
* popup menus registered with {@link SwtPopupRegistry#setMenu} and as
* fallback at the popup menu registered on this <code>Control</code>.
* <p>
* This method can be overridden, to achieve dynamic popup menus.
*
* @param component
* The component on which a popup event was received.
* @param x
* The x coordinate, relative to the component's top left corner,
* of the mouse cursor when the event occurred.
* @param y
* The y coordinate, relative to the component's top left corner,
* of the mouse cursor when the event occurred.
* @param xAbsolute
* The x coordinate, relative to this control's top left corner,
* of the mouse cursor when the event occurred.
* @param yAbsolute
* The y coordinate, relative to this control's top left corner,
* of the mouse cursor when the event occurred.
*/
public Menu getMenu ( final java.awt.Component component, final int x, final int y, final int xAbsolute, final int yAbsolute )
{
checkWidget ();
Menu menu = SwtPopupRegistry.getInstance ().findMenu ( component, x, y, xAbsolute, yAbsolute );
if ( menu == null )
{
// Fallback: The menu set through the SWT API on this Control.
menu = getMenu ();
}
return menu;
}
protected void initPopupMenuSupport ( final javax.swing.JRootPane root )
{
SwtPopupHandler.getInstance ().monitorAwtComponent ( root );
}
@Override
public String toString ()
{
return super.toString () + " [frame=" + ( this.frame != null ? this.frame.getName () : "null" ) + "]";
}
// ============================= Keystroke Management
// =============================
Set consumedKeystrokes = new HashSet ();
/**
* Initializes keystroke management for this control.
*/
protected void initKeystrokeManagement ()
{
assert Display.getCurrent () != null; // On SWT event thread
// Platform-specific default consumed keystrokes
if ( Platform.isWin32 () )
{
// Shift-F10 is normally used to display a context popup menu.
// When this happens in Windows and inside of a Swing component,
// the consumption of the key is unknown to SWT. As a result,
// when SWT passes control to the default windows windowProc,
// Windows will handle the Shift-F10 like it handles F10 and Alt
// (alone); it will shift keyboard focus to the main menu bar.
// This will interfere with the Swing popup menu by removing
// its focus. Prevents the default windows behavior by
// consuming the released keystroke event for Shift-F10, so that
// the Swing context menu, if any. can be properly used.
//
// TODO: This is really l&f-dependent. Find a way to query the l&f
// for the popup key
addConsumedKeystroke ( new SwtKeystroke ( SWT.KeyUp, SWT.F10, SWT.SHIFT ) );
}
this.borderlessChild.addKeyListener ( new KeyListener () {
public void keyPressed ( final KeyEvent e )
{
handleKeyEvent ( SWT.KeyDown, e );
}
public void keyReleased ( final KeyEvent e )
{
handleKeyEvent ( SWT.KeyUp, e );
}
} );
}
protected void handleKeyEvent ( final int type, final KeyEvent e )
{
assert Display.getCurrent () != null; // On SWT event thread
final SwtKeystroke key = new SwtKeystroke ( type, e );
if ( this.consumedKeystrokes.contains ( key ) )
{
// System.err.println("Capturing key " + key);
e.doit = false;
}
}
/**
* Returns the set of keystrokes which will be consumed by this control. See
* {@link #addConsumedKeystroke(SwtKeystroke)} for more information.
*
* @return Set the keystrokes configured to be consumed.
*/
public Set getConsumedKeystrokes ()
{
checkWidget ();
return Collections.unmodifiableSet ( this.consumedKeystrokes );
}
/**
* Configures a SWT keystroke to be consumed automatically by this control
* whenever it is detected.
* <p>
* This method can be used to block a SWT keystroke from being propagated
* both to the embedded Swing component and to the native window system. By
* consuming a keystroke, you can avoid conflicts in key handling between
* the Swing component and the rest of the application.
*
* @param key
* the keystroke to consume.
*/
public void addConsumedKeystroke ( final SwtKeystroke key )
{
checkWidget ();
this.consumedKeystrokes.add ( key );
}
/**
* Removes a SWT keystroke from the set of keystrokes to be consumed by this
* control. See {@link #addConsumedKeystroke(SwtKeystroke)} for more
* information.
*
* @return <code>true</code> if a keystroke was successfully removed from
* the set.
*/
public boolean removeConsumedKeystroke ( final SwtKeystroke key )
{
checkWidget ();
return this.consumedKeystrokes.remove ( key );
}
// ============================= Post-first resize actions
// =============================
// Swing components created in createSwingComponent are not always
// initialized properly because the embedded frame does not have its
// bounds set early enough. This can happen when the
// component tries to do initialization with an invokeLater() call.
// In a normal Swing environment that would delay the initialization until
// after the frame and its child components had a real size, but not so in
// this
// environment. SWT_AWT sets the frame size inside an invokeLater, which
// is itself nested inside a syncExec. So the bounds can be set after any
// invokeLater() in called as part of createSwingComponent().
protected void initFirstResizeActions ()
{
this.frame.addComponentListener ( new ComponentAdapter () {
@Override
public void componentResized ( final ComponentEvent e )
{
scrollTextFields ( SwingControl.this.frame );
// We care about only the first resize
SwingControl.this.frame.removeComponentListener ( this );
}
} );
}
// Scroll all the text fields (JTextComponent) so that the caret will be
// visible
// when they are focused.
protected void scrollTextFields ( final Component c )
{
if ( c instanceof JTextComponent )
{
final JTextComponent tc = (JTextComponent)c;
if ( tc.getDocument () != null && tc.getDocument ().getLength () > 0 )
{
// Reset the caret position to force a scroll of
// the text component to the proper place
final int position = tc.getCaretPosition ();
final int tempPosition = position > 0 ? 0 : 1;
tc.setCaretPosition ( tempPosition );
tc.setCaretPosition ( position );
}
}
else if ( c instanceof Container )
{
final Component[] children = ( (Container)c ).getComponents ();
for ( int i = 0; i < children.length; i++ )
{
scrollTextFields ( children[i] );
}
}
}
}