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 // Marks the content dirty
85 protected IContentListener contentListener = new IContentListener() {
87 public void onDirty(IContentContext sender) {
89 ICanvasContext ctx = canvasContext;
90 if (ctx==null) return;
91 if (ctx.getEventQueue().isEmpty())
93 RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);
94 rm.markCompletelyDirty(AWTChassis.this);
99 // Paints dirty contents after all events are handled
100 protected IEventQueueListener queueListener = new IEventQueueListener() {
102 public void onEventAdded(IEventQueue queue, Event e, int index) {
105 public void onQueueEmpty(IEventQueue queue) {
107 RepaintManager rm = RepaintManager.currentManager(AWTChassis.this);
108 rm.markCompletelyDirty(AWTChassis.this);
113 protected boolean hookKeyEvents;
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
121 private VolatileImage buffer = null;
122 // private final int imageNo = 0;
125 public AWTChassis() {
129 public AWTChassis(boolean hookKeyEvents) {
132 this.hookKeyEvents = hookKeyEvents;
133 this.setDoubleBuffered(false);
134 this.setOpaque(true);
135 this.setBackground(Color.WHITE);
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);
155 removeKeyListener(keyAdapter);
156 removeFocusListener(focusAdapter);
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 );
166 this.canvasContext = null;
167 this.focusAdapter = null;
168 this.mouseAdapter = null;
169 this.keyAdapter = null;
176 this.canvasContext = canvasContext;
178 if (canvasContext!=null) {
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() );
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);
194 mouseAdapter = new AWTMouseEventAdapter(canvasContext, canvasContext.getEventQueue());
196 keyAdapter = new AWTKeyEventAdapter(canvasContext, canvasContext.getEventQueue());
197 addKeyListener(keyAdapter);
199 focusAdapter = new AWTFocusAdapter(canvasContext, canvasContext.getEventQueue());
200 addMouseListener(mouseAdapter);
201 addMouseMotionListener(mouseAdapter);
202 addMouseWheelListener(mouseAdapter);
203 addFocusListener(focusAdapter);
205 canvasContext.add( drag=new DragInteractor(this) );
206 canvasContext.add( drop=new DropInteractor(this) );
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() {
214 if (canvasContext.isDisposed())
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);
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);
236 holder.addMouseListener(mouseAdapter);
237 holder.addMouseMotionListener(mouseAdapter);
238 holder.addMouseWheelListener(mouseAdapter);
239 holder.addFocusListener(focusAdapter);
242 if (AWTThread.getThreadAccess().currentThreadAccess())
243 initializeHolder.run();
245 AWTThread.getThreadAccess().asyncExec(initializeHolder);
252 public ICanvasContext getCanvasContext() {
253 return canvasContext;
257 public void addChassisListener(IThreadWorkQueue thread, IChassisListener listener) {
258 listeners.add(thread, listener);
262 public void removeChassisListener(IThreadWorkQueue thread, IChassisListener listener) {
263 listeners.remove(thread, listener);
267 public void addChassisListener(IChassisListener listener) {
268 listeners.add(listener);
272 public void removeChassisListener(IChassisListener listener) {
273 listeners.remove(listener);
277 public IHintContext getHintContext() {
281 private void paintScenegraph(Graphics2D g2d, Rectangle controlBounds) {
282 Color bg = getBackground();
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);
294 * Perform a best effort to render the current scene graph into a
295 * VolatileImage compatible with this chassis.
299 * @return the VolatileImage rendered if successful, <code>null</code> if
302 private VolatileImage paintToVolatileImage(Graphics2D g2d, Rectangle b) {
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)
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)
318 || b.width != buffer.getWidth()
319 || b.height != buffer.getHeight()
320 || buffer.validate(g2d.getDeviceConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE)
323 buffer = createVolatileImage(b.width, b.height);
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());
333 Graphics2D bg = buffer.createGraphics();
334 paintScenegraph(bg, b);
338 } while (buffer.contentsLost());
340 // Successfully rendered to buffer!
345 public void paintComponent(Graphics g) {
347 if (canvasContext == null)
350 Graphics2D g2d = (Graphics2D) g;
351 Rectangle b = getBounds();
353 long startmem = 0, start = 0;
354 if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) {
355 startmem = Runtime.getRuntime().freeMemory();
356 start = System.nanoTime();
358 VolatileImage buffer = paintToVolatileImage(g2d, b);
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");
367 if (buffer != null) {
368 // Successfully buffered the scenegraph, copy the image to screen.
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();
379 g2d.drawImage(buffer, 0, 0, null);
381 // Failed to paint volatile image, paint directly to the provided
383 paintScenegraph(g2d, b);
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()));
393 private final static Method CLOSED_METHOD = SyncListenerList.getMethod(IChassisListener.class, "chassisClosed");
394 protected void fireChassisClosed()
397 Executable e[] = listeners.getExecutables(CLOSED_METHOD, this);
398 ThreadUtils.multiSyncExec(e);
402 public boolean contains(int x, int y) {
403 // TODO Auto-generated method stub
404 return super.contains(x, y);