/*******************************************************************************
 * 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 Listener settingsListener = new Listener() {
        public void handleEvent(Event event) {
            handleSettingsChange();
        }
    };
    final /*private*/ Display display;
    private Composite layoutDeferredAncestor;

    // The width of the border to keep around the embedded AWT frame.
    private 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(Composite parent, int style) {
        super(parent, style | ((style & SWT.BORDER) == 0 ? SWT.EMBEDDED : 0) | SWT.NO_BACKGROUND);
        setLayout(new FillLayout());
        display = getDisplay();

        display.addListener(SWT.Settings, 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.
        layoutDeferredAncestor = getLayoutAncestor();
        if (layoutDeferredAncestor != null) {
            layoutDeferredAncestor.setLayoutDeferred(true);
            // Ensure populate() is called nevertheless.
            ThreadingHandler.getInstance().asyncExec(
                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.
        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.
            borderlessChild =
                new Composite(this, (style & ~SWT.BORDER) | SWT.EMBEDDED | SWT.NO_BACKGROUND) {
                    /**
                     * Overridden.
                     */
                    public Rectangle getClientArea() {
                        assert Display.getCurrent() != null;     // On SWT event thread
                        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. 
                     */
                    public boolean forceFocus() {
                        checkWidget();
                        return handleFocusOperation(new RunnableWithResult() {
                            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. 
                     */
                    public boolean setFocus() {
                        checkWidget();
                        return handleFocusOperation(new RunnableWithResult() {
                            public void run() {
                                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 borderWidth == 0;
            borderlessChild = this;
        }

        // Clean up on dispose
        addListener(SWT.Dispose, new Listener() {
            public void handleEvent(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 (!populated) {
            populated = true;
            createFrame();
            scheduleComponentCreation();
        }
    }

    protected void createFrame() {
        assert Display.getCurrent() != null;     // On SWT event thread

        // Make sure Awt environment is initialized.
        AwtEnvironment.getInstance(display);

        frame = SWT_AWT.new_Frame(borderlessChild);

        if (verboseSizeLayout)
            ComponentDebugging.addComponentSizeDebugListeners(frame);

        initializeFocusManagement();
        initKeystrokeManagement();
        initFirstResizeActions();

        if (HIDE_SWING_POPUPS_ON_SWT_SHELL_BOUNDS_CHANGE) {
            getShell().addControlListener(shellControlListener);
        }
        
    }
    

    protected void scheduleComponentCreation() {
        assert 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?
                KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
                if (kfm.getDefaultFocusTraversalPolicy().getClass().getName()
                    == "javax.swing.LegacyGlueFocusTraversalPolicy")
                    kfm.setDefaultFocusTraversalPolicy(new LayoutFocusTraversalPolicy());
                if (frame.getFocusTraversalPolicy() != null
                    && frame.getFocusTraversalPolicy().getClass().getName()
                       == "javax.swing.LegacyGlueFocusTraversalPolicy")
                    frame.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy());

                rootPaneContainer = addRootPaneContainer(frame);
                initPopupMenuSupport(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(frame, background, true);

                swingComponent = createSwingComponent();
                if (swingComponent != null) {
                    // Pass on color and font values
                    // The color of the content Pane is visible permanently.
                    setComponentForeground(rootPaneContainer.getContentPane(), foreground, true);
                    setComponentBackground(rootPaneContainer.getContentPane(), background, true);
                    setComponentFont(font, fontData, true);
                    
                    rootPaneContainer.getRootPane().getContentPane().add(swingComponent);
                    swingComponent.putClientProperty(SWT_PARENT_PROPERTY_KEY, SwingControl.this);
                }
                
                // Invoke hooks, for use by the application.
                afterComponentCreatedAWTThread();
                try {
                    ThreadingHandler.getInstance().asyncExec(
                        display,
                        new Runnable() {
                            public void run() {

                                // Propagate focus to Swing, if necesssary
                                focusHandler.activateEmbeddedFrame();

                                // Now that the preferred size is known, enable
                                // the layout on the layoutable ancestor.
                                if ((layoutDeferredAncestor != null) && !layoutDeferredAncestor.isDisposed()) {
                                    layoutDeferredAncestor.layout();
                                    layoutDeferredAncestor.setLayoutDeferred(false);
                                }
                                // Invoke hooks, for use by the application.
                                afterComponentCreatedSWTThread();
                            }
                        });
                } catch (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(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.
        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.
        protected JRootPane createRootPane() {
            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.
        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 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 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).
     */
    public Rectangle getClientArea() {
        checkWidget();
        
        assert Display.getCurrent() != null;     // On SWT event thread
        Rectangle rect = super.getClientArea();
        if (borderlessChild == this)
            assignInitialClientArea(rect);
        return rect;
    }
    /**
     * Invoked from borderlessChild's override of getClientArea().
     */
    void assignInitialClientArea(Rectangle rect) {
        if (INITIAL_CLIENT_AREA_WORKAROUND && initialClientArea == null) {
            synchronized (this) {
                if (cachedSizesInitialized >= 1) {
                    rect.width = cachedPrefSize.width;
                    rect.height = cachedPrefSize.height;
                }
            }
            // We don't want to clobber arbitrary Rectangle objects, only the
            // one use by the SWT_AWT inner class.
            Exception e = new Exception();
            e.fillInStackTrace();
            StackTraceElement[] stack = e.getStackTrace();
            if (stack.length >= 3
                && stack[2].getClassName().startsWith("org.eclipse.swt.awt.SWT_AWT$")) {
                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.
        lastValidatedAWTTime = ++currentAWTTime;

        boolean mustNotify;

        synchronized (this) {
            mustNotify = (cachedSizesInitialized == 0);
            if (!mustNotify) {
                mustNotify = !(min.equals(cachedMinSize)
                               && pref.equals(cachedPrefSize)
                               && max.equals(cachedMaxSize));
            }
            if (cachedSizesInitialized == 0)
              cachedSizesInitialized = 1;
            cachedMinSize = min;
            cachedPrefSize = pref;
            cachedMaxSize = max;

            /**
             * Part of a workaround, see {@link #getClientArea()}.
             */
            if (INITIAL_CLIENT_AREA_WORKAROUND && initialClientArea != null) {
                initialClientArea.width = cachedPrefSize.width;
                initialClientArea.height = 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 = lastValidatedAWTTime;
            ThreadingHandler.getInstance().asyncExec(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.
                            Point minSize =
                                new Point(Math.min(min.width, Integer.MAX_VALUE-2*borderWidth) + 2*borderWidth,
                                          Math.min(min.height, Integer.MAX_VALUE-2*borderWidth) + 2*borderWidth);
                            Point prefSize =
                                new Point(Math.min(pref.width, Integer.MAX_VALUE-2*borderWidth) + 2*borderWidth,
                                          Math.min(pref.height, Integer.MAX_VALUE-2*borderWidth) + 2*borderWidth);
                            Point maxSize =
                                new Point(Math.min(max.width, Integer.MAX_VALUE-2*borderWidth) + 2*borderWidth,
                                          Math.min(max.height, Integer.MAX_VALUE-2*borderWidth) + 2*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(Dimension min, Dimension pref, Dimension max) {
        synchronized(this) {
            if (cachedSizesInitialized >= 1) {
                min.setSize(cachedMinSize);
                pref.setSize(cachedPrefSize);
                max.setSize(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() {
            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, int width, 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 " + 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 (processing)
                onBehalfAWTTime = null;
            boolean wasPending = this.pending;
            // Shortcut to avoid posting a Runnable that has no effect.
            // (lastValidatedAWTTime can only increase until the Runnable is
            // actually run.)
            boolean effective = (onBehalfAWTTime == null
                                 || 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 (pending) {
                pending = false;
                // Compare the AWT time of the notification with the
                // time at which the rootpane was last validated.
                if (onBehalfAWTTime == null
                    || lastValidatedAWTTime - onBehalfAWTTime.intValue() < 0) {
                    processing = true;
                    return new Dimension(width, height);
                }
            }
            return null;
        }
        // Implementation of Runnable.
        public void run() {
            assert EventQueue.isDispatchThread();    // On AWT event thread
            assert !processing;
            for (;;) {
                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 " + swingComponent);
                if (frame != null) {
                    frame.setBounds(0, 0, Math.max(size.width, 0), Math.max(size.height, 0));
                    frame.validate();
                }
                // Test if another request was enqueued (from the SWT thread)
                // while we were processing this one.
                synchronized(this) {
                    processing = false;
                    if (!pending)
                        break;
                }
                // While this thread was resizing the frame, the SWT thread
                // enqueued another request.
            }
            assert !processing;
        }
    }
    private 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(int width, 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 (setAWTSizeQueue.enqueue(onBehalfAWTTime, width, height)) {
            // Switch to the AWT thread.
            EventQueue.invokeLater(setAWTSizeQueue);
        }
    }

    /**
     * Called when the SWT layout or client code assigns a size and position to this Control.
     */
    protected void handleSetBounds(int width, 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 ((cachedSizesInitialized >= 2) || 
                        (Platform.isGtk() && Platform.JAVA_VERSION < Platform.javaVersion(1, 6, 0)) ||
                        Platform.isWin32()) {
                    setAWTSize(Math.max(width-2*borderWidth, 0),
                               Math.max(height-2*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.
     */
    public Point computeSize(int widthHint, int heightHint, boolean changed) {
        checkWidget();

        Dimension min = new Dimension();
        Dimension pref = new Dimension();
        Dimension max = new Dimension();
        boolean initialized = getCachedAWTSizes(min, pref, max);

        if (!initialized) {
            if (verboseSizeLayout)
                System.err.println("SWT thread: Uninitialized AWT sizes for " + swingComponent);
            return super.computeSize(widthHint, heightHint, changed);
        } else {
            synchronized (this) {
                assert cachedSizesInitialized >= 1;
                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*borderWidth) + 2*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*borderWidth) + 2*borderWidth;
            if (verboseSizeLayout)
                System.err.println("SWT thread: Computed size: " + width + " x " + height + " for " + 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(Point minSize, Point prefSize, 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(Point minSize, Point prefSize, Point maxSize) {
        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[] { 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.
     */
    public void setBounds(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.
     */
    public void setBounds(int x, int y, int width, 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 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(boolean enabled) {
        if (enabled != isCleanResizeEnabled()) {
            if (enabled) {
                cleanResizeListener = new CleanResizeListener();
                borderlessChild.addControlListener(cleanResizeListener);
            } else {
                borderlessChild.removeControlListener(cleanResizeListener);
                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.
     */
    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(Font swtFont, FontData[] swtFontData) {
        assert EventQueue.isDispatchThread();    // On AWT event thread

        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 (swingComponent != null) {
            // Allow components to update their UI based on new font
            // TODO: should the update method be called on the root pane instead?
            Container contentPane = swingComponent.getRootPane().getContentPane();
            SwingUtilities.updateComponentTreeUI(contentPane);
        }
    }

    protected void setComponentFont(Font swtFont, FontData[] swtFontData, boolean preserveDefaults) {
        assert EventQueue.isDispatchThread();

        ResourceConverter converter = ResourceConverter.getInstance();
        java.awt.Font awtFont = converter.convertFont(swtFont, swtFontData);

        // Allow subclasses to react to font change if necessary.
        updateAwtFont(awtFont);

        if (rootPaneContainer != null) {
            Container contentPane = 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(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 (focusHandler != null)
            focusHandler.dispose();
        if (borderlessChild != this)
            borderlessChild.dispose();
        display.removeListener(SWT.Settings, settingsListener);
    }

    // ============================= Painting =============================

    /**
     * {@inheritDoc}
     * <p>
     * Overridden to propagate the background color change to the embedded AWT
     * component.
     */
    public void setBackground(final Color background) {
        super.setBackground(background);

        if (rootPaneContainer != null) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    setComponentBackground(rootPaneContainer.getContentPane(), background, false);
                }
            });
        }
    }

    /**
     * {@inheritDoc}
     * <p>
     * Overridden to propagate the foreground color change to the embedded AWT
     * component.
     */
    public void setForeground(final Color foreground) {
        super.setForeground(foreground);

        if (rootPaneContainer != null) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    setComponentForeground(rootPaneContainer.getContentPane(), foreground, false);
                }
            });
        }
    }

    protected void setComponentForeground(Component component, Color foreground, boolean preserveDefaults) {
        assert EventQueue.isDispatchThread();
        assert component != null;

        LookAndFeelHandler.getInstance().propagateSwtForeground(component, foreground, preserveDefaults);
    }

    protected void setComponentBackground(Component component, Color background, 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 frame != null;
        assert Display.getCurrent() != null;     // On SWT event thread
        
        GlobalFocusHandler handler = AwtEnvironment.getInstance(display).getGlobalFocusHandler();
        focusHandler = new FocusHandler(this, handler, borderlessChild, frame);   
    }
    
    
    /**
     * Configures the SwingControl's participation in SWT traversals. See {@link #isSwtTabOrderExtended} 
     * for more information. 
     * 
     * @param isSwtTabOrderExtended
     */
    public void setSwtTabOrderExtended(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 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 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(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.   
     */
    public boolean setFocus() {
        checkWidget();
        
        if (borderlessChild == this) {
            return handleFocusOperation(new RunnableWithResult() {
                public void run() {
                    boolean success = isDisposed() ? false : superSetFocus();
                    setResult(new Boolean(success));
                }
            });
        } else {
            return borderlessChild.setFocus();
        }
    }

    /** 
     * {@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.   
     */
    public boolean forceFocus() {
        checkWidget();

        if (borderlessChild == this) {
            return handleFocusOperation(new RunnableWithResult() {
                public void run() {
                    boolean success;
                    if (isDisposed()) {
                        success = false;
                    } else {
                        success = superForceFocus();
                        // Handle the return value
                        success = postProcessForceFocus(success);
                    }
                    setResult(new Boolean(success));
                }
            });
        } else {
            return borderlessChild.forceFocus();
        }
        
    }

    /**
     * Postprocess the super.forceFocus() result.
     */
    protected boolean postProcessForceFocus(boolean result) {
        if (focusHandler != null) {
            result = 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 (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 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(SizeListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        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(SizeListener listener) {
        checkWidget();
        if (listener == null) {
            SWT.error(SWT.ERROR_NULL_ARGUMENT);
        }
        sizeListeners.remove(listener);
    }

    protected void firePreferredSizeChangedEvent(Point minSize, Point prefSize, Point maxSize) {
        assert Display.getCurrent() != null;     // On SWT event thread
        SizeEvent event = new SizeEvent(this, minSize, prefSize, maxSize);
        for (Iterator iterator = sizeListeners.iterator(); iterator.hasNext();) {
            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(ControlEvent e) {
            scheduleHide();
        }

        public void controlResized(ControlEvent e) {
            scheduleHide();
        }

        private void scheduleHide() {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    AwtEnvironment.getInstance(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(java.awt.Component component, int x, int y, int xAbsolute, 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(javax.swing.JRootPane root) {
        SwtPopupHandler.getInstance().monitorAwtComponent(root);
    }

    public String toString() {
        return super.toString() + " [frame=" + ((frame != null) ? 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));
        }
        
        borderlessChild.addKeyListener(new KeyListener() {
            public void keyPressed(KeyEvent e) {
                handleKeyEvent(SWT.KeyDown, e);
            }
            public void keyReleased(KeyEvent e) {
                handleKeyEvent(SWT.KeyUp, e);
            }
        });

    }
    
    protected void handleKeyEvent(int type, KeyEvent e) {
        assert Display.getCurrent() != null;     // On SWT event thread
        SwtKeystroke key = new SwtKeystroke(type, e);
        if (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(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(SwtKeystroke key) {
        checkWidget();
        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(SwtKeystroke key) {
        checkWidget();
        return 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() {
        frame.addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent e) {
                scrollTextFields(frame);
                // We care about only the first resize
                frame.removeComponentListener(this);
            }
        });
    }

    // Scroll all the text fields (JTextComponent) so that the caret will be visible
    // when they are focused. 
    protected void scrollTextFields(Component c) {
        if (c instanceof JTextComponent) {
            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
                int position = tc.getCaretPosition();
                int tempPosition = (position > 0) ? 0 : 1; 
                tc.setCaretPosition(tempPosition);
                tc.setCaretPosition(position);
            }
        } else if (c instanceof Container) {
            Component[] children = ((Container)c).getComponents();
            for (int i = 0; i < children.length; i++) {
                scrollTextFields(children[i]);
            }
        }
    }
}
