]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/chassis/AWTChassis.java
Fixed all line endings of the repository
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / chassis / AWTChassis.java
index 49c020bce6b87586e0a84ebfc9d187d7247e9c35..17304ef3e548da3642672c21993c6f4e4042e6c8 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- *     VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.g2d.chassis;\r
-\r
-import java.awt.Color;\r
-import java.awt.Container;\r
-import java.awt.Cursor;\r
-import java.awt.Graphics;\r
-import java.awt.Graphics2D;\r
-import java.awt.Rectangle;\r
-import java.awt.image.VolatileImage;\r
-import java.lang.reflect.Method;\r
-\r
-import javax.swing.JComponent;\r
-import javax.swing.RepaintManager;\r
-\r
-import org.simantics.g2d.canvas.ICanvasContext;\r
-import org.simantics.g2d.canvas.IContentContext;\r
-import org.simantics.g2d.canvas.IContentContext.IContentListener;\r
-import org.simantics.g2d.canvas.IMouseCursorContext;\r
-import org.simantics.g2d.canvas.IMouseCursorListener;\r
-import org.simantics.g2d.dnd.DragInteractor;\r
-import org.simantics.g2d.dnd.DropInteractor;\r
-import org.simantics.g2d.internal.DebugPolicy;\r
-import org.simantics.scenegraph.g2d.G2DRenderingHints;\r
-import org.simantics.scenegraph.g2d.events.Event;\r
-import org.simantics.scenegraph.g2d.events.IEventQueue;\r
-import org.simantics.scenegraph.g2d.events.IEventQueue.IEventQueueListener;\r
-import org.simantics.scenegraph.g2d.events.adapter.AWTFocusAdapter;\r
-import org.simantics.scenegraph.g2d.events.adapter.AWTKeyEventAdapter;\r
-import org.simantics.scenegraph.g2d.events.adapter.AWTMouseEventAdapter;\r
-import org.simantics.utils.datastructures.hints.HintContext;\r
-import org.simantics.utils.datastructures.hints.IHintContext;\r
-import org.simantics.utils.threads.AWTThread;\r
-import org.simantics.utils.threads.Executable;\r
-import org.simantics.utils.threads.IThreadWorkQueue;\r
-import org.simantics.utils.threads.SyncListenerList;\r
-import org.simantics.utils.threads.ThreadUtils;\r
-\r
-/**\r
- * Swing component chassis for a canvas context.\r
- *\r
- * @author Toni Kalajainen\r
- */\r
-public class AWTChassis extends JComponent implements ICanvasChassis {\r
-\r
-//    private static final String PROBLEM = "Encountered VolatileImage issue, please reopen editor.";\r
-\r
-    private static final long serialVersionUID = 1L;\r
-\r
-    /** Hint context */\r
-    protected IHintContext                       hintCtx          = new HintContext();\r
-\r
-    protected SyncListenerList<IChassisListener> listeners        = new SyncListenerList<IChassisListener>(IChassisListener.class);\r
-    protected DropInteractor                     drop;\r
-    protected DragInteractor                     drag;\r
-\r
-    AWTMouseEventAdapter                         mouseAdapter;\r
-    AWTKeyEventAdapter                           keyAdapter;\r
-    AWTFocusAdapter                              focusAdapter;\r
-\r
-    Container holder = null; // Holder for possible swing components. Scenegraph handles event and painting, thus the size of the holder should be 0x0\r
-\r
-    IMouseCursorListener cursorListener = new IMouseCursorListener() {\r
-        @Override\r
-        public void onCursorSet(IMouseCursorContext sender, int mouseId, Cursor cursor) {\r
-            if (mouseId==0) setCursor(cursor);\r
-        }\r
-    };\r
-\r
-    private transient boolean                    dirty            = false;\r
-    private transient boolean                    closed           = false;\r
-    protected ICanvasContext                     canvasContext;\r
-\r
-    // Marks the content dirty\r
-    protected IContentListener contentListener = new IContentListener() {\r
-        @Override\r
-        public void onDirty(IContentContext sender) {\r
-            dirty = true;\r
-            ICanvasContext ctx = canvasContext;\r
-            if (ctx==null) return;\r
-            if (ctx.getEventQueue().isEmpty())\r
-            {\r
-                RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);\r
-                rm.markCompletelyDirty(AWTChassis.this);\r
-            }\r
-        }\r
-    };\r
-\r
-    // Paints dirty contents after all events are handled\r
-    protected IEventQueueListener queueListener = new IEventQueueListener() {\r
-        @Override\r
-        public void onEventAdded(IEventQueue queue, Event e, int index) {\r
-        }\r
-        @Override\r
-        public void onQueueEmpty(IEventQueue queue) {\r
-            if (dirty) {\r
-                RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);\r
-                rm.markCompletelyDirty(AWTChassis.this);\r
-            }\r
-        }\r
-    };\r
-\r
-    protected boolean hookKeyEvents;\r
-\r
-    /**\r
-     * Background buffer for rendered canvas. Allocated on-demand in\r
-     * {@link #paintComponent(Graphics)}. Nullified by\r
-     * {@link #setCanvasContext(ICanvasContext)} when canvas context is null to\r
-     * prevent leakage.\r
-     */\r
-    private VolatileImage buffer = null;\r
-//  private final int imageNo = 0;\r
-\r
-\r
-    public AWTChassis() {\r
-        this(true);\r
-    }\r
-\r
-    public AWTChassis(boolean hookKeyEvents) {\r
-        super();\r
-        setFocusable(true);\r
-        this.hookKeyEvents = hookKeyEvents;\r
-        this.setDoubleBuffered(false);\r
-        this.setOpaque(true);\r
-        this.setBackground(Color.WHITE);\r
-    }\r
-\r
-    @Override\r
-    public void setCanvasContext(final ICanvasContext canvasContext) {\r
-        // FIXME: this should be true but is currently not.\r
-        //assert AWTThread.getThreadAccess().currentThreadAccess();\r
-        if (this.canvasContext == canvasContext) return;\r
-        // Unhook old context\r
-        if (this.canvasContext!=null) {\r
-            this.canvasContext.getHintStack().removeHintContext(hintCtx);\r
-            this.canvasContext.getContentContext().removePaintableContextListener(contentListener);\r
-            this.canvasContext.getEventQueue().removeQueueListener(queueListener);\r
-            this.canvasContext.getMouseCursorContext().removeCursorListener(cursorListener);\r
-            this.canvasContext.remove(drop);\r
-            this.canvasContext.remove(drag);\r
-            removeMouseListener(mouseAdapter);\r
-            removeMouseMotionListener(mouseAdapter);\r
-            removeMouseWheelListener(mouseAdapter);\r
-            if (hookKeyEvents)\r
-                removeKeyListener(keyAdapter);\r
-            removeFocusListener(focusAdapter);\r
-\r
-            // SceneGraph event handling\r
-            removeMouseListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
-            removeMouseMotionListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
-            removeMouseWheelListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
-            removeKeyListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
-            removeFocusListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
-            this.canvasContext.setTooltipProvider( null );\r
-\r
-            this.canvasContext = null;\r
-            this.focusAdapter = null;\r
-            this.mouseAdapter = null;\r
-            this.keyAdapter = null;\r
-            this.drop = null;\r
-            this.drag = null;\r
-            this.holder = null;\r
-            removeAll();\r
-            \r
-        }\r
-        this.canvasContext = canvasContext;\r
-        // Hook new context\r
-        if (canvasContext!=null) {\r
-\r
-            // SceneGraph event handling\r
-            addMouseListener(canvasContext.getSceneGraph().getEventDelegator());\r
-            addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator());\r
-            addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator());\r
-            addKeyListener(canvasContext.getSceneGraph().getEventDelegator());\r
-            addFocusListener(canvasContext.getSceneGraph().getEventDelegator());\r
-            canvasContext.setTooltipProvider( new AWTTooltipProvider() );\r
-\r
-            //Create canvas context and a layer of interactors\r
-            canvasContext.getHintStack().addHintContext(hintCtx, 0);\r
-            canvasContext.getContentContext().addPaintableContextListener(contentListener);\r
-            canvasContext.getEventQueue().addQueueListener(queueListener);\r
-            canvasContext.getMouseCursorContext().addCursorListener(cursorListener);\r
-\r
-            mouseAdapter = new AWTMouseEventAdapter(canvasContext, canvasContext.getEventQueue());\r
-            if (hookKeyEvents) {\r
-                keyAdapter = new AWTKeyEventAdapter(canvasContext, canvasContext.getEventQueue());\r
-                addKeyListener(keyAdapter);\r
-            }\r
-            focusAdapter = new AWTFocusAdapter(canvasContext, canvasContext.getEventQueue());\r
-            addMouseListener(mouseAdapter);\r
-            addMouseMotionListener(mouseAdapter);\r
-            addMouseWheelListener(mouseAdapter);\r
-            addFocusListener(focusAdapter);\r
-\r
-            canvasContext.add( drag=new DragInteractor(this) );\r
-            canvasContext.add( drop=new DropInteractor(this) );\r
-\r
-            // FIXME: hack to work around this:\r
-            // FIXME: this should be true but is currently not.\r
-            //assert AWTThread.getThreadAccess().currentThreadAccess();\r
-            Runnable initializeHolder = new Runnable() {\r
-                @Override\r
-                public void run() {\r
-                    if (canvasContext.isDisposed())\r
-                        return;\r
-                    if (holder == null) {\r
-                        holder = new Holder();\r
-//                        holder.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));\r
-                        holder.setSize(1, 1);\r
-                        holder.setLocation(0, 0);\r
-                        holder.setFocusable(false);\r
-                        holder.setEnabled(true);\r
-                        holder.setVisible(true);\r
-                        holder.addMouseListener(canvasContext.getSceneGraph().getEventDelegator());\r
-                        holder.addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator());\r
-                        holder.addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator());\r
-                        holder.addKeyListener(canvasContext.getSceneGraph().getEventDelegator());\r
-                        holder.addFocusListener(canvasContext.getSceneGraph().getEventDelegator());\r
-                        //System.err.println("layout: " + holder.getLayout());\r
-                        AWTChassis.this.add(holder);\r
-                    }\r
-\r
-                    // Use dummy holder as root pane. Swing components need a root pane, but we don't want swing to handle painting..\r
-                    canvasContext.getSceneGraph().setRootPane(holder, AWTChassis.this);\r
-\r
-                    holder.addMouseListener(mouseAdapter);\r
-                    holder.addMouseMotionListener(mouseAdapter);\r
-                    holder.addMouseWheelListener(mouseAdapter);\r
-                    holder.addFocusListener(focusAdapter);\r
-                }\r
-            };\r
-            if (AWTThread.getThreadAccess().currentThreadAccess())\r
-                initializeHolder.run();\r
-            else\r
-                AWTThread.getThreadAccess().asyncExec(initializeHolder);\r
-        }\r
-        buffer = null;\r
-        repaint();\r
-    }\r
-\r
-    @Override\r
-    public ICanvasContext getCanvasContext() {\r
-        return canvasContext;\r
-    }\r
-\r
-    @Override\r
-    public void addChassisListener(IThreadWorkQueue thread, IChassisListener listener) {\r
-        listeners.add(thread, listener);\r
-    }\r
-\r
-    @Override\r
-    public void removeChassisListener(IThreadWorkQueue thread, IChassisListener listener) {\r
-        listeners.remove(thread, listener);\r
-    }\r
-\r
-    @Override\r
-    public void addChassisListener(IChassisListener listener) {\r
-        listeners.add(listener);\r
-    }\r
-\r
-    @Override\r
-    public void removeChassisListener(IChassisListener listener) {\r
-        listeners.remove(listener);\r
-    }\r
-\r
-    @Override\r
-    public IHintContext getHintContext() {\r
-        return hintCtx;\r
-    }\r
-\r
-    private void paintScenegraph(Graphics2D g2d, Rectangle controlBounds) {\r
-        Color bg = getBackground();\r
-        if (bg == null)\r
-            bg = Color.WHITE;\r
-        g2d.setBackground(bg);\r
-        g2d.clearRect(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height);\r
-        g2d.setClip(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height);\r
-        g2d.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, controlBounds);\r
-        if (!canvasContext.isLocked())\r
-            canvasContext.getSceneGraph().render(g2d);\r
-    }\r
-\r
-    /**\r
-     * Perform a best effort to render the current scene graph into a\r
-     * VolatileImage compatible with this chassis.\r
-     * \r
-     * @param g2d\r
-     * @param b\r
-     * @return the VolatileImage rendered if successful, <code>null</code> if\r
-     *         rendering fails\r
-     */\r
-    private VolatileImage paintToVolatileImage(Graphics2D g2d, Rectangle b) {\r
-        int attempts = 0;\r
-        do {\r
-            // Prevent eternal looping experienced by Kalle.\r
-            // Caused by buffer.contentsLost failing constantly\r
-            // for the buffer created by createVolatileImage(w, h) below.\r
-            // This happens often with the following sequence:\r
-            //   - two displays, application window on the second display\r
-            //   - lock windows, (possibly wait a while/get coffee)\r
-            //   - unlock windows\r
-            // Also using an attempt count to let the code fall back to a\r
-            // slower rendering path if volatile images are causing trouble.\r
-            if (closed || attempts >= 10)\r
-                return null;\r
-\r
-            if (buffer == null\r
-                    || b.width != buffer.getWidth()\r
-                    || b.height != buffer.getHeight()\r
-                    || buffer.validate(g2d.getDeviceConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE)\r
-            {\r
-               \r
-                buffer = createVolatileImage(b.width, b.height);\r
-            }\r
-            if (buffer == null)\r
-                return null;\r
-\r
-//            ImageCapabilities caps = g2d.getDeviceConfiguration().getImageCapabilities();\r
-//            System.out.println("CAPS: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile());\r
-//            caps = buffer.getCapabilities();\r
-//            System.out.println("CAPS2: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile());\r
-\r
-            Graphics2D bg = buffer.createGraphics();\r
-            paintScenegraph(bg, b);\r
-            bg.dispose();\r
-\r
-            ++attempts;\r
-        } while (buffer.contentsLost());\r
-\r
-        // Successfully rendered to buffer!\r
-        return buffer;\r
-    }\r
-\r
-    @Override\r
-    public void paintComponent(Graphics g) {\r
-        dirty = false;\r
-        if (canvasContext == null)\r
-            return;\r
-\r
-        Graphics2D g2d = (Graphics2D) g;\r
-        Rectangle b = getBounds();\r
-\r
-        long startmem = 0, start = 0;\r
-        if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {\r
-            startmem = Runtime.getRuntime().freeMemory();\r
-            start = System.nanoTime();\r
-        }\r
-        VolatileImage buffer = paintToVolatileImage(g2d, b);\r
-        if (closed)\r
-            return;\r
-        if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {\r
-            long end = System.nanoTime();\r
-            long endmem = Runtime.getRuntime().freeMemory();\r
-            System.out.println("frame render: " + ((end-start)*1e-6) + " ms, " + (startmem-endmem)/(1024.0*1024.0) + " MB");\r
-        }\r
-\r
-        if (buffer != null) {\r
-            // Successfully buffered the scenegraph, copy the image to screen.\r
-\r
-            // DEBUG\r
-//          try {\r
-//              File frame = new File("d:/frames/frame" + (++imageNo) + ".png");\r
-//              System.out.println("writing frame: " + frame);\r
-//              ImageIO.write(buffer.getSnapshot(), "PNG", frame);\r
-//          } catch (IOException e) {\r
-//              e.printStackTrace();\r
-//          }\r
-\r
-            g2d.drawImage(buffer, 0, 0, null);\r
-        } else {\r
-            // Failed to paint volatile image, paint directly to the provided\r
-            // graphics context.\r
-            paintScenegraph(g2d, b);\r
-\r
-//            g2d.setFont(Font.getFont("Arial 14"));\r
-//            g2d.setColor(Color.RED);\r
-//            FontMetrics fm = g2d.getFontMetrics();\r
-//            Rectangle2D r = fm.getStringBounds(PROBLEM, g2d);\r
-//            g2d.drawString(PROBLEM, (int) (b.width-r.getWidth()), (int) (b.getHeight()-fm.getMaxDescent()));\r
-        }\r
-    }\r
-\r
-    private final static Method CLOSED_METHOD = SyncListenerList.getMethod(IChassisListener.class, "chassisClosed");\r
-    protected void fireChassisClosed()\r
-    {\r
-        closed = true;\r
-        Executable e[] = listeners.getExecutables(CLOSED_METHOD, this);\r
-        ThreadUtils.multiSyncExec(e);\r
-    }\r
-    \r
-    @Override\r
-       public boolean contains(int x, int y) {\r
-               // TODO Auto-generated method stub\r
-               return super.contains(x, y);\r
-       }\r
-    \r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.g2d.chassis;
+
+import java.awt.Color;
+import java.awt.Container;
+import java.awt.Cursor;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.image.VolatileImage;
+import java.lang.reflect.Method;
+
+import javax.swing.JComponent;
+import javax.swing.RepaintManager;
+
+import org.simantics.g2d.canvas.ICanvasContext;
+import org.simantics.g2d.canvas.IContentContext;
+import org.simantics.g2d.canvas.IContentContext.IContentListener;
+import org.simantics.g2d.canvas.IMouseCursorContext;
+import org.simantics.g2d.canvas.IMouseCursorListener;
+import org.simantics.g2d.dnd.DragInteractor;
+import org.simantics.g2d.dnd.DropInteractor;
+import org.simantics.g2d.internal.DebugPolicy;
+import org.simantics.scenegraph.g2d.G2DRenderingHints;
+import org.simantics.scenegraph.g2d.events.Event;
+import org.simantics.scenegraph.g2d.events.IEventQueue;
+import org.simantics.scenegraph.g2d.events.IEventQueue.IEventQueueListener;
+import org.simantics.scenegraph.g2d.events.adapter.AWTFocusAdapter;
+import org.simantics.scenegraph.g2d.events.adapter.AWTKeyEventAdapter;
+import org.simantics.scenegraph.g2d.events.adapter.AWTMouseEventAdapter;
+import org.simantics.utils.datastructures.hints.HintContext;
+import org.simantics.utils.datastructures.hints.IHintContext;
+import org.simantics.utils.threads.AWTThread;
+import org.simantics.utils.threads.Executable;
+import org.simantics.utils.threads.IThreadWorkQueue;
+import org.simantics.utils.threads.SyncListenerList;
+import org.simantics.utils.threads.ThreadUtils;
+
+/**
+ * Swing component chassis for a canvas context.
+ *
+ * @author Toni Kalajainen
+ */
+public class AWTChassis extends JComponent implements ICanvasChassis {
+
+//    private static final String PROBLEM = "Encountered VolatileImage issue, please reopen editor.";
+
+    private static final long serialVersionUID = 1L;
+
+    /** Hint context */
+    protected IHintContext                       hintCtx          = new HintContext();
+
+    protected SyncListenerList<IChassisListener> listeners        = new SyncListenerList<IChassisListener>(IChassisListener.class);
+    protected DropInteractor                     drop;
+    protected DragInteractor                     drag;
+
+    AWTMouseEventAdapter                         mouseAdapter;
+    AWTKeyEventAdapter                           keyAdapter;
+    AWTFocusAdapter                              focusAdapter;
+
+    Container holder = null; // Holder for possible swing components. Scenegraph handles event and painting, thus the size of the holder should be 0x0
+
+    IMouseCursorListener cursorListener = new IMouseCursorListener() {
+        @Override
+        public void onCursorSet(IMouseCursorContext sender, int mouseId, Cursor cursor) {
+            if (mouseId==0) setCursor(cursor);
+        }
+    };
+
+    private transient boolean                    dirty            = false;
+    private transient boolean                    closed           = false;
+    protected ICanvasContext                     canvasContext;
+
+    // Marks the content dirty
+    protected IContentListener contentListener = new IContentListener() {
+        @Override
+        public void onDirty(IContentContext sender) {
+            dirty = true;
+            ICanvasContext ctx = canvasContext;
+            if (ctx==null) return;
+            if (ctx.getEventQueue().isEmpty())
+            {
+                RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);
+                rm.markCompletelyDirty(AWTChassis.this);
+            }
+        }
+    };
+
+    // Paints dirty contents after all events are handled
+    protected IEventQueueListener queueListener = new IEventQueueListener() {
+        @Override
+        public void onEventAdded(IEventQueue queue, Event e, int index) {
+        }
+        @Override
+        public void onQueueEmpty(IEventQueue queue) {
+            if (dirty) {
+                RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);
+                rm.markCompletelyDirty(AWTChassis.this);
+            }
+        }
+    };
+
+    protected boolean hookKeyEvents;
+
+    /**
+     * Background buffer for rendered canvas. Allocated on-demand in
+     * {@link #paintComponent(Graphics)}. Nullified by
+     * {@link #setCanvasContext(ICanvasContext)} when canvas context is null to
+     * prevent leakage.
+     */
+    private VolatileImage buffer = null;
+//  private final int imageNo = 0;
+
+
+    public AWTChassis() {
+        this(true);
+    }
+
+    public AWTChassis(boolean hookKeyEvents) {
+        super();
+        setFocusable(true);
+        this.hookKeyEvents = hookKeyEvents;
+        this.setDoubleBuffered(false);
+        this.setOpaque(true);
+        this.setBackground(Color.WHITE);
+    }
+
+    @Override
+    public void setCanvasContext(final ICanvasContext canvasContext) {
+        // FIXME: this should be true but is currently not.
+        //assert AWTThread.getThreadAccess().currentThreadAccess();
+        if (this.canvasContext == canvasContext) return;
+        // Unhook old context
+        if (this.canvasContext!=null) {
+            this.canvasContext.getHintStack().removeHintContext(hintCtx);
+            this.canvasContext.getContentContext().removePaintableContextListener(contentListener);
+            this.canvasContext.getEventQueue().removeQueueListener(queueListener);
+            this.canvasContext.getMouseCursorContext().removeCursorListener(cursorListener);
+            this.canvasContext.remove(drop);
+            this.canvasContext.remove(drag);
+            removeMouseListener(mouseAdapter);
+            removeMouseMotionListener(mouseAdapter);
+            removeMouseWheelListener(mouseAdapter);
+            if (hookKeyEvents)
+                removeKeyListener(keyAdapter);
+            removeFocusListener(focusAdapter);
+
+            // SceneGraph event handling
+            removeMouseListener(this.canvasContext.getSceneGraph().getEventDelegator());
+            removeMouseMotionListener(this.canvasContext.getSceneGraph().getEventDelegator());
+            removeMouseWheelListener(this.canvasContext.getSceneGraph().getEventDelegator());
+            removeKeyListener(this.canvasContext.getSceneGraph().getEventDelegator());
+            removeFocusListener(this.canvasContext.getSceneGraph().getEventDelegator());
+            this.canvasContext.setTooltipProvider( null );
+
+            this.canvasContext = null;
+            this.focusAdapter = null;
+            this.mouseAdapter = null;
+            this.keyAdapter = null;
+            this.drop = null;
+            this.drag = null;
+            this.holder = null;
+            removeAll();
+            
+        }
+        this.canvasContext = canvasContext;
+        // Hook new context
+        if (canvasContext!=null) {
+
+            // SceneGraph event handling
+            addMouseListener(canvasContext.getSceneGraph().getEventDelegator());
+            addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator());
+            addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator());
+            addKeyListener(canvasContext.getSceneGraph().getEventDelegator());
+            addFocusListener(canvasContext.getSceneGraph().getEventDelegator());
+            canvasContext.setTooltipProvider( new AWTTooltipProvider() );
+
+            //Create canvas context and a layer of interactors
+            canvasContext.getHintStack().addHintContext(hintCtx, 0);
+            canvasContext.getContentContext().addPaintableContextListener(contentListener);
+            canvasContext.getEventQueue().addQueueListener(queueListener);
+            canvasContext.getMouseCursorContext().addCursorListener(cursorListener);
+
+            mouseAdapter = new AWTMouseEventAdapter(canvasContext, canvasContext.getEventQueue());
+            if (hookKeyEvents) {
+                keyAdapter = new AWTKeyEventAdapter(canvasContext, canvasContext.getEventQueue());
+                addKeyListener(keyAdapter);
+            }
+            focusAdapter = new AWTFocusAdapter(canvasContext, canvasContext.getEventQueue());
+            addMouseListener(mouseAdapter);
+            addMouseMotionListener(mouseAdapter);
+            addMouseWheelListener(mouseAdapter);
+            addFocusListener(focusAdapter);
+
+            canvasContext.add( drag=new DragInteractor(this) );
+            canvasContext.add( drop=new DropInteractor(this) );
+
+            // FIXME: hack to work around this:
+            // FIXME: this should be true but is currently not.
+            //assert AWTThread.getThreadAccess().currentThreadAccess();
+            Runnable initializeHolder = new Runnable() {
+                @Override
+                public void run() {
+                    if (canvasContext.isDisposed())
+                        return;
+                    if (holder == null) {
+                        holder = new Holder();
+//                        holder.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
+                        holder.setSize(1, 1);
+                        holder.setLocation(0, 0);
+                        holder.setFocusable(false);
+                        holder.setEnabled(true);
+                        holder.setVisible(true);
+                        holder.addMouseListener(canvasContext.getSceneGraph().getEventDelegator());
+                        holder.addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator());
+                        holder.addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator());
+                        holder.addKeyListener(canvasContext.getSceneGraph().getEventDelegator());
+                        holder.addFocusListener(canvasContext.getSceneGraph().getEventDelegator());
+                        //System.err.println("layout: " + holder.getLayout());
+                        AWTChassis.this.add(holder);
+                    }
+
+                    // Use dummy holder as root pane. Swing components need a root pane, but we don't want swing to handle painting..
+                    canvasContext.getSceneGraph().setRootPane(holder, AWTChassis.this);
+
+                    holder.addMouseListener(mouseAdapter);
+                    holder.addMouseMotionListener(mouseAdapter);
+                    holder.addMouseWheelListener(mouseAdapter);
+                    holder.addFocusListener(focusAdapter);
+                }
+            };
+            if (AWTThread.getThreadAccess().currentThreadAccess())
+                initializeHolder.run();
+            else
+                AWTThread.getThreadAccess().asyncExec(initializeHolder);
+        }
+        buffer = null;
+        repaint();
+    }
+
+    @Override
+    public ICanvasContext getCanvasContext() {
+        return canvasContext;
+    }
+
+    @Override
+    public void addChassisListener(IThreadWorkQueue thread, IChassisListener listener) {
+        listeners.add(thread, listener);
+    }
+
+    @Override
+    public void removeChassisListener(IThreadWorkQueue thread, IChassisListener listener) {
+        listeners.remove(thread, listener);
+    }
+
+    @Override
+    public void addChassisListener(IChassisListener listener) {
+        listeners.add(listener);
+    }
+
+    @Override
+    public void removeChassisListener(IChassisListener listener) {
+        listeners.remove(listener);
+    }
+
+    @Override
+    public IHintContext getHintContext() {
+        return hintCtx;
+    }
+
+    private void paintScenegraph(Graphics2D g2d, Rectangle controlBounds) {
+        Color bg = getBackground();
+        if (bg == null)
+            bg = Color.WHITE;
+        g2d.setBackground(bg);
+        g2d.clearRect(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height);
+        g2d.setClip(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height);
+        g2d.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, controlBounds);
+        if (!canvasContext.isLocked())
+            canvasContext.getSceneGraph().render(g2d);
+    }
+
+    /**
+     * Perform a best effort to render the current scene graph into a
+     * VolatileImage compatible with this chassis.
+     * 
+     * @param g2d
+     * @param b
+     * @return the VolatileImage rendered if successful, <code>null</code> if
+     *         rendering fails
+     */
+    private VolatileImage paintToVolatileImage(Graphics2D g2d, Rectangle b) {
+        int attempts = 0;
+        do {
+            // Prevent eternal looping experienced by Kalle.
+            // Caused by buffer.contentsLost failing constantly
+            // for the buffer created by createVolatileImage(w, h) below.
+            // This happens often with the following sequence:
+            //   - two displays, application window on the second display
+            //   - lock windows, (possibly wait a while/get coffee)
+            //   - unlock windows
+            // Also using an attempt count to let the code fall back to a
+            // slower rendering path if volatile images are causing trouble.
+            if (closed || attempts >= 10)
+                return null;
+
+            if (buffer == null
+                    || b.width != buffer.getWidth()
+                    || b.height != buffer.getHeight()
+                    || buffer.validate(g2d.getDeviceConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE)
+            {
+               
+                buffer = createVolatileImage(b.width, b.height);
+            }
+            if (buffer == null)
+                return null;
+
+//            ImageCapabilities caps = g2d.getDeviceConfiguration().getImageCapabilities();
+//            System.out.println("CAPS: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile());
+//            caps = buffer.getCapabilities();
+//            System.out.println("CAPS2: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile());
+
+            Graphics2D bg = buffer.createGraphics();
+            paintScenegraph(bg, b);
+            bg.dispose();
+
+            ++attempts;
+        } while (buffer.contentsLost());
+
+        // Successfully rendered to buffer!
+        return buffer;
+    }
+
+    @Override
+    public void paintComponent(Graphics g) {
+        dirty = false;
+        if (canvasContext == null)
+            return;
+
+        Graphics2D g2d = (Graphics2D) g;
+        Rectangle b = getBounds();
+
+        long startmem = 0, start = 0;
+        if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {
+            startmem = Runtime.getRuntime().freeMemory();
+            start = System.nanoTime();
+        }
+        VolatileImage buffer = paintToVolatileImage(g2d, b);
+        if (closed)
+            return;
+        if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {
+            long end = System.nanoTime();
+            long endmem = Runtime.getRuntime().freeMemory();
+            System.out.println("frame render: " + ((end-start)*1e-6) + " ms, " + (startmem-endmem)/(1024.0*1024.0) + " MB");
+        }
+
+        if (buffer != null) {
+            // Successfully buffered the scenegraph, copy the image to screen.
+
+            // DEBUG
+//          try {
+//              File frame = new File("d:/frames/frame" + (++imageNo) + ".png");
+//              System.out.println("writing frame: " + frame);
+//              ImageIO.write(buffer.getSnapshot(), "PNG", frame);
+//          } catch (IOException e) {
+//              e.printStackTrace();
+//          }
+
+            g2d.drawImage(buffer, 0, 0, null);
+        } else {
+            // Failed to paint volatile image, paint directly to the provided
+            // graphics context.
+            paintScenegraph(g2d, b);
+
+//            g2d.setFont(Font.getFont("Arial 14"));
+//            g2d.setColor(Color.RED);
+//            FontMetrics fm = g2d.getFontMetrics();
+//            Rectangle2D r = fm.getStringBounds(PROBLEM, g2d);
+//            g2d.drawString(PROBLEM, (int) (b.width-r.getWidth()), (int) (b.getHeight()-fm.getMaxDescent()));
+        }
+    }
+
+    private final static Method CLOSED_METHOD = SyncListenerList.getMethod(IChassisListener.class, "chassisClosed");
+    protected void fireChassisClosed()
+    {
+        closed = true;
+        Executable e[] = listeners.getExecutables(CLOSED_METHOD, this);
+        ThreadUtils.multiSyncExec(e);
+    }
+    
+    @Override
+       public boolean contains(int x, int y) {
+               // TODO Auto-generated method stub
+               return super.contains(x, y);
+       }
+    
+}