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