diff options
author | Sven Gothel <[email protected]> | 2012-01-09 08:36:18 -0800 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2012-01-09 08:36:18 -0800 |
commit | 8b66da812b21725c8d0ca309f476b92be282ec8e (patch) | |
tree | 97906963c6396985d15eb5de92913f0245987e4f | |
parent | d959cf4c79ff5b44a907f6cefffab2234ff12eff (diff) | |
parent | 78c45f888d831c700227c4f6f3bf6aa0b30aa247 (diff) |
Merge pull request #41 from krisher/master
More complete implementation of SWT GLCanvas
-rw-r--r-- | make/build-common.xml | 2 | ||||
-rw-r--r-- | make/build-jogl.xml | 23 | ||||
-rw-r--r-- | src/jogl/classes/jogamp/opengl/swt/GLCanvas.java | 652 |
3 files changed, 673 insertions, 4 deletions
diff --git a/make/build-common.xml b/make/build-common.xml index 79d55b5f2..a7fef8ba6 100644 --- a/make/build-common.xml +++ b/make/build-common.xml @@ -291,6 +291,7 @@ <property name="jogl.glumipmap.jar" value="${build.jogl}/jogl.glu.mipmap.jar" /> <property name="jogl.util.fixedfuncemu.jar" value="${build.jogl}/jogl.util.fixedfuncemu.jar" /> <property name="jogl.awt.jar" value="${build.jogl}/jogl.awt.jar" /> + <property name="jogl.swt.jar" value="${build.jogl}/jogl.swt.jar" /> <property name="jogl.util.awt.jar" value="${build.jogl}/jogl.util.awt.jar" /> <property name="jogl.os.x11.jar" value="${build.jogl}/jogl.os.x11.jar" /> <property name="jogl.os.win.jar" value="${build.jogl}/jogl.os.win.jar" /> @@ -312,6 +313,7 @@ <pathelement location="${jogl.glumipmap.jar}" /> <pathelement location="${jogl.util.fixedfuncemu.jar}" /> <pathelement location="${jogl.awt.jar}" /> + <pathelement location="${jogl.swt.jar}" /> <pathelement location="${jogl.util.awt.jar}" /> <pathelement location="${jogl.os.x11.jar}" /> <pathelement location="${jogl.os.win.jar}" /> diff --git a/make/build-jogl.xml b/make/build-jogl.xml index b14824ec4..02222225c 100644 --- a/make/build-jogl.xml +++ b/make/build-jogl.xml @@ -139,6 +139,9 @@ <property name="java.part.awt" value="javax/media/opengl/awt/** jogamp/opengl/**/awt/**"/> + + <property name="java.part.swt" + value="jogamp/opengl/**/swt/**"/> <property name="java.part.util" value="com/jogamp/opengl/util/* com/jogamp/opengl/util/texture/** com/jogamp/opengl/util/packrect/** jogamp/opengl/util/*"/> @@ -180,13 +183,18 @@ <isset property="setup.noAWT"/> </condition> + <condition property="java.excludes.swt" + value="${java.part.swt}"> + <isset property="setup.noSWT"/> + </condition> + <property name="java.excludes.javadoc.packagenames" value="jogamp.opengl.gl2.fixme.*,com.jogamp.audio.windows.waveout.TestSpatialization"/> <property name="java.excludes.fixme" value="jogamp/opengl/gl2/fixme/** com/jogamp/audio/windows/waveout/TestSpatialization.java" /> - <property name="java.excludes.all" value="${java.excludes.fixme} ${java.excludes.awt}" /> + <property name="java.excludes.all" value="${java.excludes.fixme} ${java.excludes.awt} ${java.excludes.swt}" /> <echo message="java.excludes.all: ${java.excludes.all}" /> </target> @@ -1516,7 +1524,7 @@ <jar manifest="${build.jogl}/manifest.mf" destfile="${jogl.glmobile.jar}" filesonly="true"> <fileset dir="${classes}" includes="${java.part.egl} ${java.part.es1} ${java.part.es2}" - excludes="${java.part.awt} ${java.part.es1.dbg} ${java.part.es2.dbg}"/> + excludes="${java.part.awt} ${java.part.swt} ${java.part.es1.dbg} ${java.part.es2.dbg}"/> </jar> <jar manifest="${build.jogl}/manifest.mf" destfile="${jogl.glmobile.dbg.jar}" filesonly="true"> <fileset dir="${classes}" @@ -1535,6 +1543,13 @@ </jar> </target> + <target name="build-jars-swt-javase" depends="setup-manifestfile" unless="setup.noSWT"> + <jar manifest="${build.jogl}/manifest.mf" destfile="${jogl.swt.jar}" filesonly="true"> + <fileset dir="${classes}" + includes="${java.part.swt}" /> + </jar> + </target> + <target name="build-jars-desktop-javase" depends="setup-manifestfile,build-jars-os-desktop-javase"> <!--os specific gldesktop--> <jar manifest="${build.jogl}/manifest.mf" destfile="${jogl.gldesktop.jar}" filesonly="true"> @@ -1555,11 +1570,11 @@ <jar manifest="${build.jogl}/manifest.mf" destfile="${jogl.util.gldesktop.jar}" filesonly="true"> <fileset dir="${classes}" includes="${java.part.util.gldesktop}" - excludes="${java.part.awt} ${java.part.util.awt}"/> + excludes="${java.part.awt} ${java.part.util.awt} ${java.part.swt}"/> </jar> </target> - <target name="build-jars-javase" depends="setup-manifestfile, build-jars-mobile-javase, build-jars-desktop-javase, build-jars-awt-javase"> + <target name="build-jars-javase" depends="setup-manifestfile, build-jars-mobile-javase, build-jars-desktop-javase, build-jars-awt-javase, build-jars-swt-javase"> <jar manifest="${build.jogl}/manifest.mf" destfile="${jogl.core.jar}" filesonly="true"> <fileset dir="${classes}" includes="${java.part.core}" diff --git a/src/jogl/classes/jogamp/opengl/swt/GLCanvas.java b/src/jogl/classes/jogamp/opengl/swt/GLCanvas.java new file mode 100644 index 000000000..d8c1f33d1 --- /dev/null +++ b/src/jogl/classes/jogamp/opengl/swt/GLCanvas.java @@ -0,0 +1,652 @@ +/** + * + */ +package jogamp.opengl.swt; + +import javax.media.nativewindow.AbstractGraphicsDevice; +import javax.media.nativewindow.NativeSurface; +import javax.media.nativewindow.ProxySurface; +import javax.media.opengl.GL; +import javax.media.opengl.GLAnimatorControl; +import javax.media.opengl.GLAutoDrawable; +import javax.media.opengl.GLCapabilities; +import javax.media.opengl.GLCapabilitiesChooser; +import javax.media.opengl.GLCapabilitiesImmutable; +import javax.media.opengl.GLContext; +import javax.media.opengl.GLDrawable; +import javax.media.opengl.GLDrawableFactory; +import javax.media.opengl.GLEventListener; +import javax.media.opengl.GLException; +import javax.media.opengl.GLProfile; +import javax.media.opengl.GLRunnable; +import javax.media.opengl.Threading; + +import jogamp.nativewindow.swt.SWTAccessor; +import jogamp.opengl.GLContextImpl; +import jogamp.opengl.GLDrawableHelper; +import jogamp.opengl.ThreadingImpl; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import com.jogamp.common.util.locks.LockFactory; +import com.jogamp.common.util.locks.RecursiveLock; + +/** + * + */ +public class GLCanvas extends Canvas implements GLAutoDrawable { + + /* + * Flag for whether the SWT thread should be used for OpenGL calls when in single-threaded mode. This is controlled + * by the setting of the threading mode to worker (do not use SWT thread), awt (use SWT thread), or false (always use + * calling thread). + * + * @see Threading + * + * Now done dynamically to avoid early loading of gluegen library. + */ + //private static final boolean useSWTThread = ThreadingImpl.getMode() != ThreadingImpl.WORKER; + + /* GL Stuff */ + private final GLDrawableHelper drawableHelper = new GLDrawableHelper(); + private GLDrawable drawable; + private GLContext context; + + /* Native window surface */ + private AbstractGraphicsDevice device; + private final long nativeWindowHandle; + private final ProxySurface proxySurface; + + /* Construction parameters stored for GLAutoDrawable accessor methods */ + private int ctxCreationFlags = 0; + + private final GLCapabilitiesImmutable glCapsRequested; + + /* + * Lock for access to GLDrawable, as used in GLCanvas, + */ + private final RecursiveLock lock = LockFactory.createRecursiveLock(); + + /* Flag indicating whether an unprocessed reshape is pending. */ + private volatile boolean sendReshape; + + /* + * Invokes init(...) on all GLEventListeners. Assumes context is current when run. + */ + private final Runnable initAction = new Runnable() { + @Override + public void run() { + drawableHelper.init(GLCanvas.this); + } + }; + + /* + * Action to handle display in OpenGL, also processes reshape since they should be done at the same time. + * + * Assumes GLContext is current when run. + */ + private final Runnable displayAction = new Runnable() { + @Override + public void run() { + if (sendReshape) { + drawableHelper.reshape(GLCanvas.this, 0, 0, getWidth(), getHeight()); + sendReshape = false; + } + drawableHelper.display(GLCanvas.this); + } + }; + + /* Action to make specified context current prior to running displayAction */ + private final Runnable makeCurrentAndDisplayAction = new Runnable() { + @Override + public void run() { + drawableHelper.invokeGL(drawable, context, displayAction, initAction); + } + }; + + /* Swaps buffers, assuming the GLContext is current */ + private final Runnable swapBuffersAction = new Runnable() { + @Override + public void run() { + drawable.swapBuffers(); + } + }; + + /* Swaps buffers, making the GLContext current first */ + private final Runnable makeCurrentAndSwapBuffersAction = new Runnable() { + @Override + public void run() { + drawableHelper.invokeGL(drawable, context, swapBuffersAction, initAction); + } + }; + + /* + * Disposes of OpenGL resources + */ + private final Runnable disposeGLAction = new Runnable() { + @Override + public void run() { + drawableHelper.dispose(GLCanvas.this); + + if (null != context) { + context.makeCurrent(); // implicit wait for lock .. + context.destroy(); + context = null; + } + + if (null != drawable) { + drawable.setRealized(false); + drawable = null; + } + } + }; + + private final Runnable makeCurrentAndDisposeGLAction = new Runnable() { + @Override + public void run() { + drawableHelper.invokeGL(drawable, context, disposeGLAction, null); + } + }; + + private final Runnable disposeGraphicsDeviceAction = new Runnable() { + @Override + public void run() { + if (null != device) { + device.close(); + device = null; + } + } + }; + + /** + * Creates a new SWT GLCanvas. + * + * @param parent + * Required (non-null) parent Composite. + * @param style + * Optional SWT style bit-field. The {@link SWT#NO_BACKGROUND} bit is set before passing this up to the + * Canvas constructor, so OpenGL handles the background. + * @param caps + * Optional GLCapabilities. If not provided, the default capabilities for the default GLProfile for the + * graphics device determined by the parent Composite are used. Note that the GLCapabilities that are + * actually used may differ based on the capabilities of the graphics device. + * @param chooser + * Optional GLCapabilitiesChooser to customize the selection of the used GLCapabilities based on the + * requested GLCapabilities, and the available capabilities of the graphics device. + * @param shareWith + * Optional GLContext to share state (textures, vbos, shaders, etc.) with. + */ + public GLCanvas(final Composite parent, final int style, final GLCapabilities caps, + final GLCapabilitiesChooser chooser, final GLContext shareWith) { + /* NO_BACKGROUND required to avoid clearing bg in native SWT widget (we do this in the GL display) */ + super(parent, style | SWT.NO_BACKGROUND); + + SWTAccessor.setRealized(this, true); + + /* Get the nativewindow-Graphics Device associated with this control (which is determined by the parent Composite) */ + device = SWTAccessor.getDevice(this); + /* Native handle for the control, used to associate with GLContext */ + nativeWindowHandle = SWTAccessor.getWindowHandle(this); + + /* Select default GLCapabilities if none was provided, otherwise clone provided caps to ensure safety */ + final GLCapabilitiesImmutable fixedCaps = (caps == null) ? new GLCapabilities(GLProfile.getDefault(device)) + : (GLCapabilitiesImmutable) caps.cloneMutable(); + glCapsRequested = fixedCaps; + + final GLDrawableFactory glFactory = GLDrawableFactory.getFactory(fixedCaps.getGLProfile()); + + /* Create a NativeWindow proxy for the SWT canvas */ + proxySurface = glFactory.createProxySurface(device, nativeWindowHandle, fixedCaps, chooser); + + /* Associate a GL surface with the proxy */ + drawable = glFactory.createGLDrawable(proxySurface); + drawable.setRealized(true); + + context = drawable.createContext(shareWith); + + /* Register SWT listeners (e.g. PaintListener) to render/resize GL surface. */ + /* TODO: verify that these do not need to be manually de-registered when destroying the SWT component */ + addPaintListener(new PaintListener() { + + @Override + public void paintControl(final PaintEvent arg0) { + if (!drawableHelper.isExternalAnimatorAnimating()) { + display(); + } + } + }); + addControlListener(new ControlAdapter() { + + @Override + public void controlResized(final ControlEvent arg0) { + /* Mark for OpenGL reshape next time the control is painted */ + sendReshape = true; + } + }); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#addGLEventListener(javax.media.opengl.GLEventListener) + */ + @Override + public void addGLEventListener(final GLEventListener arg0) { + drawableHelper.addGLEventListener(arg0); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#addGLEventListener(int, javax.media.opengl.GLEventListener) + */ + @Override + public void addGLEventListener(final int arg0, final GLEventListener arg1) throws IndexOutOfBoundsException { + drawableHelper.addGLEventListener(arg0, arg1); + } + + /** + * {@inheritDoc} + * <p> + * Also disposes of the SWT component. + */ + @Override + public void destroy() { + drawable.setRealized(false); + dispose(); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#display() + */ + @Override + public void display() { + runInGLThread(makeCurrentAndDisplayAction, displayAction); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#getAnimator() + */ + @Override + public GLAnimatorControl getAnimator() { + return drawableHelper.getAnimator(); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#getAutoSwapBufferMode() + */ + @Override + public boolean getAutoSwapBufferMode() { + return drawableHelper.getAutoSwapBufferMode(); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#getContext() + */ + @Override + public GLContext getContext() { + return context; + } + + /* + * @see javax.media.opengl.GLAutoDrawable#getContextCreationFlags() + */ + @Override + public int getContextCreationFlags() { + return ctxCreationFlags; + } + + /* + * @see javax.media.opengl.GLAutoDrawable#getGL() + */ + @Override + public GL getGL() { + final GLContext ctx = getContext(); + return (ctx == null) ? null : ctx.getGL(); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#invoke(boolean, javax.media.opengl.GLRunnable) + */ + @Override + public void invoke(final boolean wait, final GLRunnable run) { + /* Queue task for running during the next display(). */ + drawableHelper.invoke(this, wait, run); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#removeGLEventListener(javax.media.opengl.GLEventListener) + */ + @Override + public void removeGLEventListener(final GLEventListener arg0) { + drawableHelper.removeGLEventListener(arg0); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#setAnimator(javax.media.opengl.GLAnimatorControl) + */ + @Override + public void setAnimator(final GLAnimatorControl arg0) throws GLException { + drawableHelper.setAnimator(arg0); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#setAutoSwapBufferMode(boolean) + */ + @Override + public void setAutoSwapBufferMode(final boolean arg0) { + drawableHelper.setAutoSwapBufferMode(arg0); + } + + /* + * @see javax.media.opengl.GLAutoDrawable#setContext(javax.media.opengl.GLContext) + */ + @Override + public void setContext(final GLContext ctx) { + this.context = ctx; + if (ctx instanceof GLContextImpl) { + ((GLContextImpl) ctx).setContextCreationFlags(ctxCreationFlags); + } + } + + /* + * @see javax.media.opengl.GLAutoDrawable#setContextCreationFlags(int) + */ + @Override + public void setContextCreationFlags(final int arg0) { + ctxCreationFlags = arg0; + } + + /* + * @see javax.media.opengl.GLAutoDrawable#setGL(javax.media.opengl.GL) + */ + @Override + public GL setGL(final GL arg0) { + final GLContext ctx = getContext(); + if (ctx != null) { + ctx.setGL(arg0); + return arg0; + } + return null; + } + + /* + * @see javax.media.opengl.GLDrawable#createContext(javax.media.opengl.GLContext) + */ + @Override + public GLContext createContext(final GLContext arg0) { + lock.lock(); + try { + final GLDrawable drawable = this.drawable; + return (drawable != null) ? drawable.createContext(arg0) : null; + } finally { + lock.unlock(); + } + } + + /* + * @see javax.media.opengl.GLDrawable#getChosenGLCapabilities() + */ + @Override + public GLCapabilitiesImmutable getChosenGLCapabilities() { + return (GLCapabilitiesImmutable)proxySurface.getGraphicsConfiguration().getChosenCapabilities(); + } + + /** + * Accessor for the GLCapabilities that were requested (via the constructor parameter). + * + * @return Non-null GLCapabilities. + */ + public GLCapabilitiesImmutable getRequestedGLCapabilities() { + return (GLCapabilitiesImmutable)proxySurface.getGraphicsConfiguration().getRequestedCapabilities(); + } + + /* + * @see javax.media.opengl.GLDrawable#getFactory() + */ + @Override + public GLDrawableFactory getFactory() { + lock.lock(); + try { + final GLDrawable drawable = this.drawable; + return (drawable != null) ? drawable.getFactory() : null; + } finally { + lock.unlock(); + } + } + + /* + * @see javax.media.opengl.GLDrawable#getGLProfile() + */ + @Override + public GLProfile getGLProfile() { + return glCapsRequested.getGLProfile(); + } + + /* + * @see javax.media.opengl.GLDrawable#getHandle() + */ + @Override + public long getHandle() { + lock.lock(); + try { + final GLDrawable drawable = this.drawable; + return (drawable != null) ? drawable.getHandle() : 0; + } finally { + lock.unlock(); + } + } + + /* + * @see javax.media.opengl.GLDrawable#getHeight() + */ + @Override + public int getHeight() { + return getClientArea().height; + } + + /* + * @see javax.media.opengl.GLDrawable#getNativeSurface() + */ + @Override + public NativeSurface getNativeSurface() { + lock.lock(); + try { + final GLDrawable drawable = this.drawable; + return (drawable != null) ? drawable.getNativeSurface() : null; + } finally { + lock.unlock(); + } + } + + /* + * @see javax.media.opengl.GLDrawable#getWidth() + */ + @Override + public int getWidth() { + return getClientArea().width; + } + + /* + * @see javax.media.opengl.GLDrawable#isRealized() + */ + @Override + public boolean isRealized() { + lock.lock(); + try { + final GLDrawable drawable = this.drawable; + return (drawable != null) ? drawable.isRealized() : false; + } finally { + lock.unlock(); + } + } + + /* + * @see javax.media.opengl.GLDrawable#setRealized(boolean) + */ + @Override + public void setRealized(final boolean arg0) { + /* Intentionally empty */ + } + + /* + * @see javax.media.opengl.GLDrawable#swapBuffers() + */ + @Override + public void swapBuffers() throws GLException { + runInGLThread(makeCurrentAndSwapBuffersAction, swapBuffersAction); + } + + /* + * @see mil.afrl.rrs.ifsb.jview.graph.graph3d.RenderSurface#update() + */ + @Override + public void update() { +// display(); + } + + /* + * @see mil.afrl.rrs.ifsb.jview.graph.graph3d.RenderSurface#dispose() + */ + @Override + public void dispose() { + lock.lock(); + try { + final Display display = getDisplay(); + + if (null != context) { + boolean animatorPaused = false; + final GLAnimatorControl animator = getAnimator(); + if (null != animator) { + // can't remove us from animator for recreational addNotify() + animatorPaused = animator.pause(); + } + if (Threading.isSingleThreaded() && !Threading.isOpenGLThread()) { + runInDesignatedGLThread(makeCurrentAndDisposeGLAction); + } else if (context.isCreated()) { + drawableHelper.invokeGL(drawable, context, disposeGLAction, null); + } + + if (animatorPaused) { + animator.resume(); + } + } + if (display.getThread() == Thread.currentThread()) + disposeGraphicsDeviceAction.run(); + else + display.syncExec(disposeGraphicsDeviceAction); + } finally { + lock.unlock(); + } + super.dispose(); + } + + /** + * Determines whether the current thread is the appropriate thread to use the GLContext in. If we are using one of + * the single-threaded policies in {@link Threading}, than this is either the SWT event dispatch thread, or the + * OpenGL worker thread depending on the state of {@link #useSWTThread}. Otherwise this always returns true because + * the threading model is user defined. + * <p> + * TODO: should this be moved to {@link Threading}? + * + * @return true if the calling thread is the correct thread to execute OpenGL calls in, false otherwise. + */ + protected boolean isRenderThread() { + if (Threading.isSingleThreaded()) { + if (ThreadingImpl.getMode() != ThreadingImpl.WORKER) { + final Display display = getDisplay(); + return display != null && display.getThread() == Thread.currentThread(); + } + return Threading.isOpenGLThread(); + } + /* + * For multi-threaded rendering, the render thread is not defined... + */ + return true; + } + + /** + * Runs the specified action in the designated OpenGL thread. If the current thread is designated, then the + * syncAction is run synchronously, otherwise the asyncAction is dispatched to the appropriate worker thread. + * + * @param asyncAction + * The non-null action to dispatch to an OpenGL worker thread. This action should not assume that a + * GLContext is current when invoked. + * @param syncAction + * The non-null action to run synchronously if the current thread is designated to handle OpenGL calls. + * This action may assume the GLContext is current. + */ + private void runInGLThread(final Runnable asyncAction, final Runnable syncAction) { + if (Threading.isSingleThreaded() && !isRenderThread()) { + /* Run in designated GL thread */ + runInDesignatedGLThread(asyncAction); + } else { + /* Run in current thread... */ + drawableHelper.invokeGL(drawable, context, syncAction, initAction); + } + } + + /** + * Dispatches the specified runnable to the appropriate OpenGL worker thread (either the SWT event dispatch thread, + * or the OpenGL worker thread depending on the state of {@link #useSWTThread}). + * + * @param makeCurrentAndRunAction + * The non-null action to dispatch. + */ + private void runInDesignatedGLThread(final Runnable makeCurrentAndRunAction) { + if (ThreadingImpl.getMode() != ThreadingImpl.WORKER) { + final Display display = getDisplay(); + assert display.getThread() != Thread.currentThread() : "Incorrect use of thread dispatching."; + display.syncExec(makeCurrentAndRunAction); + } else { + Threading.invokeOnOpenGLThread(makeCurrentAndRunAction); + } + } + + + public static void main(final String[] args) { + GLProfile.initSingleton(true); + final Display display = new Display(); + final Shell shell = new Shell(display); + shell.setSize(800,600); + shell.setLayout(new FillLayout()); + + final GLCanvas canvas = new GLCanvas(shell, + 0, null, null, null); + + canvas.addGLEventListener(new GLEventListener() { + + @Override + public void reshape(final GLAutoDrawable drawable, final int x, final int y, final int width, final int height) { + System.out.println("Reshape"); + } + + @Override + public void init(final GLAutoDrawable drawable) { + System.out.println("Init"); + } + + @Override + public void dispose(final GLAutoDrawable drawable) { + System.out.println("Dispose"); + } + + @Override + public void display(final GLAutoDrawable drawable) { + System.out.println("Display"); + } + }); + shell.setSize(500, 500); + shell.open(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + } +} |