]> gerrit.simantics Code Review - simantics/platform.git/blob - 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
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.g2d.chassis;\r
13 \r
14 import java.awt.Color;\r
15 import java.awt.Container;\r
16 import java.awt.Cursor;\r
17 import java.awt.Graphics;\r
18 import java.awt.Graphics2D;\r
19 import java.awt.Rectangle;\r
20 import java.awt.image.VolatileImage;\r
21 import java.lang.reflect.Method;\r
22 \r
23 import javax.swing.JComponent;\r
24 import javax.swing.RepaintManager;\r
25 \r
26 import org.simantics.g2d.canvas.ICanvasContext;\r
27 import org.simantics.g2d.canvas.IContentContext;\r
28 import org.simantics.g2d.canvas.IContentContext.IContentListener;\r
29 import org.simantics.g2d.canvas.IMouseCursorContext;\r
30 import org.simantics.g2d.canvas.IMouseCursorListener;\r
31 import org.simantics.g2d.dnd.DragInteractor;\r
32 import org.simantics.g2d.dnd.DropInteractor;\r
33 import org.simantics.g2d.internal.DebugPolicy;\r
34 import org.simantics.scenegraph.g2d.G2DRenderingHints;\r
35 import org.simantics.scenegraph.g2d.events.Event;\r
36 import org.simantics.scenegraph.g2d.events.IEventQueue;\r
37 import org.simantics.scenegraph.g2d.events.IEventQueue.IEventQueueListener;\r
38 import org.simantics.scenegraph.g2d.events.adapter.AWTFocusAdapter;\r
39 import org.simantics.scenegraph.g2d.events.adapter.AWTKeyEventAdapter;\r
40 import org.simantics.scenegraph.g2d.events.adapter.AWTMouseEventAdapter;\r
41 import org.simantics.utils.datastructures.hints.HintContext;\r
42 import org.simantics.utils.datastructures.hints.IHintContext;\r
43 import org.simantics.utils.threads.AWTThread;\r
44 import org.simantics.utils.threads.Executable;\r
45 import org.simantics.utils.threads.IThreadWorkQueue;\r
46 import org.simantics.utils.threads.SyncListenerList;\r
47 import org.simantics.utils.threads.ThreadUtils;\r
48 \r
49 /**\r
50  * Swing component chassis for a canvas context.\r
51  *\r
52  * @author Toni Kalajainen\r
53  */\r
54 public class AWTChassis extends JComponent implements ICanvasChassis {\r
55 \r
56 //    private static final String PROBLEM = "Encountered VolatileImage issue, please reopen editor.";\r
57 \r
58     private static final long serialVersionUID = 1L;\r
59 \r
60     /** Hint context */\r
61     protected IHintContext                       hintCtx          = new HintContext();\r
62 \r
63     protected SyncListenerList<IChassisListener> listeners        = new SyncListenerList<IChassisListener>(IChassisListener.class);\r
64     protected DropInteractor                     drop;\r
65     protected DragInteractor                     drag;\r
66 \r
67     AWTMouseEventAdapter                         mouseAdapter;\r
68     AWTKeyEventAdapter                           keyAdapter;\r
69     AWTFocusAdapter                              focusAdapter;\r
70 \r
71     Container holder = null; // Holder for possible swing components. Scenegraph handles event and painting, thus the size of the holder should be 0x0\r
72 \r
73     IMouseCursorListener cursorListener = new IMouseCursorListener() {\r
74         @Override\r
75         public void onCursorSet(IMouseCursorContext sender, int mouseId, Cursor cursor) {\r
76             if (mouseId==0) setCursor(cursor);\r
77         }\r
78     };\r
79 \r
80     private transient boolean                    dirty            = false;\r
81     private transient boolean                    closed           = false;\r
82     protected ICanvasContext                     canvasContext;\r
83 \r
84     // Marks the content dirty\r
85     protected IContentListener contentListener = new IContentListener() {\r
86         @Override\r
87         public void onDirty(IContentContext sender) {\r
88             dirty = true;\r
89             ICanvasContext ctx = canvasContext;\r
90             if (ctx==null) return;\r
91             if (ctx.getEventQueue().isEmpty())\r
92             {\r
93                 RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);\r
94                 rm.markCompletelyDirty(AWTChassis.this);\r
95             }\r
96         }\r
97     };\r
98 \r
99     // Paints dirty contents after all events are handled\r
100     protected IEventQueueListener queueListener = new IEventQueueListener() {\r
101         @Override\r
102         public void onEventAdded(IEventQueue queue, Event e, int index) {\r
103         }\r
104         @Override\r
105         public void onQueueEmpty(IEventQueue queue) {\r
106             if (dirty) {\r
107                 RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);\r
108                 rm.markCompletelyDirty(AWTChassis.this);\r
109             }\r
110         }\r
111     };\r
112 \r
113     protected boolean hookKeyEvents;\r
114 \r
115     /**\r
116      * Background buffer for rendered canvas. Allocated on-demand in\r
117      * {@link #paintComponent(Graphics)}. Nullified by\r
118      * {@link #setCanvasContext(ICanvasContext)} when canvas context is null to\r
119      * prevent leakage.\r
120      */\r
121     private VolatileImage buffer = null;\r
122 //  private final int imageNo = 0;\r
123 \r
124 \r
125     public AWTChassis() {\r
126         this(true);\r
127     }\r
128 \r
129     public AWTChassis(boolean hookKeyEvents) {\r
130         super();\r
131         setFocusable(true);\r
132         this.hookKeyEvents = hookKeyEvents;\r
133         this.setDoubleBuffered(false);\r
134         this.setOpaque(true);\r
135         this.setBackground(Color.WHITE);\r
136     }\r
137 \r
138     @Override\r
139     public void setCanvasContext(final ICanvasContext canvasContext) {\r
140         // FIXME: this should be true but is currently not.\r
141         //assert AWTThread.getThreadAccess().currentThreadAccess();\r
142         if (this.canvasContext == canvasContext) return;\r
143         // Unhook old context\r
144         if (this.canvasContext!=null) {\r
145             this.canvasContext.getHintStack().removeHintContext(hintCtx);\r
146             this.canvasContext.getContentContext().removePaintableContextListener(contentListener);\r
147             this.canvasContext.getEventQueue().removeQueueListener(queueListener);\r
148             this.canvasContext.getMouseCursorContext().removeCursorListener(cursorListener);\r
149             this.canvasContext.remove(drop);\r
150             this.canvasContext.remove(drag);\r
151             removeMouseListener(mouseAdapter);\r
152             removeMouseMotionListener(mouseAdapter);\r
153             removeMouseWheelListener(mouseAdapter);\r
154             if (hookKeyEvents)\r
155                 removeKeyListener(keyAdapter);\r
156             removeFocusListener(focusAdapter);\r
157 \r
158             // SceneGraph event handling\r
159             removeMouseListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
160             removeMouseMotionListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
161             removeMouseWheelListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
162             removeKeyListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
163             removeFocusListener(this.canvasContext.getSceneGraph().getEventDelegator());\r
164             this.canvasContext.setTooltipProvider( null );\r
165 \r
166             this.canvasContext = null;\r
167             this.focusAdapter = null;\r
168             this.mouseAdapter = null;\r
169             this.keyAdapter = null;\r
170             this.drop = null;\r
171             this.drag = null;\r
172             this.holder = null;\r
173             removeAll();\r
174             \r
175         }\r
176         this.canvasContext = canvasContext;\r
177         // Hook new context\r
178         if (canvasContext!=null) {\r
179 \r
180             // SceneGraph event handling\r
181             addMouseListener(canvasContext.getSceneGraph().getEventDelegator());\r
182             addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator());\r
183             addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator());\r
184             addKeyListener(canvasContext.getSceneGraph().getEventDelegator());\r
185             addFocusListener(canvasContext.getSceneGraph().getEventDelegator());\r
186             canvasContext.setTooltipProvider( new AWTTooltipProvider() );\r
187 \r
188             //Create canvas context and a layer of interactors\r
189             canvasContext.getHintStack().addHintContext(hintCtx, 0);\r
190             canvasContext.getContentContext().addPaintableContextListener(contentListener);\r
191             canvasContext.getEventQueue().addQueueListener(queueListener);\r
192             canvasContext.getMouseCursorContext().addCursorListener(cursorListener);\r
193 \r
194             mouseAdapter = new AWTMouseEventAdapter(canvasContext, canvasContext.getEventQueue());\r
195             if (hookKeyEvents) {\r
196                 keyAdapter = new AWTKeyEventAdapter(canvasContext, canvasContext.getEventQueue());\r
197                 addKeyListener(keyAdapter);\r
198             }\r
199             focusAdapter = new AWTFocusAdapter(canvasContext, canvasContext.getEventQueue());\r
200             addMouseListener(mouseAdapter);\r
201             addMouseMotionListener(mouseAdapter);\r
202             addMouseWheelListener(mouseAdapter);\r
203             addFocusListener(focusAdapter);\r
204 \r
205             canvasContext.add( drag=new DragInteractor(this) );\r
206             canvasContext.add( drop=new DropInteractor(this) );\r
207 \r
208             // FIXME: hack to work around this:\r
209             // FIXME: this should be true but is currently not.\r
210             //assert AWTThread.getThreadAccess().currentThreadAccess();\r
211             Runnable initializeHolder = new Runnable() {\r
212                 @Override\r
213                 public void run() {\r
214                     if (canvasContext.isDisposed())\r
215                         return;\r
216                     if (holder == null) {\r
217                         holder = new Holder();\r
218 //                        holder.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));\r
219                         holder.setSize(1, 1);\r
220                         holder.setLocation(0, 0);\r
221                         holder.setFocusable(false);\r
222                         holder.setEnabled(true);\r
223                         holder.setVisible(true);\r
224                         holder.addMouseListener(canvasContext.getSceneGraph().getEventDelegator());\r
225                         holder.addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator());\r
226                         holder.addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator());\r
227                         holder.addKeyListener(canvasContext.getSceneGraph().getEventDelegator());\r
228                         holder.addFocusListener(canvasContext.getSceneGraph().getEventDelegator());\r
229                         //System.err.println("layout: " + holder.getLayout());\r
230                         AWTChassis.this.add(holder);\r
231                     }\r
232 \r
233                     // Use dummy holder as root pane. Swing components need a root pane, but we don't want swing to handle painting..\r
234                     canvasContext.getSceneGraph().setRootPane(holder, AWTChassis.this);\r
235 \r
236                     holder.addMouseListener(mouseAdapter);\r
237                     holder.addMouseMotionListener(mouseAdapter);\r
238                     holder.addMouseWheelListener(mouseAdapter);\r
239                     holder.addFocusListener(focusAdapter);\r
240                 }\r
241             };\r
242             if (AWTThread.getThreadAccess().currentThreadAccess())\r
243                 initializeHolder.run();\r
244             else\r
245                 AWTThread.getThreadAccess().asyncExec(initializeHolder);\r
246         }\r
247         buffer = null;\r
248         repaint();\r
249     }\r
250 \r
251     @Override\r
252     public ICanvasContext getCanvasContext() {\r
253         return canvasContext;\r
254     }\r
255 \r
256     @Override\r
257     public void addChassisListener(IThreadWorkQueue thread, IChassisListener listener) {\r
258         listeners.add(thread, listener);\r
259     }\r
260 \r
261     @Override\r
262     public void removeChassisListener(IThreadWorkQueue thread, IChassisListener listener) {\r
263         listeners.remove(thread, listener);\r
264     }\r
265 \r
266     @Override\r
267     public void addChassisListener(IChassisListener listener) {\r
268         listeners.add(listener);\r
269     }\r
270 \r
271     @Override\r
272     public void removeChassisListener(IChassisListener listener) {\r
273         listeners.remove(listener);\r
274     }\r
275 \r
276     @Override\r
277     public IHintContext getHintContext() {\r
278         return hintCtx;\r
279     }\r
280 \r
281     private void paintScenegraph(Graphics2D g2d, Rectangle controlBounds) {\r
282         Color bg = getBackground();\r
283         if (bg == null)\r
284             bg = Color.WHITE;\r
285         g2d.setBackground(bg);\r
286         g2d.clearRect(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height);\r
287         g2d.setClip(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height);\r
288         g2d.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, controlBounds);\r
289         if (!canvasContext.isLocked())\r
290             canvasContext.getSceneGraph().render(g2d);\r
291     }\r
292 \r
293     /**\r
294      * Perform a best effort to render the current scene graph into a\r
295      * VolatileImage compatible with this chassis.\r
296      * \r
297      * @param g2d\r
298      * @param b\r
299      * @return the VolatileImage rendered if successful, <code>null</code> if\r
300      *         rendering fails\r
301      */\r
302     private VolatileImage paintToVolatileImage(Graphics2D g2d, Rectangle b) {\r
303         int attempts = 0;\r
304         do {\r
305             // Prevent eternal looping experienced by Kalle.\r
306             // Caused by buffer.contentsLost failing constantly\r
307             // for the buffer created by createVolatileImage(w, h) below.\r
308             // This happens often with the following sequence:\r
309             //   - two displays, application window on the second display\r
310             //   - lock windows, (possibly wait a while/get coffee)\r
311             //   - unlock windows\r
312             // Also using an attempt count to let the code fall back to a\r
313             // slower rendering path if volatile images are causing trouble.\r
314             if (closed || attempts >= 10)\r
315                 return null;\r
316 \r
317             if (buffer == null\r
318                     || b.width != buffer.getWidth()\r
319                     || b.height != buffer.getHeight()\r
320                     || buffer.validate(g2d.getDeviceConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE)\r
321             {\r
322                 \r
323                 buffer = createVolatileImage(b.width, b.height);\r
324             }\r
325             if (buffer == null)\r
326                 return null;\r
327 \r
328 //            ImageCapabilities caps = g2d.getDeviceConfiguration().getImageCapabilities();\r
329 //            System.out.println("CAPS: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile());\r
330 //            caps = buffer.getCapabilities();\r
331 //            System.out.println("CAPS2: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile());\r
332 \r
333             Graphics2D bg = buffer.createGraphics();\r
334             paintScenegraph(bg, b);\r
335             bg.dispose();\r
336 \r
337             ++attempts;\r
338         } while (buffer.contentsLost());\r
339 \r
340         // Successfully rendered to buffer!\r
341         return buffer;\r
342     }\r
343 \r
344     @Override\r
345     public void paintComponent(Graphics g) {\r
346         dirty = false;\r
347         if (canvasContext == null)\r
348             return;\r
349 \r
350         Graphics2D g2d = (Graphics2D) g;\r
351         Rectangle b = getBounds();\r
352 \r
353         long startmem = 0, start = 0;\r
354         if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {\r
355             startmem = Runtime.getRuntime().freeMemory();\r
356             start = System.nanoTime();\r
357         }\r
358         VolatileImage buffer = paintToVolatileImage(g2d, b);\r
359         if (closed)\r
360             return;\r
361         if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {\r
362             long end = System.nanoTime();\r
363             long endmem = Runtime.getRuntime().freeMemory();\r
364             System.out.println("frame render: " + ((end-start)*1e-6) + " ms, " + (startmem-endmem)/(1024.0*1024.0) + " MB");\r
365         }\r
366 \r
367         if (buffer != null) {\r
368             // Successfully buffered the scenegraph, copy the image to screen.\r
369 \r
370             // DEBUG\r
371 //          try {\r
372 //              File frame = new File("d:/frames/frame" + (++imageNo) + ".png");\r
373 //              System.out.println("writing frame: " + frame);\r
374 //              ImageIO.write(buffer.getSnapshot(), "PNG", frame);\r
375 //          } catch (IOException e) {\r
376 //              e.printStackTrace();\r
377 //          }\r
378 \r
379             g2d.drawImage(buffer, 0, 0, null);\r
380         } else {\r
381             // Failed to paint volatile image, paint directly to the provided\r
382             // graphics context.\r
383             paintScenegraph(g2d, b);\r
384 \r
385 //            g2d.setFont(Font.getFont("Arial 14"));\r
386 //            g2d.setColor(Color.RED);\r
387 //            FontMetrics fm = g2d.getFontMetrics();\r
388 //            Rectangle2D r = fm.getStringBounds(PROBLEM, g2d);\r
389 //            g2d.drawString(PROBLEM, (int) (b.width-r.getWidth()), (int) (b.getHeight()-fm.getMaxDescent()));\r
390         }\r
391     }\r
392 \r
393     private final static Method CLOSED_METHOD = SyncListenerList.getMethod(IChassisListener.class, "chassisClosed");\r
394     protected void fireChassisClosed()\r
395     {\r
396         closed = true;\r
397         Executable e[] = listeners.getExecutables(CLOSED_METHOD, this);\r
398         ThreadUtils.multiSyncExec(e);\r
399     }\r
400     \r
401     @Override\r
402         public boolean contains(int x, int y) {\r
403                 // TODO Auto-generated method stub\r
404                 return super.contains(x, y);\r
405         }\r
406     \r
407 }\r