/*
* Copyright (c) 2003-2009 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.swingGui;
import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.awt.event.FocusEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.beans.PropertyVetoException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JInternalFrame;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JViewport;
import javax.swing.Popup;
import javax.swing.PopupFactory;
import javax.swing.RepaintManager;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicInternalFrameUI;
import com.jme3.asset.AssetManager;
import com.jme3.input.InputManager;
import com.jme3.input.KeyInput;
import com.jme3.input.RawInputListener;
import com.jme3.input.event.JoyAxisEvent;
import com.jme3.input.event.JoyButtonEvent;
import com.jme3.input.event.KeyInputEvent;
import com.jme3.input.event.MouseButtonEvent;
import com.jme3.input.event.MouseMotionEvent;
import com.jme3.input.event.TouchEvent;
import com.jme3.material.Material;
import com.jme3.material.TechniqueDef.LightMode;
import com.jme3.math.Ray;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Quad;
import com.jme3.swingGui.dnd.JMEDragAndDrop;
import com.jme3.system.AppSettings;
import com.jme3.system.JmeSystem;
import com.jme3.texture.Texture;
import com.jme3.texture.Texture2D;
/**
* A quad that displays a {@link JDesktopPane} as texture. It also converts jME mouse and keyboard events to Swing
* events. The latter does work for ortho mode only. There are some issues with using multiple of this desktops.
* <p>
* Notes
* <ul>
* <li> Only access the Swing UI from the Swing event dispatch thread! See {@link SwingUtilities#invokeLater}
* and <a href="http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html">Swing tutorial</a> for details.</li>
* <li>If you would like to change the L&F take into account that PropertiesDialog and PropertiesDialog2 change
* the L&F setting upon invocation (you can e.g. change the L&F after the dialog).</li>
* </ul>
*
* @author Portet to jme3 by user starcom "Paul Kashofer Austria"
*
* @see ImageGraphics
*/
public class JMEDesktop extends Geometry {
private static final Logger logger = Logger.getLogger(JMEDesktop.class
.getName());
private static final long serialVersionUID = 1L;
private ImageGraphics graphics;
private JDesktopPane desktop;
private Texture texture;
private boolean initialized;
private int width;
private int height;
private boolean showingJFrame = false;
private final Frame awtWindow;
private int desktopWidth;
private int desktopHeight;
private static final int DOUBLE_CLICK_TIME = 300;
private RawInputListener rawInputListener; // The Listener, which handles the Events;
private InputManager inputManager; // The Parent InputManager
private JMEDragAndDrop dragAndDropSupport;
private AppSettings settings;
private RenderManager renderer;
private boolean disposing = false;
/**
* @return JMEDragAndDrop used for this desktop
*/
public JMEDragAndDrop getDragAndDropSupport() {
return dragAndDropSupport;
}
/**
* @param dragAndDropSupport JMEDragAndDrop to be used for this desktop
* @see JMEDragAndDrop#setDesktop(JMEDesktop)
*/
public void setDragAndDropSupport( JMEDragAndDrop dragAndDropSupport ) {
this.dragAndDropSupport = dragAndDropSupport;
}
/**
* @see #setShowingJFrame
* @return true if frame is displayed
*/
public boolean isShowingJFrame() {
return showingJFrame;
}
/**
* @param showingJFrame true to display the desktop in a JFrame instead on this quad.
* @deprecated for debuggin only
*/
public void setShowingJFrame( boolean showingJFrame ) {
this.showingJFrame = showingJFrame;
awtWindow.setVisible( showingJFrame );
awtWindow.repaint();
}
/**
* Allows to disable input for the whole desktop and to add custom input actions.
*
* @return this desktops input hander for input bindings
* @see #getXUpdateAction()
* @see #getYUpdateAction()
* @see #getWheelUpdateAction()
* @see #getButtonUpdateAction(int)
* @see #getKeyUpdateAction()
*/
public InputManager getInputManager() {
return inputManager;
}
/**
* Create a quad with a Swing-Texture. Creates the quad and the JFrame but do not setup the rest.
* Call {@link #setup(int, int, boolean, InputManager)} to finish setup.
*
* @param name name of this desktop
*/
private JMEDesktop( String name ) {
super(name);
/*
Quad quad = new Quad(2,2)
{
@Override
public void updateGeometry(float x, float y)
{
this.updateGeometry(x,y,false);
}
@Override
public void updateGeometry(float x, float y, boolean flip)
{
super.updateGeometry(x,y,flip);
setBuffer(Type.Position, 3, new float[]{0, 0, 0,
width, 0, 0,
width, height, 0,
0, height, 0
});
// setBuffer(Type.Position, 3, new float[]{(-width / 2f),(height / 2f),0, (-width / 2f),(-height / 2f),0, (width / 2f),(-height / 2f),0, (width / 2f),(height / 2f),0 });
}
};
*/
//setMesh(quad);
setMesh(new Quad());
awtWindow = new Frame() {
private static final long serialVersionUID = 1L;
public boolean isShowing() {
return true;
}
public boolean isVisible() {
// if ( new Throwable().getStackTrace()[1].getMethodName().startsWith( "requestFocus" ) ) {
// logger.info( "requestFocus" );
// }
if ( awtWindow.isFocusableWindow()
&& new Throwable().getStackTrace()[1].getMethodName().startsWith( "requestFocus" ) ) {
return false;
}
return initialized || super.isVisible();
}
public Graphics getGraphics() {
if ( !showingJFrame ) {
return graphics == null ? super.getGraphics() : graphics.create();
}
return super.getGraphics();
}
public boolean isFocused() {
return true;
}
};
awtWindow.setFocusableWindowState( false );
Container contentPane = awtWindow;
awtWindow.setUndecorated( true );
dontDrawBackground( contentPane );
// ( (JComponent) contentPane ).setOpaque( false );
desktop = new JDesktopPane() {
private static final long serialVersionUID = 1L;
public void paint( Graphics g ) {
if ( !isShowingJFrame() ) {
g.clearRect( 0, 0, getWidth(), getHeight() );
}
super.paint( g );
}
public boolean isOptimizedDrawingEnabled() {
return false;
}
};
new ScrollPaneRepaintFixListener().addTo( desktop );
final Color transparent = new Color( 0, 0, 0, 0 );
desktop.setBackground( transparent );
desktop.setFocusable( true );
desktop.addMouseListener( new MouseAdapter() {
public void mousePressed( MouseEvent e ) {
desktop.requestFocusInWindow();
}
} );
// this internal frame is a workaround for key binding problems in JDK1.5
// todo: this workaround does not seem to work on mac
if ( System.getProperty( "os.name" ).toLowerCase().indexOf( "mac" ) < 0 ) {
final JInternalFrame internalFrame = new JInternalFrame();
internalFrame.setUI( new BasicInternalFrameUI( internalFrame ) {
protected void installComponents() {
}
} );
internalFrame.setOpaque( false );
internalFrame.setBackground( null );
internalFrame.getContentPane().setLayout( new BorderLayout() );
internalFrame.getContentPane().add( desktop, BorderLayout.CENTER );
internalFrame.setVisible( true );
internalFrame.setBorder( null );
contentPane.add( internalFrame );
}
else {
// this would have suited for JDK1.4:
contentPane.add( desktop, BorderLayout.CENTER );
}
awtWindow.pack();
RepaintManager.currentManager( null ).setDoubleBufferingEnabled( false );
}
/**
* Create a quad with a Swing-Texture.
* Note that for the texture a width and height that is a power of 2 is used if the graphics card does
* not support the specified size for textures. E.g. this results in a 1024x512
* texture for a 640x480 desktop (consider using a 512x480 desktop in that case).
*
* @param name name of the spatial
* @param width desktop width
* @param height desktop height
* @param inputHandlerParent InputManager where the InputListener of this desktop should be added as subhandler.
* @see #getInputManager()
*/
public JMEDesktop( String name, final int width, final int height, InputManager inputHandlerParent , AppSettings settings, RenderManager renderer) {
this( name, width, height, false, inputHandlerParent, settings, renderer );
}
/**
* Create a quad with a Swing-Texture.
* Note that for the texture a width and height that is a power of 2 is used if the graphics card does
* not support the specified size for textures or mipMapping is true. E.g. this results in a 1024x512
* texture for a 640x480 desktop (consider using a 512x480 desktop in that case).
*
* @param name name of the spatial
* @param width desktop width
* @param height desktop hieght
* @param mipMapping true to compute mipmaps for the desktop (not recommended), false for creating
* a single image texture
* @param inputHandlerParent InputManager where the InputListener of this desktop should be added as subhandler.
* @see #getInputManager()
*/
public JMEDesktop( String name, final int width, final int height, boolean mipMapping, InputManager inputHandlerParent, AppSettings settings, RenderManager renderer ) {
this( name, width, height, 1280, 720, mipMapping, inputHandlerParent, settings, renderer );
}
/**
* Create a quad with a Swing-Texture.
* Note that for the texture a width and height that is a power of 2 is used if the graphics card does
* not support the specified size for textures or mipMapping is true. E.g. this results in a 1024x512
* texture for a 640x480 desktop (consider using a 512x480 desktop in that case).
*
* @param name name of the spatial
* @param width desktop width
* @param height desktop hieght
* @param scaleFromWidth default width, that will be scaled
* @param scaleFromHeight default height, that will be scaled
* @param mipMapping true to compute mipmaps for the desktop (not recommended), false for creating
* a single image texture
* @param inputHandlerParent InputManager where the InputListener of this desktop should be added as subhandler.
* @see #getInputManager()
*/
public JMEDesktop( String name, final int width, final int height, int scaleFromWidth, int scaleFromHeight, boolean mipMapping, InputManager inputHandlerParent, AppSettings settings, RenderManager renderer ) {
this( name );
this.renderer = renderer;
setup( width, height, scaleFromWidth, scaleFromHeight, mipMapping, inputHandlerParent, settings );
}
/**
* Set up the desktop quad - may be called only once.
* Note that for the texture a width and height that is a power of 2 is used if the graphics card does
* not support the specified size for textures or mipMapping is true. E.g. this results in a 1024x512
* texture for a 640x480 desktop (consider using a 512x480 desktop in that case).
*
* @param width desktop width
* @param height desktop hieght
* @param mipMapping true to compute mipmaps for the desktop (not recommended), false for creating
* a single image texture
* @param inputHandlerParent InputManager where the InputListener of this desktop should be added as subhandler,
* @see #getInputManager()
*/
private void setup( int width, int height, int unscaledWidth, int unscaledHeight, boolean mipMapping, InputManager inputHandlerParent, AppSettings settings ) {
// reconstruct( null, null, null, null );
if ( inputHandlerParent == null ) {
throw new IllegalStateException( "InputManager must not be null!" );
}
if ( initialized ) {
throw new IllegalStateException( "may be called only once" );
}
inputManager = inputHandlerParent;
this.settings = settings;
initInputActions(inputHandlerParent);
((Quad)getMesh()).updateGeometry( powerOf2SizeIfNeeded( unscaledWidth, mipMapping ), powerOf2SizeIfNeeded( unscaledHeight, mipMapping ), true );
this.width = powerOf2SizeIfNeeded( unscaledWidth, mipMapping );
this.height = powerOf2SizeIfNeeded( unscaledHeight, mipMapping );
// setModelBound( new BoundingBox() );
// updateModelBound();
desktop.setPreferredSize( new Dimension( unscaledWidth, unscaledHeight ) );
desktopWidth = unscaledWidth;
desktopHeight = unscaledHeight;
awtWindow.pack();
// TextureState ts = DisplaySystem.getDisplaySystem().getRenderer().createTextureState();
// ts.setCorrectionType( TextureState.CorrectionType.Perspective );
texture = new Texture2D();
texture.setMagFilter( Texture.MagFilter.Bilinear );
texture.setMinFilter( mipMapping ? Texture.MinFilter.Trilinear : Texture.MinFilter.BilinearNoMipMaps );
texture.setWrap( Texture.WrapMode.Repeat );
graphics = ImageGraphics.createInstance( this.width, this.height, mipMapping ? 2 : 0 , settings, renderer.getRenderer());
enableAntiAlias( graphics );
graphics.translate( ( this.width - unscaledWidth ) * 0.5f, ( this.height - unscaledHeight ) * 0.5f );
texture.setImage( graphics.getImage() );
// texture.setScale( new Vector3f( 1, -1, 1 ) );
// ts.setTexture( texture );
// this.setRenderState( ts );
AssetManager assetManager = JmeSystem.newAssetManager(Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg"));
Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
mat.setTransparent(true);
mat.getAdditionalRenderState().setBlendMode(com.jme3.material.RenderState.BlendMode.Alpha);
mat.selectTechnique("Default",renderer);
mat.getActiveTechnique().getDef().setLightMode(LightMode.Disable);
mat.setTexture("ColorMap", texture);
setMaterial(mat);
setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket.Gui);
//setQueueBucket(com.jme3.renderer.queue.RenderQueue.Bucket.Transparent);
//getLocalScale().set(new Vector3f(0.5f,0.5f,0.5f)); //Nur zum Testen
// mat = new Material(assetManager, "Common/MatDefs/Misc/ColoredTextured.j3md");
// mat.getActiveTechnique().getDef().setLightMode(LightMode.Disable);
// mat.setTexture("ColorMap", texture);
/*
BlendState alpha = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
alpha.setEnabled( true );
alpha.setBlendEnabled( true );
alpha.setSourceFunction( BlendState.SourceFunction.SourceAlpha );
alpha.setDestinationFunction( BlendState.DestinationFunction.OneMinusSourceAlpha );
alpha.setTestEnabled( true );
alpha.setTestFunction( BlendState.TestFunction.GreaterThan );
this.setRenderState( alpha );
*/
// Toolkit.getDefaultToolkit().addAWTEventListener( new AWTEventListener() {
// public void eventDispatched( AWTEvent event ) {
// if ( isShowingJFrame() ) {
// logger.info( event );
// }
// }
// }, 0xFFFFFFFFFFFFFFFFl );
if ( desktopsUsed == 0 ) {
PopupFactory.setSharedInstance( new MyPopupFactory() );
}
desktopsUsed++;
SwingUtilities.invokeLater( new Runnable() {
public void run() {
if (!disposing)JMEDesktop.this.setFocusOwner( desktop );
}
} );
initialized = true;
setSynchronizingThreadsOnUpdate( true );
setLocalScale(((float)width/(float)unscaledWidth),((float)height/(float)unscaledHeight),1.0f);
}
private void initInputActions(InputManager manager)
{
inputManager = manager;
rawInputListener = new RawInputListener()
{
@Override
public void beginInput() {}
@Override
public void endInput() {}
@Override
public void onJoyAxisEvent(JoyAxisEvent evt) {}
@Override
public void onJoyButtonEvent(JoyButtonEvent evt) {}
@Override
public void onMouseMotionEvent(MouseMotionEvent evt)
{
onMove(evt.getDX(), evt.getDY(), evt.getX(), evt.getY());
onWheel(evt.getDeltaWheel(), evt.getX(), evt.getY());
}
@Override
public void onMouseButtonEvent(MouseButtonEvent evt)
{
setButtonDown(evt.getButtonIndex(), evt.isPressed());
onButton( evt.getButtonIndex(), evt.isPressed(), lastXin, lastYin );
}
@Override
public void onKeyEvent(KeyInputEvent evt)
{
setKeyDown(evt.getKeyCode(), evt.isPressed());
onKey( evt.getKeyChar(), evt.getKeyCode(), evt.isPressed());
}
@Override
public void onTouchEvent(TouchEvent evt) {
// TODO Auto-generated method stub
}
};
manager.addRawInputListener(rawInputListener);
}
private static int desktopsUsed = 0;
//todo: reuse the runnables
//todo: possibly reuse events, too?
public void onKey( final char character, final int keyCode, final boolean pressed ) {
try {
SwingUtilities.invokeAndWait( new Runnable() {
public void run() {
if (!disposing)sendAWTKeyEvent( keyCode, pressed, character );
}
} );
} catch ( InterruptedException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"onKey(character, keyCode, pressed)", "Exception", e);
} catch ( InvocationTargetException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"onKey(character, keyCode, pressed)", "Exception", e);
}
}
public void onButton( final int swingButton, final boolean pressed, final int x, final int y ) {
convert( x, y, location );
final int awtX = (int) location.x;
final int awtY = (int) location.y;
final int awtSwingButton = convertButton(swingButton);
try {
SwingUtilities.invokeAndWait( new Runnable() {
public void run() {
if (!disposing)sendAWTMouseEvent( awtX, awtY, pressed, awtSwingButton );
}
} );
} catch ( InterruptedException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"onButton(swingButton, pressed, x, y)", "Exception", e);
} catch ( InvocationTargetException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"onButton(swingButton, pressed, x, y)", "Exception", e);
}
}
public void onWheel( final int wheelDelta, final int x, final int y ) {
convert( x, y, location );
final int awtX = (int) location.x;
final int awtY = (int) location.y;
try {
SwingUtilities.invokeAndWait( new Runnable() {
public void run() {
if (!disposing)sendAWTWheelEvent( wheelDelta, awtX, awtY );
}
} );
} catch ( InterruptedException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"onWheel(wheelDelta, x, y)", "Exception", e);
} catch ( InvocationTargetException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"onWheel(wheelDelta, x, y)", "Exception", e);
}
}
public void onMove( int xDelta, int yDelta, final int newX, final int newY ) {
convert( newX, newY, location );
final int awtX = (int) location.x;
final int awtY = (int) location.y;
try {
SwingUtilities.invokeAndWait( new Runnable() {
public void run() {
if (!disposing)sendAWTMouseEvent( awtX, awtY, false, MouseEvent.NOBUTTON );
}
} );
} catch ( InterruptedException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"onMove(xDelta, yDelta, newX, newY)", "Exception", e);
} catch ( InvocationTargetException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"onMove(xDelta, yDelta, newX, newY)", "Exception", e);
}
}
private boolean synchronizingThreadsOnUpdate;
/**
* @return true if update and swing thread should be synchronized (avoids flickering, eats some performance)
*/
public boolean isSynchronizingThreadsOnUpdate() {
return synchronizingThreadsOnUpdate;
}
/**
* Choose if update and swing thread should be synchronized (avoids flickering, eats some performance)
*
* @param synchronizingThreadsOnUpdate true to synchronize
*/
public void setSynchronizingThreadsOnUpdate( boolean synchronizingThreadsOnUpdate ) {
if ( this.synchronizingThreadsOnUpdate != synchronizingThreadsOnUpdate ) {
this.synchronizingThreadsOnUpdate = synchronizingThreadsOnUpdate;
}
}
private void enableAntiAlias( Graphics2D graphics ) {
RenderingHints hints = graphics.getRenderingHints();
if ( hints == null ) {
hints = new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
}
else {
hints.put( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
}
graphics.setRenderingHints( hints );
}
public Vector2f getMousePosition()
{
return new Vector2f((float)lastXin, (float)lastYin);
}
private static class LightWeightPopup extends Popup {
private static final Integer INTEGER_MAX_VALUE = Integer.MAX_VALUE;
public LightWeightPopup( JComponent desktop ) {
this.desktop = desktop;
new ScrollPaneRepaintFixListener().addTo( panel );
}
private final JComponent desktop;
JPanel panel = new JPanel( new BorderLayout() );
public void adjust( Component owner, Component contents, int x, int y ) {
panel.setVisible( false );
desktop.add( panel, INTEGER_MAX_VALUE );
panel.removeAll();
panel.add( contents, BorderLayout.CENTER );
if ( contents instanceof JComponent ) {
JComponent jComponent = (JComponent) contents;
jComponent.setDoubleBuffered( false );
}
panel.setSize( panel.getPreferredSize() );
y = Math.min( y, desktop.getHeight() - panel.getHeight() );
x = Math.min( x, desktop.getWidth() - panel.getWidth() );
panel.setLocation( x, y );
contents.invalidate();
panel.validate();
}
public void show() {
panel.setVisible( true );
}
public void hide() {
Rectangle bounds = panel.getBounds();
desktop.remove( panel );
desktop.repaint( bounds );
}
}
private void sendAWTKeyEvent( int keyCode, boolean pressed, char character ) {
keyCode = AWTKeyInput.toAWTCode( keyCode );
if ( keyCode != 0 ) {
Component focusOwner = getFocusOwner();
if ( focusOwner == null ) {
focusOwner = desktop;
}
if ( character == '\0' ) {
character = KeyEvent.CHAR_UNDEFINED;
}
if ( focusOwner != null ) {
if ( pressed ) {
KeyEvent event = new KeyEvent( focusOwner, KeyEvent.KEY_PRESSED,
System.currentTimeMillis(), getCurrentModifiers( -1 ),
keyCode, character );
dispatchEvent( focusOwner, event );
anInt.value = keyCode;
Char c = characters.get( anInt );
if ( c == null ) {
characters.put( new Int( keyCode ), new Char( character ) );
}
else {
c.value = character;
}
if ( character != KeyEvent.CHAR_UNDEFINED ) {
dispatchEvent( focusOwner, new KeyEvent( focusOwner, KeyEvent.KEY_TYPED,
System.currentTimeMillis(), getCurrentModifiers( -1 ),
0, character ) );
}
}
if ( !pressed ) {
anInt.value = keyCode;
Char c = characters.get( anInt );
if ( c != null ) {
character = c.value;
//TODO: repeat input
// if ( character != KeyEvent.CHAR_UNDEFINED ) {
// dispatchEvent( focusOwner, new KeyEvent( focusOwner, KeyEvent.KEY_TYPED,
// System.currentTimeMillis(), getCurrentModifiers( -1 ),
// 0, character ) );
// }
}
dispatchEvent( focusOwner, new KeyEvent( focusOwner, KeyEvent.KEY_RELEASED,
System.currentTimeMillis(), getCurrentModifiers( -1 ),
keyCode, character ) );
}
}
}
}
private void dispatchEvent( final Component receiver, final AWTEvent event ) {
if ( getModalComponent() == null || SwingUtilities.isDescendingFrom( receiver, getModalComponent() ) ) {
if ( !SwingUtilities.isEventDispatchThread() ) {
throw new IllegalStateException( "not in swing thread!" );
}
receiver.dispatchEvent( event );
}
}
private static Int anInt = new Int( 0 );
private static class Int {
public Int( int value ) {
this.value = value;
}
public boolean equals( Object obj ) {
return obj instanceof Int && ( (Int) obj ).value == value;
}
public int hashCode() {
return value;
}
int value;
}
private static class Char {
public Char( char value ) {
this.value = value;
}
char value;
}
/**
* From keyCode (Int) to character (Char)
*/
private Map<Int,Char> characters = new HashMap<Int,Char>();
private static void dontDrawBackground( Container container ) {
if ( container != null ) {
container.setBackground( null );
if ( container instanceof JComponent ) {
final JComponent component = ( (JComponent) container );
component.setOpaque( false );
}
dontDrawBackground( container.getParent() );
}
}
private static int powerOf2SizeIfNeeded( int size, boolean generateMipMaps ) {
if ( generateMipMaps ) { // || !TextureState.isNonPowerOfTwoTextureSupported() ) {
int powerOf2Size = 1;
while ( powerOf2Size < size ) {
powerOf2Size <<= 1;
}
return powerOf2Size;
}
return size;
}
private Component lastComponent;
private Component grabbedMouse;
private int grabbedMouseButton;
private int downX = 0;
private int downY = 0;
private long lastClickTime = 0;
private int clickCount = 0;
private static final int MAX_CLICKED_OFFSET = 4;
private Vector2f location = new Vector2f();
private void sendAWTWheelEvent( int wheelDelta, int x, int y ) {
Component comp = lastComponent != null ? lastComponent : componentAt( x, y, desktop, false );
if ( comp == null ) {
comp = desktop;
}
final Point pos = convertPoint( desktop, x, y, comp );
final MouseWheelEvent event = new MouseWheelEvent( comp,
MouseEvent.MOUSE_WHEEL,
System.currentTimeMillis(), getCurrentModifiers( -1 ), pos.x, pos.y, 1, false,
MouseWheelEvent.WHEEL_UNIT_SCROLL,
Math.abs( wheelDelta ), wheelDelta > 0 ? -1 : 1 );
dispatchEvent( comp, event );
}
private boolean useConvertPoint = true;
private Point convertPoint( Component parent, int x, int y, Component comp ) {
if ( useConvertPoint ) {
try {
return SwingUtilities.convertPoint( parent, x, y, comp );
} catch ( InternalError e ) {
useConvertPoint = false;
}
}
if ( comp != null ) {
while ( comp != parent ) {
x -= comp.getX();
y -= comp.getY();
if ( comp.getParent() == null ) {
break;
}
comp = comp.getParent();
}
}
return new Point( x, y );
}
private void sendAWTMouseEvent( int x, int y, boolean pressed, int swingButton ) {
Component comp = componentAt( x, y, desktop, false );
final int eventType;
if ( swingButton > MouseEvent.NOBUTTON ) {
eventType = pressed ? MouseEvent.MOUSE_PRESSED : MouseEvent.MOUSE_RELEASED;
}
else {
eventType = getButtonMask( MouseEvent.NOBUTTON ) == 0 ? MouseEvent.MOUSE_MOVED : MouseEvent.MOUSE_DRAGGED;
}
final long time = System.currentTimeMillis();
if ( lastComponent != comp ) {
//enter/leave events
while ( lastComponent != null && ( comp == null || !SwingUtilities.isDescendingFrom( comp, lastComponent ) ) )
{
final Point pos = convertPoint( desktop, x, y, lastComponent );
sendExitedEvent( lastComponent, getCurrentModifiers( swingButton ), pos );
lastComponent = lastComponent.getParent();
}
final Point pos = convertPoint( desktop, x, y, lastComponent );
if ( lastComponent == null ) {
lastComponent = desktop;
}
sendEnteredEvent( comp, lastComponent, getCurrentModifiers( swingButton ), pos );
lastComponent = comp;
downX = Integer.MIN_VALUE;
downY = Integer.MIN_VALUE;
lastClickTime = 0;
}
if ( comp != null ) {
boolean clicked = false;
if ( swingButton > MouseEvent.NOBUTTON ) {
if ( pressed ) {
grabbedMouse = comp;
grabbedMouseButton = swingButton;
downX = x;
downY = y;
setFocusOwner( componentAt( x, y, desktop, true ) );
}
else if ( grabbedMouseButton == swingButton && grabbedMouse != null ) {
comp = grabbedMouse;
grabbedMouse = null;
if ( Math.abs( downX - x ) <= MAX_CLICKED_OFFSET && Math.abs( downY - y ) < MAX_CLICKED_OFFSET ) {
if ( lastClickTime + DOUBLE_CLICK_TIME > time ) {
clickCount++;
}
else {
clickCount = 1;
}
clicked = true;
lastClickTime = time;
}
downX = Integer.MIN_VALUE;
downY = Integer.MIN_VALUE;
}
}
else if ( grabbedMouse != null ) {
comp = grabbedMouse;
}
final Point pos = convertPoint( desktop, x, y, comp );
final MouseEvent event = new MouseEvent( comp,
eventType,
time, getCurrentModifiers( swingButton ), pos.x, pos.y, clickCount,
swingButton == MouseEvent.BUTTON2 && pressed, // todo: should this be platform dependent? (e.g. mac)
swingButton >= 0 ? swingButton : 0 );
dispatchEvent( comp, event );
if ( clicked ) {
// CLICKED seems to need special glass pane handling o_O
comp = componentAt( x, y, desktop, true );
final Point clickedPos = convertPoint( desktop, x, y, comp );
final MouseEvent clickedEvent = new MouseEvent( comp,
MouseEvent.MOUSE_CLICKED,
time, getCurrentModifiers( swingButton ), clickedPos.x, clickedPos.y, clickCount,
false, swingButton );
dispatchEvent( comp, clickedEvent );
}
}
else if ( pressed ) {
// clicked no component at all
setFocusOwner( null );
}
}
private boolean focusCleared = false;
public void setFocusOwner( Component comp ) {
if ( comp == null || comp.isFocusable() ) {
for ( Component p = comp; p != null; p = p.getParent() ) {
if ( p instanceof JInternalFrame ) {
try {
( (JInternalFrame) p ).setSelected( true );
} catch ( PropertyVetoException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"setFocusOwner(Component comp)", "Exception", e);
}
}
}
awtWindow.setFocusableWindowState( true );
Component oldFocusOwner = getFocusOwner();
if ( comp == desktop ) {
comp = null;
}
if ( oldFocusOwner != comp ) {
if ( oldFocusOwner != null ) {
dispatchEvent( oldFocusOwner, new FocusEvent( oldFocusOwner,
FocusEvent.FOCUS_LOST, false, comp ) );
}
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
if ( comp != null ) {
dispatchEvent( comp, new FocusEvent( comp,
FocusEvent.FOCUS_GAINED, false, oldFocusOwner ) );
}
}
awtWindow.setFocusableWindowState( false );
}
focusCleared = comp == null;
}
private int getCurrentModifiers( int swingBtton ) {
int modifiers = 0;
if ( isKeyDown( KeyInput.KEY_LMENU ) ) {
modifiers |= InputEvent.ALT_DOWN_MASK;
modifiers |= InputEvent.ALT_MASK;
}
if ( isKeyDown( KeyInput.KEY_RMENU ) ) {
modifiers |= InputEvent.ALT_GRAPH_DOWN_MASK;
modifiers |= InputEvent.ALT_GRAPH_MASK;
}
if ( isKeyDown( KeyInput.KEY_LCONTROL ) || isKeyDown( KeyInput.KEY_RCONTROL ) ) {
modifiers |= InputEvent.CTRL_DOWN_MASK;
modifiers |= InputEvent.CTRL_MASK;
}
if ( isKeyDown( KeyInput.KEY_LSHIFT ) || isKeyDown( KeyInput.KEY_RSHIFT ) ) {
modifiers |= InputEvent.SHIFT_DOWN_MASK;
modifiers |= InputEvent.SHIFT_MASK;
}
return modifiers | getButtonMask( swingBtton );
}
boolean butLMenuDown = false;
boolean butRMenuDown = false;
boolean butLControlDown = false;
boolean butRControlDown = false;
boolean butLShiftDown = false;
boolean butRShiftDown = false;
private boolean isKeyDown( int key )
{
if (key==KeyInput.KEY_LMENU) {return butLMenuDown;}
else if (key==KeyInput.KEY_RMENU) {return butRMenuDown;}
else if (key==KeyInput.KEY_LCONTROL) {return butLControlDown;}
else if (key==KeyInput.KEY_RCONTROL) {return butRControlDown;}
else if (key==KeyInput.KEY_LSHIFT) {return butLShiftDown;}
else if (key==KeyInput.KEY_RSHIFT) {return butRShiftDown;}
System.err.println("JMEDesktop.java: Key not supported: "+key);
return false;
}
private void setKeyDown( int key, boolean down )
{
if (key==KeyInput.KEY_LMENU) {butLMenuDown = down;}
else if (key==KeyInput.KEY_RMENU) {butRMenuDown = down;}
else if (key==KeyInput.KEY_LCONTROL) {butLControlDown = down;}
else if (key==KeyInput.KEY_RCONTROL) {butRControlDown = down;}
else if (key==KeyInput.KEY_LSHIFT) {butLShiftDown = down;}
else if (key==KeyInput.KEY_RSHIFT) {butRShiftDown = down;}
}
boolean m1 = false;
boolean m2 = false;
boolean m3 = false;
private void setButtonDown( int mouseBut, boolean down)
{
if (mouseBut == 0) { m1 = down; }
if (mouseBut == 1) { m2 = down; }
if (mouseBut == 2) { m3 = down; }
}
private boolean isButtonDown( int mouseBut )
{
if (mouseBut == 0) { return m1; }
if (mouseBut == 1) { return m2; }
if (mouseBut == 2) { return m3; }
System.err.println("JMEDesktop.java: Button not supported: "+mouseBut);
return false;
}
private int getButtonMask( int swingButton ) {
int buttonMask = 0;
if ( isButtonDown( 0 ) || swingButton == MouseEvent.BUTTON1 ) {
buttonMask |= InputEvent.BUTTON1_MASK;
buttonMask |= InputEvent.BUTTON1_DOWN_MASK;
}
if ( isButtonDown( 1 ) || swingButton == MouseEvent.BUTTON2 ) {
buttonMask |= InputEvent.BUTTON2_MASK;
buttonMask |= InputEvent.BUTTON2_DOWN_MASK;
}
if ( isButtonDown( 2 ) || swingButton == MouseEvent.BUTTON3 ) {
buttonMask |= InputEvent.BUTTON3_MASK;
buttonMask |= InputEvent.BUTTON3_DOWN_MASK;
}
return buttonMask;
}
private int convertButton(int mouseBut)
{
if (mouseBut == 0) { return MouseEvent.BUTTON1; }
if (mouseBut == 1) { return MouseEvent.BUTTON2; }
if (mouseBut == 2) { return MouseEvent.BUTTON3; }
System.err.println("JMEDesktop.java - convertButton(): Button not supported: "+mouseBut);
return mouseBut;
}
private int lastXin = -1;
private int lastXout = -1;
private int lastYin = -1;
private int lastYout = -1;
private Ray pickRay = new Ray();
private Vector3f bottomLeft = new Vector3f();
private Vector3f topLeft = new Vector3f();
private Vector3f topRight = new Vector3f();
private Vector3f bottomRight = new Vector3f();
private Vector3f tuv = new Vector3f();
/**
* Convert mouse coordinates from jME screen to JMEDesktop coordinates (Swing).
* @param x jME x coordinate
* @param y jME y coordinate
* @param store resulting JDesktop coordinates
*/
public void convert( int x, int y, Vector2f store ) {
if ( lastXin == x && lastYin == y ) {
store.x = lastXout;
store.y = lastYout;
}
else {
lastXin = x;
lastYin = y;
logger.fine("Warning in JMEDesktop.java: Gehe von QUEUE_ORTHO aus! (assume QUEUE_ORTHO)");
// if ( getRenderQueueMode() == Renderer.QUEUE_ORTHO ) {
//TODO: occlusion by other quads (JMEFrames)
x = (int) (x * (desktopWidth/(desktopWidth*getLocalScale().x)));
y = (int) (desktopHeight - (y * (desktopHeight/(desktopHeight*getLocalScale().y))));
// x = (int) ( x - getWorldTranslation().x );
// y = (int) ( ( (desktopHeight-y) - getWorldTranslation().y ) );
// }
/*
else {
store.set( x, y );
DisplaySystem.getDisplaySystem().getWorldCoordinates( store, 0, pickRay.origin );
DisplaySystem.getDisplaySystem().getWorldCoordinates( store, 0.3f, pickRay.direction ).subtractLocal( pickRay.origin ).normalizeLocal();
applyWorld( bottomLeft.set( -width * 0.5f, -height * 0.5f, 0 ) );
applyWorld( topLeft.set( -width * 0.5f, height * 0.5f, 0 ) );
applyWorld( topRight.set( width * 0.5f, height * 0.5f, 0 ) );
applyWorld( bottomRight.set( width * 0.5f, -height * 0.5f, 0 ) );
if ( pickRay.intersectWherePlanarQuad( topLeft, topRight, bottomLeft, tuv ) ) {
x = (int) ( ( tuv.y - 0.5f ) * width ) + desktopWidth / 2;
y = (int) ( ( tuv.z - 0.5f ) * height ) + desktopHeight / 2;
}
else {
x = -1;
y = -1;
}
}
*/
lastYout = y;
lastXout = x;
store.set( x, y );
}
}
private void applyWorld( Vector3f point ) {
getWorldRotation().multLocal( point.multLocal( getWorldScale() ) ).addLocal( getWorldTranslation() );
}
/**
* Find a component at specified desktop position.
*
* @param x x coordinate in Swing coordinate space
* @param y y coordinate in Swing coordinate space
* @return the top most component at specified location, null if no child component is found at that location
*/
public Component componentAt( int x, int y ) {
Component component = componentAt( x, y, desktop, true );
if ( component != desktop ) {
return component;
}
return null;
}
private Component componentAt( int x, int y, Component parent, boolean scanRootPanes ) {
if ( scanRootPanes && parent instanceof JRootPane ) {
JRootPane rootPane = (JRootPane) parent;
parent = rootPane.getContentPane();
}
Component child = parent;
if ( !parent.contains( x, y ) ) {
child = null;
}
else {
synchronized ( parent.getTreeLock() ) {
if ( parent instanceof Container ) {
Container container = (Container) parent;
int ncomponents = container.getComponentCount();
for ( int i = 0; i < ncomponents; i++ ) {
Component comp = container.getComponent( i );
if ( comp != null
&& comp.isVisible()
&& ( dragAndDropSupport == null || !dragAndDropSupport.isDragPanel(comp) )
&& comp.contains( x - comp.getX(), y - comp.getY() ) ) {
child = comp;
break;
}
}
}
}
}
if ( child != null ) {
if ( parent instanceof JTabbedPane && child != parent ) {
child = ( (JTabbedPane) parent ).getSelectedComponent();
}
x -= child.getX();
y -= child.getY();
}
return child != parent && child != null ? componentAt( x, y, child, scanRootPanes ) : child;
}
private void sendEnteredEvent( Component comp, Component lastComponent, int buttonMask, Point pos ) {
if ( comp != null && comp != lastComponent ) {
sendEnteredEvent( comp.getParent(), lastComponent, buttonMask, pos );
pos = convertPoint( lastComponent, pos.x, pos.y, comp );
final MouseEvent event = new MouseEvent( comp,
MouseEvent.MOUSE_ENTERED,
System.currentTimeMillis(), buttonMask, pos.x, pos.y, 0, false, 0 );
dispatchEvent( comp, event );
}
}
private void sendExitedEvent( Component lastComponent, int buttonMask, Point pos ) {
final MouseEvent event = new MouseEvent( lastComponent,
MouseEvent.MOUSE_EXITED,
System.currentTimeMillis(), buttonMask, pos.x, pos.y, 1, false, 0 );
dispatchEvent( lastComponent, event );
}
private final LockRunnable paintLockRunnable = new LockRunnable();
@Override
public void updateGeometricState() {
if ( graphics.isDirty() ) {
final boolean synchronizingThreadsOnUpdate = this.synchronizingThreadsOnUpdate;
if ( synchronizingThreadsOnUpdate ) {
synchronized ( paintLockRunnable ) {
try {
paintLockRunnable.wait = true;
if (!disposing)SwingUtilities.invokeLater( paintLockRunnable );
paintLockRunnable.wait( 100 );
} catch ( InterruptedException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(), "draw(Renderer r)", "Exception", e);
}
}
}
try {
if ( graphics != null ) { // && texture.getTextureId() > 0) {
graphics.update( texture );
}
} finally {
if ( synchronizingThreadsOnUpdate ) {
synchronized ( paintLockRunnable ) {
paintLockRunnable.notifyAll();
}
}
}
}
texture.getImage().setUpdateNeeded();
//getLocalTranslation().set(starcom.jme3d.Referenz.view.getLocation().add(starcom.jme3d.Referenz.view.getDirection()));
//getLocalRotation().set(starcom.jme3d.Referenz.view.getRotation());
super.updateGeometricState();
}
public JDesktopPane getJDesktop() {
return desktop;
}
public Component getFocusOwner() {
if ( !focusCleared ) {
return this.awtWindow.getFocusOwner();
}
return null;
}
private class LockRunnable implements Runnable {
private boolean wait = false;
public void run() {
synchronized ( paintLockRunnable ) {
notifyAll();
if ( wait ) {
try {
//wait for repaint to finish
wait = false;
paintLockRunnable.wait( 200 );
} catch ( InterruptedException e ) {
logger.logp(Level.SEVERE, this.getClass().toString(),
"run()", "Exception", e);
}
}
}
}
}
private static class MyPopupFactory extends PopupFactory {
private final PopupFactory defaultPopupFactory = new PopupFactory();
public Popup getPopup( Component owner, Component contents, int x, int y ) throws IllegalArgumentException {
while ( !( owner instanceof JDesktopPane ) ) {
owner = owner.getParent();
if ( owner == null ) {
logger.warning("jME Popup creation failed, default popup created - desktop not found in component hierarchy of "
+ owner);
return defaultPopupFactory.getPopup( owner, contents, x, y );
}
}
JMEDesktop.LightWeightPopup popup = new JMEDesktop.LightWeightPopup( (JComponent) owner );
popup.adjust( owner, contents, x, y );
return popup;
}
}
/**
* @return current modal component
* @see #setModalComponent(java.awt.Component)
*/
public Component getModalComponent() {
return this.modalComponent;
}
/**
* @see #setModalComponent(java.awt.Component)
*/
private Component modalComponent;
/**
* Filter the swing event to allow events to the specified component and its children only.
* Note: this does not prevent shortcuts and mnemonics to work for the other components!
*
* @param value component that can be exclusively accessed (including children)
*/
public void setModalComponent( final Component value ) {
this.modalComponent = value;
}
protected void setParent( Node parent ) {
if ( desktop != null ) {
super.setParent( parent );
}
else {
throw new IllegalStateException( "already disposed" );
}
}
/**
* Call this method of the desktop is no longer needed. Removes this from the scenegraph, later use is not
* possible any more.
*/
public void dispose()
{
disposing = true;
SwingUtilities.invokeLater( new Runnable()
{
public void run()
{
logger.fine("Disposing JMEDesktop: START");
doDispose();
logger.fine("Disposing JMEDesktop: READY");
}
} );
}
private void doDispose(){
if ( desktop != null ) {
if ( getParent() != null ) {
getParent().detachChild( this );
}
if ( rawInputListener != null ) {
inputManager.removeRawInputListener( rawInputListener );
}
desktop.removeAll();
awtWindow.dispose();
desktop = null;
desktopsUsed--;
if ( desktopsUsed == 0 ) {
PopupFactory.setSharedInstance( new PopupFactory() );
}
}
}
private static class ScrollPaneRepaintFixListener implements ContainerListener {
public void componentAdded( ContainerEvent e ) {
Component child = e.getChild();
componentAdded( child );
}
private void componentAdded( Component child ) {
if ( child instanceof Container ) {
Container container = (Container) child;
addTo( container );
container.addContainerListener( this );
}
if ( child instanceof JScrollPane ) {
final JScrollPane scrollPane = (JScrollPane) child;
// note: the listener added here is only a fix for repaint problems with scrolling
subscribeRepaintListener( scrollPane.getViewport() );
}
}
private void addTo( Container container ) {
container.addContainerListener( this );
for ( int i = 0; i < container.getComponentCount(); i++ ) {
componentAdded( container.getComponent( i ) );
}
}
private void removeFrom( Container container ) {
container.removeContainerListener( this );
for ( int i = 0; i < container.getComponentCount(); i++ ) {
componentRemoved( container.getComponent( i ) );
}
}
private void subscribeRepaintListener( JViewport viewport ) {
for ( int i = 0; i < viewport.getChangeListeners().length; i++ ) {
ChangeListener listener = viewport.getChangeListeners()[i];
if ( listener instanceof ScrollPaneRepaintChangeListener ) {
// listener already subscribed
return;
}
}
viewport.addChangeListener( new ScrollPaneRepaintChangeListener( viewport ) );
}
public void componentRemoved( ContainerEvent e ) {
Component child = e.getChild();
componentRemoved( child );
}
private void componentRemoved( Component child ) {
if ( child instanceof Container ) {
Container container = (Container) child;
removeFrom( container );
}
}
private static class ScrollPaneRepaintChangeListener implements ChangeListener {
private final Component component;
public ScrollPaneRepaintChangeListener( Component component ) {
this.component = component;
}
public void stateChanged( ChangeEvent e ) {
component.repaint();
}
}
}
}