-/*******************************************************************************\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;
+
+ private boolean useVolatileImage = true;
+
+ // 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;
+ }
+
+ public void setUseVolatileImage(boolean useVolatileImage) {
+ this.useVolatileImage = useVolatileImage;
+ }
+
+ public boolean isUseVolatileImage() {
+ return useVolatileImage;
+ }
+
+ 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 = null;
+ if (useVolatileImage)
+ 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);
+ }
+
+}