--- /dev/null
+/*******************************************************************************\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