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