X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Fchassis%2FAWTChassis.java;h=2679ef620ac5c87b65de2337c9a188219895a88e;hb=56e6bade5f3a9cd2210184ffc13ffb29f007dccf;hp=49c020bce6b87586e0a84ebfc9d187d7247e9c35;hpb=969bd23cab98a79ca9101af33334000879fb60c5;p=simantics%2Fplatform.git diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/chassis/AWTChassis.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/chassis/AWTChassis.java index 49c020bce..2679ef620 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/chassis/AWTChassis.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/chassis/AWTChassis.java @@ -1,407 +1,419 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management - * in Industry THTH ry. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.g2d.chassis; - -import java.awt.Color; -import java.awt.Container; -import java.awt.Cursor; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Rectangle; -import java.awt.image.VolatileImage; -import java.lang.reflect.Method; - -import javax.swing.JComponent; -import javax.swing.RepaintManager; - -import org.simantics.g2d.canvas.ICanvasContext; -import org.simantics.g2d.canvas.IContentContext; -import org.simantics.g2d.canvas.IContentContext.IContentListener; -import org.simantics.g2d.canvas.IMouseCursorContext; -import org.simantics.g2d.canvas.IMouseCursorListener; -import org.simantics.g2d.dnd.DragInteractor; -import org.simantics.g2d.dnd.DropInteractor; -import org.simantics.g2d.internal.DebugPolicy; -import org.simantics.scenegraph.g2d.G2DRenderingHints; -import org.simantics.scenegraph.g2d.events.Event; -import org.simantics.scenegraph.g2d.events.IEventQueue; -import org.simantics.scenegraph.g2d.events.IEventQueue.IEventQueueListener; -import org.simantics.scenegraph.g2d.events.adapter.AWTFocusAdapter; -import org.simantics.scenegraph.g2d.events.adapter.AWTKeyEventAdapter; -import org.simantics.scenegraph.g2d.events.adapter.AWTMouseEventAdapter; -import org.simantics.utils.datastructures.hints.HintContext; -import org.simantics.utils.datastructures.hints.IHintContext; -import org.simantics.utils.threads.AWTThread; -import org.simantics.utils.threads.Executable; -import org.simantics.utils.threads.IThreadWorkQueue; -import org.simantics.utils.threads.SyncListenerList; -import org.simantics.utils.threads.ThreadUtils; - -/** - * Swing component chassis for a canvas context. - * - * @author Toni Kalajainen - */ -public class AWTChassis extends JComponent implements ICanvasChassis { - -// private static final String PROBLEM = "Encountered VolatileImage issue, please reopen editor."; - - private static final long serialVersionUID = 1L; - - /** Hint context */ - protected IHintContext hintCtx = new HintContext(); - - protected SyncListenerList listeners = new SyncListenerList(IChassisListener.class); - protected DropInteractor drop; - protected DragInteractor drag; - - AWTMouseEventAdapter mouseAdapter; - AWTKeyEventAdapter keyAdapter; - AWTFocusAdapter focusAdapter; - - Container holder = null; // Holder for possible swing components. Scenegraph handles event and painting, thus the size of the holder should be 0x0 - - IMouseCursorListener cursorListener = new IMouseCursorListener() { - @Override - public void onCursorSet(IMouseCursorContext sender, int mouseId, Cursor cursor) { - if (mouseId==0) setCursor(cursor); - } - }; - - private transient boolean dirty = false; - private transient boolean closed = false; - protected ICanvasContext canvasContext; - - // Marks the content dirty - protected IContentListener contentListener = new IContentListener() { - @Override - public void onDirty(IContentContext sender) { - dirty = true; - ICanvasContext ctx = canvasContext; - if (ctx==null) return; - if (ctx.getEventQueue().isEmpty()) - { - RepaintManager rm = RepaintManager.currentManager(AWTChassis.this); - rm.markCompletelyDirty(AWTChassis.this); - } - } - }; - - // Paints dirty contents after all events are handled - protected IEventQueueListener queueListener = new IEventQueueListener() { - @Override - public void onEventAdded(IEventQueue queue, Event e, int index) { - } - @Override - public void onQueueEmpty(IEventQueue queue) { - if (dirty) { - RepaintManager rm = RepaintManager.currentManager(AWTChassis.this); - rm.markCompletelyDirty(AWTChassis.this); - } - } - }; - - protected boolean hookKeyEvents; - - /** - * Background buffer for rendered canvas. Allocated on-demand in - * {@link #paintComponent(Graphics)}. Nullified by - * {@link #setCanvasContext(ICanvasContext)} when canvas context is null to - * prevent leakage. - */ - private VolatileImage buffer = null; -// private final int imageNo = 0; - - - public AWTChassis() { - this(true); - } - - public AWTChassis(boolean hookKeyEvents) { - super(); - setFocusable(true); - this.hookKeyEvents = hookKeyEvents; - this.setDoubleBuffered(false); - this.setOpaque(true); - this.setBackground(Color.WHITE); - } - - @Override - public void setCanvasContext(final ICanvasContext canvasContext) { - // FIXME: this should be true but is currently not. - //assert AWTThread.getThreadAccess().currentThreadAccess(); - if (this.canvasContext == canvasContext) return; - // Unhook old context - if (this.canvasContext!=null) { - this.canvasContext.getHintStack().removeHintContext(hintCtx); - this.canvasContext.getContentContext().removePaintableContextListener(contentListener); - this.canvasContext.getEventQueue().removeQueueListener(queueListener); - this.canvasContext.getMouseCursorContext().removeCursorListener(cursorListener); - this.canvasContext.remove(drop); - this.canvasContext.remove(drag); - removeMouseListener(mouseAdapter); - removeMouseMotionListener(mouseAdapter); - removeMouseWheelListener(mouseAdapter); - if (hookKeyEvents) - removeKeyListener(keyAdapter); - removeFocusListener(focusAdapter); - - // SceneGraph event handling - removeMouseListener(this.canvasContext.getSceneGraph().getEventDelegator()); - removeMouseMotionListener(this.canvasContext.getSceneGraph().getEventDelegator()); - removeMouseWheelListener(this.canvasContext.getSceneGraph().getEventDelegator()); - removeKeyListener(this.canvasContext.getSceneGraph().getEventDelegator()); - removeFocusListener(this.canvasContext.getSceneGraph().getEventDelegator()); - this.canvasContext.setTooltipProvider( null ); - - this.canvasContext = null; - this.focusAdapter = null; - this.mouseAdapter = null; - this.keyAdapter = null; - this.drop = null; - this.drag = null; - this.holder = null; - removeAll(); - - } - this.canvasContext = canvasContext; - // Hook new context - if (canvasContext!=null) { - - // SceneGraph event handling - addMouseListener(canvasContext.getSceneGraph().getEventDelegator()); - addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator()); - addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator()); - addKeyListener(canvasContext.getSceneGraph().getEventDelegator()); - addFocusListener(canvasContext.getSceneGraph().getEventDelegator()); - canvasContext.setTooltipProvider( new AWTTooltipProvider() ); - - //Create canvas context and a layer of interactors - canvasContext.getHintStack().addHintContext(hintCtx, 0); - canvasContext.getContentContext().addPaintableContextListener(contentListener); - canvasContext.getEventQueue().addQueueListener(queueListener); - canvasContext.getMouseCursorContext().addCursorListener(cursorListener); - - mouseAdapter = new AWTMouseEventAdapter(canvasContext, canvasContext.getEventQueue()); - if (hookKeyEvents) { - keyAdapter = new AWTKeyEventAdapter(canvasContext, canvasContext.getEventQueue()); - addKeyListener(keyAdapter); - } - focusAdapter = new AWTFocusAdapter(canvasContext, canvasContext.getEventQueue()); - addMouseListener(mouseAdapter); - addMouseMotionListener(mouseAdapter); - addMouseWheelListener(mouseAdapter); - addFocusListener(focusAdapter); - - canvasContext.add( drag=new DragInteractor(this) ); - canvasContext.add( drop=new DropInteractor(this) ); - - // FIXME: hack to work around this: - // FIXME: this should be true but is currently not. - //assert AWTThread.getThreadAccess().currentThreadAccess(); - Runnable initializeHolder = new Runnable() { - @Override - public void run() { - if (canvasContext.isDisposed()) - return; - if (holder == null) { - holder = new Holder(); -// holder.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); - holder.setSize(1, 1); - holder.setLocation(0, 0); - holder.setFocusable(false); - holder.setEnabled(true); - holder.setVisible(true); - holder.addMouseListener(canvasContext.getSceneGraph().getEventDelegator()); - holder.addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator()); - holder.addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator()); - holder.addKeyListener(canvasContext.getSceneGraph().getEventDelegator()); - holder.addFocusListener(canvasContext.getSceneGraph().getEventDelegator()); - //System.err.println("layout: " + holder.getLayout()); - AWTChassis.this.add(holder); - } - - // Use dummy holder as root pane. Swing components need a root pane, but we don't want swing to handle painting.. - canvasContext.getSceneGraph().setRootPane(holder, AWTChassis.this); - - holder.addMouseListener(mouseAdapter); - holder.addMouseMotionListener(mouseAdapter); - holder.addMouseWheelListener(mouseAdapter); - holder.addFocusListener(focusAdapter); - } - }; - if (AWTThread.getThreadAccess().currentThreadAccess()) - initializeHolder.run(); - else - AWTThread.getThreadAccess().asyncExec(initializeHolder); - } - buffer = null; - repaint(); - } - - @Override - public ICanvasContext getCanvasContext() { - return canvasContext; - } - - @Override - public void addChassisListener(IThreadWorkQueue thread, IChassisListener listener) { - listeners.add(thread, listener); - } - - @Override - public void removeChassisListener(IThreadWorkQueue thread, IChassisListener listener) { - listeners.remove(thread, listener); - } - - @Override - public void addChassisListener(IChassisListener listener) { - listeners.add(listener); - } - - @Override - public void removeChassisListener(IChassisListener listener) { - listeners.remove(listener); - } - - @Override - public IHintContext getHintContext() { - return hintCtx; - } - - private void paintScenegraph(Graphics2D g2d, Rectangle controlBounds) { - Color bg = getBackground(); - if (bg == null) - bg = Color.WHITE; - g2d.setBackground(bg); - g2d.clearRect(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height); - g2d.setClip(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height); - g2d.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, controlBounds); - if (!canvasContext.isLocked()) - canvasContext.getSceneGraph().render(g2d); - } - - /** - * Perform a best effort to render the current scene graph into a - * VolatileImage compatible with this chassis. - * - * @param g2d - * @param b - * @return the VolatileImage rendered if successful, null if - * rendering fails - */ - private VolatileImage paintToVolatileImage(Graphics2D g2d, Rectangle b) { - int attempts = 0; - do { - // Prevent eternal looping experienced by Kalle. - // Caused by buffer.contentsLost failing constantly - // for the buffer created by createVolatileImage(w, h) below. - // This happens often with the following sequence: - // - two displays, application window on the second display - // - lock windows, (possibly wait a while/get coffee) - // - unlock windows - // Also using an attempt count to let the code fall back to a - // slower rendering path if volatile images are causing trouble. - if (closed || attempts >= 10) - return null; - - if (buffer == null - || b.width != buffer.getWidth() - || b.height != buffer.getHeight() - || buffer.validate(g2d.getDeviceConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE) - { - - buffer = createVolatileImage(b.width, b.height); - } - if (buffer == null) - return null; - -// ImageCapabilities caps = g2d.getDeviceConfiguration().getImageCapabilities(); -// System.out.println("CAPS: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile()); -// caps = buffer.getCapabilities(); -// System.out.println("CAPS2: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile()); - - Graphics2D bg = buffer.createGraphics(); - paintScenegraph(bg, b); - bg.dispose(); - - ++attempts; - } while (buffer.contentsLost()); - - // Successfully rendered to buffer! - return buffer; - } - - @Override - public void paintComponent(Graphics g) { - dirty = false; - if (canvasContext == null) - return; - - Graphics2D g2d = (Graphics2D) g; - Rectangle b = getBounds(); - - long startmem = 0, start = 0; - if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) { - startmem = Runtime.getRuntime().freeMemory(); - start = System.nanoTime(); - } - VolatileImage buffer = paintToVolatileImage(g2d, b); - if (closed) - return; - if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) { - long end = System.nanoTime(); - long endmem = Runtime.getRuntime().freeMemory(); - System.out.println("frame render: " + ((end-start)*1e-6) + " ms, " + (startmem-endmem)/(1024.0*1024.0) + " MB"); - } - - if (buffer != null) { - // Successfully buffered the scenegraph, copy the image to screen. - - // DEBUG -// try { -// File frame = new File("d:/frames/frame" + (++imageNo) + ".png"); -// System.out.println("writing frame: " + frame); -// ImageIO.write(buffer.getSnapshot(), "PNG", frame); -// } catch (IOException e) { -// e.printStackTrace(); -// } - - g2d.drawImage(buffer, 0, 0, null); - } else { - // Failed to paint volatile image, paint directly to the provided - // graphics context. - paintScenegraph(g2d, b); - -// g2d.setFont(Font.getFont("Arial 14")); -// g2d.setColor(Color.RED); -// FontMetrics fm = g2d.getFontMetrics(); -// Rectangle2D r = fm.getStringBounds(PROBLEM, g2d); -// g2d.drawString(PROBLEM, (int) (b.width-r.getWidth()), (int) (b.getHeight()-fm.getMaxDescent())); - } - } - - private final static Method CLOSED_METHOD = SyncListenerList.getMethod(IChassisListener.class, "chassisClosed"); - protected void fireChassisClosed() - { - closed = true; - Executable e[] = listeners.getExecutables(CLOSED_METHOD, this); - ThreadUtils.multiSyncExec(e); - } - - @Override - public boolean contains(int x, int y) { - // TODO Auto-generated method stub - return super.contains(x, y); - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.g2d.chassis; + +import java.awt.Color; +import java.awt.Container; +import java.awt.Cursor; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.VolatileImage; +import java.lang.reflect.Method; + +import javax.swing.JComponent; +import javax.swing.RepaintManager; + +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.canvas.IContentContext; +import org.simantics.g2d.canvas.IContentContext.IContentListener; +import org.simantics.g2d.canvas.IMouseCursorContext; +import org.simantics.g2d.canvas.IMouseCursorListener; +import org.simantics.g2d.dnd.DragInteractor; +import org.simantics.g2d.dnd.DropInteractor; +import org.simantics.g2d.internal.DebugPolicy; +import org.simantics.scenegraph.g2d.G2DRenderingHints; +import org.simantics.scenegraph.g2d.events.Event; +import org.simantics.scenegraph.g2d.events.IEventQueue; +import org.simantics.scenegraph.g2d.events.IEventQueue.IEventQueueListener; +import org.simantics.scenegraph.g2d.events.adapter.AWTFocusAdapter; +import org.simantics.scenegraph.g2d.events.adapter.AWTKeyEventAdapter; +import org.simantics.scenegraph.g2d.events.adapter.AWTMouseEventAdapter; +import org.simantics.utils.datastructures.hints.HintContext; +import org.simantics.utils.datastructures.hints.IHintContext; +import org.simantics.utils.threads.AWTThread; +import org.simantics.utils.threads.Executable; +import org.simantics.utils.threads.IThreadWorkQueue; +import org.simantics.utils.threads.SyncListenerList; +import org.simantics.utils.threads.ThreadUtils; + +/** + * Swing component chassis for a canvas context. + * + * @author Toni Kalajainen + */ +public class AWTChassis extends JComponent implements ICanvasChassis { + +// private static final String PROBLEM = "Encountered VolatileImage issue, please reopen editor."; + + private static final long serialVersionUID = 1L; + + /** Hint context */ + protected IHintContext hintCtx = new HintContext(); + + protected SyncListenerList listeners = new SyncListenerList(IChassisListener.class); + protected DropInteractor drop; + protected DragInteractor drag; + + AWTMouseEventAdapter mouseAdapter; + AWTKeyEventAdapter keyAdapter; + AWTFocusAdapter focusAdapter; + + Container holder = null; // Holder for possible swing components. Scenegraph handles event and painting, thus the size of the holder should be 0x0 + + IMouseCursorListener cursorListener = new IMouseCursorListener() { + @Override + public void onCursorSet(IMouseCursorContext sender, int mouseId, Cursor cursor) { + if (mouseId==0) setCursor(cursor); + } + }; + + private transient boolean dirty = false; + private transient boolean closed = false; + protected ICanvasContext canvasContext; + + private boolean useVolatileImage = true; + + // Marks the content dirty + protected IContentListener contentListener = new IContentListener() { + @Override + public void onDirty(IContentContext sender) { + dirty = true; + ICanvasContext ctx = canvasContext; + if (ctx==null) return; + if (ctx.getEventQueue().isEmpty()) + { + RepaintManager rm = RepaintManager.currentManager(AWTChassis.this); + rm.markCompletelyDirty(AWTChassis.this); + } + } + }; + + // Paints dirty contents after all events are handled + protected IEventQueueListener queueListener = new IEventQueueListener() { + @Override + public void onEventAdded(IEventQueue queue, Event e, int index) { + } + @Override + public void onQueueEmpty(IEventQueue queue) { + if (dirty) { + RepaintManager rm = RepaintManager.currentManager(AWTChassis.this); + rm.markCompletelyDirty(AWTChassis.this); + } + } + }; + + protected boolean hookKeyEvents; + + /** + * Background buffer for rendered canvas. Allocated on-demand in + * {@link #paintComponent(Graphics)}. Nullified by + * {@link #setCanvasContext(ICanvasContext)} when canvas context is null to + * prevent leakage. + */ + private VolatileImage buffer = null; +// private final int imageNo = 0; + + + public AWTChassis() { + this(true); + } + + public AWTChassis(boolean hookKeyEvents) { + super(); + setFocusable(true); + this.hookKeyEvents = hookKeyEvents; + this.setDoubleBuffered(false); + this.setOpaque(true); + this.setBackground(Color.WHITE); + } + + @Override + public void setCanvasContext(final ICanvasContext canvasContext) { + // FIXME: this should be true but is currently not. + //assert AWTThread.getThreadAccess().currentThreadAccess(); + if (this.canvasContext == canvasContext) return; + // Unhook old context + if (this.canvasContext!=null) { + this.canvasContext.getHintStack().removeHintContext(hintCtx); + this.canvasContext.getContentContext().removePaintableContextListener(contentListener); + this.canvasContext.getEventQueue().removeQueueListener(queueListener); + this.canvasContext.getMouseCursorContext().removeCursorListener(cursorListener); + this.canvasContext.remove(drop); + this.canvasContext.remove(drag); + removeMouseListener(mouseAdapter); + removeMouseMotionListener(mouseAdapter); + removeMouseWheelListener(mouseAdapter); + if (hookKeyEvents) + removeKeyListener(keyAdapter); + removeFocusListener(focusAdapter); + + // SceneGraph event handling + removeMouseListener(this.canvasContext.getSceneGraph().getEventDelegator()); + removeMouseMotionListener(this.canvasContext.getSceneGraph().getEventDelegator()); + removeMouseWheelListener(this.canvasContext.getSceneGraph().getEventDelegator()); + removeKeyListener(this.canvasContext.getSceneGraph().getEventDelegator()); + removeFocusListener(this.canvasContext.getSceneGraph().getEventDelegator()); + this.canvasContext.setTooltipProvider( null ); + + this.canvasContext = null; + this.focusAdapter = null; + this.mouseAdapter = null; + this.keyAdapter = null; + this.drop = null; + this.drag = null; + this.holder = null; + removeAll(); + + } + this.canvasContext = canvasContext; + // Hook new context + if (canvasContext!=null) { + + // SceneGraph event handling + addMouseListener(canvasContext.getSceneGraph().getEventDelegator()); + addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator()); + addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator()); + addKeyListener(canvasContext.getSceneGraph().getEventDelegator()); + addFocusListener(canvasContext.getSceneGraph().getEventDelegator()); + canvasContext.setTooltipProvider( new AWTTooltipProvider() ); + + //Create canvas context and a layer of interactors + canvasContext.getHintStack().addHintContext(hintCtx, 0); + canvasContext.getContentContext().addPaintableContextListener(contentListener); + canvasContext.getEventQueue().addQueueListener(queueListener); + canvasContext.getMouseCursorContext().addCursorListener(cursorListener); + + mouseAdapter = new AWTMouseEventAdapter(canvasContext, canvasContext.getEventQueue()); + if (hookKeyEvents) { + keyAdapter = new AWTKeyEventAdapter(canvasContext, canvasContext.getEventQueue()); + addKeyListener(keyAdapter); + } + focusAdapter = new AWTFocusAdapter(canvasContext, canvasContext.getEventQueue()); + addMouseListener(mouseAdapter); + addMouseMotionListener(mouseAdapter); + addMouseWheelListener(mouseAdapter); + addFocusListener(focusAdapter); + + canvasContext.add( drag=new DragInteractor(this) ); + canvasContext.add( drop=new DropInteractor(this) ); + + // FIXME: hack to work around this: + // FIXME: this should be true but is currently not. + //assert AWTThread.getThreadAccess().currentThreadAccess(); + Runnable initializeHolder = new Runnable() { + @Override + public void run() { + if (canvasContext.isDisposed()) + return; + if (holder == null) { + holder = new Holder(); +// holder.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); + holder.setSize(1, 1); + holder.setLocation(0, 0); + holder.setFocusable(false); + holder.setEnabled(true); + holder.setVisible(true); + holder.addMouseListener(canvasContext.getSceneGraph().getEventDelegator()); + holder.addMouseMotionListener(canvasContext.getSceneGraph().getEventDelegator()); + holder.addMouseWheelListener(canvasContext.getSceneGraph().getEventDelegator()); + holder.addKeyListener(canvasContext.getSceneGraph().getEventDelegator()); + holder.addFocusListener(canvasContext.getSceneGraph().getEventDelegator()); + //System.err.println("layout: " + holder.getLayout()); + AWTChassis.this.add(holder); + } + + // Use dummy holder as root pane. Swing components need a root pane, but we don't want swing to handle painting.. + canvasContext.getSceneGraph().setRootPane(holder, AWTChassis.this); + + holder.addMouseListener(mouseAdapter); + holder.addMouseMotionListener(mouseAdapter); + holder.addMouseWheelListener(mouseAdapter); + holder.addFocusListener(focusAdapter); + } + }; + if (AWTThread.getThreadAccess().currentThreadAccess()) + initializeHolder.run(); + else + AWTThread.getThreadAccess().asyncExec(initializeHolder); + } + buffer = null; + repaint(); + } + + @Override + public ICanvasContext getCanvasContext() { + return canvasContext; + } + + @Override + public void addChassisListener(IThreadWorkQueue thread, IChassisListener listener) { + listeners.add(thread, listener); + } + + @Override + public void removeChassisListener(IThreadWorkQueue thread, IChassisListener listener) { + listeners.remove(thread, listener); + } + + @Override + public void addChassisListener(IChassisListener listener) { + listeners.add(listener); + } + + @Override + public void removeChassisListener(IChassisListener listener) { + listeners.remove(listener); + } + + @Override + public IHintContext getHintContext() { + return hintCtx; + } + + public void setUseVolatileImage(boolean useVolatileImage) { + this.useVolatileImage = useVolatileImage; + } + + public boolean isUseVolatileImage() { + return useVolatileImage; + } + + private void paintScenegraph(Graphics2D g2d, Rectangle controlBounds) { + Color bg = getBackground(); + if (bg == null) + bg = Color.WHITE; + g2d.setBackground(bg); + g2d.clearRect(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height); + g2d.setClip(controlBounds.x, controlBounds.y, controlBounds.width, controlBounds.height); + g2d.setRenderingHint(G2DRenderingHints.KEY_CONTROL_BOUNDS, controlBounds); + if (!canvasContext.isLocked()) + canvasContext.getSceneGraph().render(g2d); + } + + /** + * Perform a best effort to render the current scene graph into a + * VolatileImage compatible with this chassis. + * + * @param g2d + * @param b + * @return the VolatileImage rendered if successful, null if + * rendering fails + */ + private VolatileImage paintToVolatileImage(Graphics2D g2d, Rectangle b) { + int attempts = 0; + do { + // Prevent eternal looping experienced by Kalle. + // Caused by buffer.contentsLost failing constantly + // for the buffer created by createVolatileImage(w, h) below. + // This happens often with the following sequence: + // - two displays, application window on the second display + // - lock windows, (possibly wait a while/get coffee) + // - unlock windows + // Also using an attempt count to let the code fall back to a + // slower rendering path if volatile images are causing trouble. + if (closed || attempts >= 10) + return null; + + if (buffer == null + || b.width != buffer.getWidth() + || b.height != buffer.getHeight() + || buffer.validate(g2d.getDeviceConfiguration()) == VolatileImage.IMAGE_INCOMPATIBLE) + { + + buffer = createVolatileImage(b.width, b.height); + } + if (buffer == null) + return null; + +// ImageCapabilities caps = g2d.getDeviceConfiguration().getImageCapabilities(); +// System.out.println("CAPS: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile()); +// caps = buffer.getCapabilities(); +// System.out.println("CAPS2: " + caps + ", accelerated=" + caps.isAccelerated() + ", true volatile=" + caps.isTrueVolatile()); + + Graphics2D bg = buffer.createGraphics(); + paintScenegraph(bg, b); + bg.dispose(); + + ++attempts; + } while (buffer.contentsLost()); + + // Successfully rendered to buffer! + return buffer; + } + + @Override + public void paintComponent(Graphics g) { + dirty = false; + if (canvasContext == null) + return; + + Graphics2D g2d = (Graphics2D) g; + Rectangle b = getBounds(); + + long startmem = 0, start = 0; + if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) { + startmem = Runtime.getRuntime().freeMemory(); + start = System.nanoTime(); + } + VolatileImage buffer = null; + if (useVolatileImage) + buffer = paintToVolatileImage(g2d, b); + if (closed) + return; + if (DebugPolicy.PERF_CHASSIS_RENDER_FRAME) { + long end = System.nanoTime(); + long endmem = Runtime.getRuntime().freeMemory(); + System.out.println("frame render: " + ((end-start)*1e-6) + " ms, " + (startmem-endmem)/(1024.0*1024.0) + " MB"); + } + + if (buffer != null) { + // Successfully buffered the scenegraph, copy the image to screen. + + // DEBUG +// try { +// File frame = new File("d:/frames/frame" + (++imageNo) + ".png"); +// System.out.println("writing frame: " + frame); +// ImageIO.write(buffer.getSnapshot(), "PNG", frame); +// } catch (IOException e) { +// e.printStackTrace(); +// } + + g2d.drawImage(buffer, 0, 0, null); + } else { + // Failed to paint volatile image, paint directly to the provided + // graphics context. + paintScenegraph(g2d, b); + +// g2d.setFont(Font.getFont("Arial 14")); +// g2d.setColor(Color.RED); +// FontMetrics fm = g2d.getFontMetrics(); +// Rectangle2D r = fm.getStringBounds(PROBLEM, g2d); +// g2d.drawString(PROBLEM, (int) (b.width-r.getWidth()), (int) (b.getHeight()-fm.getMaxDescent())); + } + } + + private final static Method CLOSED_METHOD = SyncListenerList.getMethod(IChassisListener.class, "chassisClosed"); + protected void fireChassisClosed() + { + closed = true; + Executable e[] = listeners.getExecutables(CLOSED_METHOD, this); + ThreadUtils.multiSyncExec(e); + } + + @Override + public boolean contains(int x, int y) { + // TODO Auto-generated method stub + return super.contains(x, y); + } + +}