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.Rectangle;
20 import java.awt.image.VolatileImage;
21 import java.lang.reflect.Method;
23 import javax.swing.JComponent;
24 import javax.swing.RepaintManager;
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;
50 * Swing component chassis for a canvas context.
52 * @author Toni Kalajainen
54 public class AWTChassis extends JComponent implements ICanvasChassis {
56 // private static final String PROBLEM = "Encountered VolatileImage issue, please reopen editor.";
58 private static final long serialVersionUID = 1L;
61 protected IHintContext hintCtx = new HintContext();
63 protected SyncListenerList<IChassisListener> listeners = new SyncListenerList<IChassisListener>(IChassisListener.class);
64 protected DropInteractor drop;
65 protected DragInteractor drag;
67 AWTMouseEventAdapter mouseAdapter;
68 AWTKeyEventAdapter keyAdapter;
69 AWTFocusAdapter focusAdapter;
71 Container holder = null; // Holder for possible swing components. Scenegraph handles event and painting, thus the size of the holder should be 0x0
73 IMouseCursorListener cursorListener = new IMouseCursorListener() {
75 public void onCursorSet(IMouseCursorContext sender, int mouseId, Cursor cursor) {
76 if (mouseId==0) setCursor(cursor);
80 private transient boolean dirty = false;
81 private transient boolean closed = false;
82 protected ICanvasContext canvasContext;
84 private boolean useVolatileImage = true;
86 // Marks the content dirty
87 protected IContentListener contentListener = new IContentListener() {
89 public void onDirty(IContentContext sender) {
91 ICanvasContext ctx = canvasContext;
92 if (ctx==null) return;
93 if (ctx.getEventQueue().isEmpty())
95 RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);
96 rm.markCompletelyDirty(AWTChassis.this);
101 // Paints dirty contents after all events are handled
102 protected IEventQueueListener queueListener = new IEventQueueListener() {
104 public void onEventAdded(IEventQueue queue, Event e, int index) {
107 public void onQueueEmpty(IEventQueue queue) {
109 RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);
110 rm.markCompletelyDirty(AWTChassis.this);
115 protected boolean hookKeyEvents;
118 * Background buffer for rendered canvas. Allocated on-demand in
119 * {@link #paintComponent(Graphics)}. Nullified by
120 * {@link #setCanvasContext(ICanvasContext)} when canvas context is null to
123 private VolatileImage buffer = null;
124 // private final int imageNo = 0;
127 public AWTChassis() {
131 public AWTChassis(boolean hookKeyEvents) {
134 this.hookKeyEvents = hookKeyEvents;
135 this.setDoubleBuffered(false);
136 this.setOpaque(true);
137 this.setBackground(Color.WHITE);
141 public void setCanvasContext(final ICanvasContext canvasContext) {
142 // FIXME: this should be true but is currently not.
143 //assert AWTThread.getThreadAccess().currentThreadAccess();
144 if (this.canvasContext == canvasContext) return;
145 // Unhook old context
146 if (this.canvasContext!=null) {
147 this.canvasContext.getHintStack().removeHintContext(hintCtx);
148 this.canvasContext.getContentContext().removePaintableContextListener(contentListener);
149 this.canvasContext.getEventQueue().removeQueueListener(queueListener);
150 this.canvasContext.getMouseCursorContext().removeCursorListener(cursorListener);
151 this.canvasContext.remove(drop);
152 this.canvasContext.remove(drag);
153 removeMouseListener(mouseAdapter);
154 removeMouseMotionListener(mouseAdapter);
155 removeMouseWheelListener(mouseAdapter);
157 removeKeyListener(keyAdapter);
158 removeFocusListener(focusAdapter);
160 // SceneGraph event handling
161 removeMouseListener(this.canvasContext.getSceneGraph().getEventDelegator());
162 removeMouseMotionListener(this.canvasContext.getSceneGraph().getEventDelegator());
163 removeMouseWheelListener(this.canvasContext.getSceneGraph().getEventDelegator());
164 removeKeyListener(this.canvasContext.getSceneGraph().getEventDelegator());
165 removeFocusListener(this.canvasContext.getSceneGraph().getEventDelegator());
166 this.canvasContext.setTooltipProvider( null );
168 this.canvasContext = null;
169 this.focusAdapter = null;
170 this.mouseAdapter = null;
171 this.keyAdapter = null;
178 this.canvasContext = canvasContext;
180 if (canvasContext!=null) {
182 // SceneGraph event handling
183 addMouseListener(canvasContext.getSceneGraph().getEventDelegator());
184 addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator());
185 addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator());
186 addKeyListener(canvasContext.getSceneGraph().getEventDelegator());
187 addFocusListener(canvasContext.getSceneGraph().getEventDelegator());
188 canvasContext.setTooltipProvider( new AWTTooltipProvider() );
190 //Create canvas context and a layer of interactors
191 canvasContext.getHintStack().addHintContext(hintCtx, 0);
192 canvasContext.getContentContext().addPaintableContextListener(contentListener);
193 canvasContext.getEventQueue().addQueueListener(queueListener);
194 canvasContext.getMouseCursorContext().addCursorListener(cursorListener);
196 mouseAdapter = new AWTMouseEventAdapter(canvasContext, canvasContext.getEventQueue());
198 keyAdapter = new AWTKeyEventAdapter(canvasContext, canvasContext.getEventQueue());
199 addKeyListener(keyAdapter);
201 focusAdapter = new AWTFocusAdapter(canvasContext, canvasContext.getEventQueue());
202 addMouseListener(mouseAdapter);
203 addMouseMotionListener(mouseAdapter);
204 addMouseWheelListener(mouseAdapter);
205 addFocusListener(focusAdapter);
207 canvasContext.add( drag=new DragInteractor(this) );
208 canvasContext.add( drop=new DropInteractor(this) );
210 // FIXME: hack to work around this:
211 // FIXME: this should be true but is currently not.
212 //assert AWTThread.getThreadAccess().currentThreadAccess();
213 Runnable initializeHolder = new Runnable() {
216 if (canvasContext.isDisposed())
218 if (holder == null) {
219 holder = new Holder();
220 // holder.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
221 holder.setSize(1, 1);
222 holder.setLocation(0, 0);
223 holder.setFocusable(false);
224 holder.setEnabled(true);
225 holder.setVisible(true);
226 holder.addMouseListener(canvasContext.getSceneGraph().getEventDelegator());
227 holder.addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator());
228 holder.addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator());
229 holder.addKeyListener(canvasContext.getSceneGraph().getEventDelegator());
230 holder.addFocusListener(canvasContext.getSceneGraph().getEventDelegator());
231 //System.err.println("layout: " + holder.getLayout());
232 AWTChassis.this.add(holder);
235 // Use dummy holder as root pane. Swing components need a root pane, but we don't want swing to handle painting..
236 canvasContext.getSceneGraph().setRootPane(holder, AWTChassis.this);
238 holder.addMouseListener(mouseAdapter);
239 holder.addMouseMotionListener(mouseAdapter);
240 holder.addMouseWheelListener(mouseAdapter);
241 holder.addFocusListener(focusAdapter);
244 if (AWTThread.getThreadAccess().currentThreadAccess())
245 initializeHolder.run();
247 AWTThread.getThreadAccess().asyncExec(initializeHolder);
254 public ICanvasContext getCanvasContext() {
255 return canvasContext;
259 public void addChassisListener(IThreadWorkQueue thread, IChassisListener listener) {
260 listeners.add(thread, listener);
264 public void removeChassisListener(IThreadWorkQueue thread, IChassisListener listener) {
265 listeners.remove(thread, listener);
269 public void addChassisListener(IChassisListener listener) {
270 listeners.add(listener);
274 public void removeChassisListener(IChassisListener listener) {
275 listeners.remove(listener);
279 public IHintContext getHintContext() {
283 public void setUseVolatileImage(boolean useVolatileImage) {
284 this.useVolatileImage = useVolatileImage;
287 public boolean isUseVolatileImage() {
288 return useVolatileImage;
291 private void paintScenegraph(Graphics2D g2d, Rectangle controlBounds) {
292 Color bg = getBackground();
295 g2d.setBackground(bg);
296 g2d.clearRect(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height);
297 g2d.setClip(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height);
298 g2d.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, controlBounds);
299 if (!canvasContext.isLocked())
300 canvasContext.getSceneGraph().render(g2d);
304 * Perform a best effort to render the current scene graph into a
305 * VolatileImage compatible with this chassis.
309 * @return the VolatileImage rendered if successful, <code>null</code> if
312 private VolatileImage paintToVolatileImage(Graphics2D g2d, Rectangle b) {
315 // Prevent eternal looping experienced by Kalle.
316 // Caused by buffer.contentsLost failing constantly
317 // for the buffer created by createVolatileImage(w, h) below.
318 // This happens often with the following sequence:
319 // - two displays, application window on the second display
320 // - lock windows, (possibly wait a while/get coffee)
322 // Also using an attempt count to let the code fall back to a
323 // slower rendering path if volatile images are causing trouble.
324 if (closed || attempts >= 10)
328 || b.width != buffer.getWidth()
329 || b.height != buffer.getHeight()
330 || buffer.validate(g2d.getDeviceConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE)
333 buffer = createVolatileImage(b.width, b.height);
338 // ImageCapabilities caps = g2d.getDeviceConfiguration().getImageCapabilities();
339 // System.out.println("CAPS: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile());
340 // caps = buffer.getCapabilities();
341 // System.out.println("CAPS2: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile());
343 Graphics2D bg = buffer.createGraphics();
344 paintScenegraph(bg, b);
348 } while (buffer.contentsLost());
350 // Successfully rendered to buffer!
355 public void paintComponent(Graphics g) {
357 if (canvasContext == null)
360 Graphics2D g2d = (Graphics2D) g;
361 Rectangle b = getBounds();
363 long startmem = 0, start = 0;
364 if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {
365 startmem = Runtime.getRuntime().freeMemory();
366 start = System.nanoTime();
368 VolatileImage buffer = null;
369 if (useVolatileImage)
370 buffer = paintToVolatileImage(g2d, b);
373 if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {
374 long end = System.nanoTime();
375 long endmem = Runtime.getRuntime().freeMemory();
376 System.out.println("frame render: " + ((end-start)*1e-6) + " ms, " + (startmem-endmem)/(1024.0*1024.0) + " MB");
379 if (buffer != null) {
380 // Successfully buffered the scenegraph, copy the image to screen.
384 // File frame = new File("d:/frames/frame" + (++imageNo) + ".png");
385 // System.out.println("writing frame: " + frame);
386 // ImageIO.write(buffer.getSnapshot(), "PNG", frame);
387 // } catch (IOException e) {
388 // e.printStackTrace();
391 g2d.drawImage(buffer, 0, 0, null);
393 // Failed to paint volatile image, paint directly to the provided
395 paintScenegraph(g2d, b);
397 // g2d.setFont(Font.getFont("Arial 14"));
398 // g2d.setColor(Color.RED);
399 // FontMetrics fm = g2d.getFontMetrics();
400 // Rectangle2D r = fm.getStringBounds(PROBLEM, g2d);
401 // g2d.drawString(PROBLEM, (int) (b.width-r.getWidth()), (int) (b.getHeight()-fm.getMaxDescent()));
405 private final static Method CLOSED_METHOD = SyncListenerList.getMethod(IChassisListener.class, "chassisClosed");
406 protected void fireChassisClosed()
409 Executable e[] = listeners.getExecutables(CLOSED_METHOD, this);
410 ThreadUtils.multiSyncExec(e);
414 public boolean contains(int x, int y) {
415 // TODO Auto-generated method stub
416 return super.contains(x, y);