1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.chassis;
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;
24 import javax.swing.JComponent;
25 import javax.swing.RepaintManager;
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;
52 * Swing component chassis for a canvas context.
54 * @author Toni Kalajainen
56 public class AWTChassis extends JComponent implements ICanvasChassis {
58 // private static final String PROBLEM = "Encountered VolatileImage issue, please reopen editor.";
60 private static final long serialVersionUID = 1L;
63 protected IHintContext hintCtx = new HintContext();
65 protected SyncListenerList<IChassisListener> listeners = new SyncListenerList<IChassisListener>(IChassisListener.class);
66 protected DropInteractor drop;
67 protected DragInteractor drag;
69 AWTMouseEventAdapter mouseAdapter;
70 AWTKeyEventAdapter keyAdapter;
71 AWTFocusAdapter focusAdapter;
73 Container holder = null; // Holder for possible swing components. Scenegraph handles event and painting, thus the size of the holder should be 0x0
75 IMouseCursorListener cursorListener = new IMouseCursorListener() {
77 public void onCursorSet(IMouseCursorContext sender, int mouseId, Cursor cursor) {
78 if (mouseId==0) setCursor(cursor);
82 private transient boolean dirty = false;
83 private transient boolean closed = false;
84 protected ICanvasContext canvasContext;
86 private boolean useVolatileImage = true;
88 // Marks the content dirty
89 protected IContentListener contentListener = new IContentListener() {
91 public void onDirty(IContentContext sender) {
93 ICanvasContext ctx = canvasContext;
94 if (ctx==null) return;
95 if (ctx.getEventQueue().isEmpty())
97 RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);
98 rm.markCompletelyDirty(AWTChassis.this);
103 // Paints dirty contents after all events are handled
104 protected IEventQueueListener queueListener = new IEventQueueListener() {
106 public void onEventAdded(IEventQueue queue, Event e, int index) {
109 public void onQueueEmpty(IEventQueue queue) {
111 RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);
112 rm.markCompletelyDirty(AWTChassis.this);
117 protected boolean hookKeyEvents;
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
125 private VolatileImage buffer = null;
126 // private final int imageNo = 0;
129 public AWTChassis() {
133 public AWTChassis(boolean hookKeyEvents) {
136 this.hookKeyEvents = hookKeyEvents;
137 this.setDoubleBuffered(false);
138 this.setOpaque(true);
139 this.setBackground(Color.WHITE);
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);
159 removeKeyListener(keyAdapter);
160 removeFocusListener(focusAdapter);
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 );
170 this.canvasContext = null;
171 this.focusAdapter = null;
172 this.mouseAdapter = null;
173 this.keyAdapter = null;
180 this.canvasContext = canvasContext;
182 if (canvasContext!=null) {
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() );
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);
198 mouseAdapter = new AWTMouseEventAdapter(canvasContext, canvasContext.getEventQueue());
200 keyAdapter = new AWTKeyEventAdapter(canvasContext, canvasContext.getEventQueue());
201 addKeyListener(keyAdapter);
203 focusAdapter = new AWTFocusAdapter(canvasContext, canvasContext.getEventQueue());
204 addMouseListener(mouseAdapter);
205 addMouseMotionListener(mouseAdapter);
206 addMouseWheelListener(mouseAdapter);
207 addFocusListener(focusAdapter);
209 canvasContext.add( drag=new DragInteractor(this) );
210 canvasContext.add( drop=new DropInteractor(this) );
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() {
218 if (canvasContext.isDisposed())
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);
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);
240 holder.addMouseListener(mouseAdapter);
241 holder.addMouseMotionListener(mouseAdapter);
242 holder.addMouseWheelListener(mouseAdapter);
243 holder.addFocusListener(focusAdapter);
245 TransformUtil util = canvasContext.getAtMostOneItemOfClass(TransformUtil.class);
247 mouseAdapter.initDragGestureListener(holder, (control) -> util.controlToCanvas(control, null));
252 if (AWTThread.getThreadAccess().currentThreadAccess())
253 initializeHolder.run();
255 AWTThread.getThreadAccess().asyncExec(initializeHolder);
262 public ICanvasContext getCanvasContext() {
263 return canvasContext;
267 public void addChassisListener(IThreadWorkQueue thread, IChassisListener listener) {
268 listeners.add(thread, listener);
272 public void removeChassisListener(IThreadWorkQueue thread, IChassisListener listener) {
273 listeners.remove(thread, listener);
277 public void addChassisListener(IChassisListener listener) {
278 listeners.add(listener);
282 public void removeChassisListener(IChassisListener listener) {
283 listeners.remove(listener);
287 public IHintContext getHintContext() {
291 public void setUseVolatileImage(boolean useVolatileImage) {
292 this.useVolatileImage = useVolatileImage;
295 public boolean isUseVolatileImage() {
296 return useVolatileImage;
299 private void paintScenegraph(Graphics2D g2d, Rectangle controlBounds) {
300 Color bg = getBackground();
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);
312 * Perform a best effort to render the current scene graph into a
313 * VolatileImage compatible with this chassis.
317 * @return the VolatileImage rendered if successful, <code>null</code> if
320 private VolatileImage paintToVolatileImage(Graphics2D g2d, Rectangle b) {
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)
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)
336 || b.width != buffer.getWidth()
337 || b.height != buffer.getHeight()
338 || buffer.validate(g2d.getDeviceConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE)
341 buffer = createVolatileImage(b.width, b.height);
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());
351 Graphics2D bg = buffer.createGraphics();
352 paintScenegraph(bg, b);
356 } while (buffer.contentsLost());
358 // Successfully rendered to buffer!
363 public void paintComponent(Graphics g) {
365 if (canvasContext == null)
368 Graphics2D g2d = (Graphics2D) g;
369 Rectangle b = getBounds();
371 long startmem = 0, start = 0;
372 if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {
373 startmem = Runtime.getRuntime().freeMemory();
374 start = System.nanoTime();
376 VolatileImage buffer = null;
377 if (useVolatileImage)
378 buffer = paintToVolatileImage(g2d, b);
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");
387 if (buffer != null) {
388 // Successfully buffered the scenegraph, copy the image to screen.
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();
399 g2d.drawImage(buffer, 0, 0, null);
401 // Failed to paint volatile image, paint directly to the provided
403 paintScenegraph(g2d, b);
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()));
413 private final static Method CLOSED_METHOD = SyncListenerList.getMethod(IChassisListener.class, "chassisClosed");
414 protected void fireChassisClosed()
417 Executable e[] = listeners.getExecutables(CLOSED_METHOD, this);
418 ThreadUtils.multiSyncExec(e);
422 public boolean contains(int x, int y) {
423 // TODO Auto-generated method stub
424 return super.contains(x, y);