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