]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.g2d/src/org/simantics/g2d/chassis/AWTChassis.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / chassis / AWTChassis.java
diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/chassis/AWTChassis.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/chassis/AWTChassis.java
new file mode 100644 (file)
index 0000000..49c020b
--- /dev/null
@@ -0,0 +1,407 @@
+/*******************************************************************************\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