/** * Copyright (c) 2003-2009, Xith3D Project Group all rights reserved. * * Portions based on the Java3D interface, Copyright by Sun Microsystems. * Many thanks to the developers of Java3D and Sun Microsystems for their * innovation and design. * * 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 the 'Xith3D Project Group' 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) A * RISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE */ package org.xith3d.ui.swingui; import java.awt.AWTEvent; import java.awt.AlphaComposite; 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.GraphicsEnvironment; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import javax.swing.JComponent; import javax.swing.JInternalFrame; import javax.swing.JTextField; import javax.swing.RepaintManager; import javax.swing.SwingUtilities; import javax.swing.text.Caret; import org.xith3d.scenegraph.Node; import org.xith3d.utility.logging.X3DLog; /** * A UI window is a special container for Swing components which can be rendered * on a Xith3D canvas. * * @author David Yazel */ public class UIWindow implements UIOverlayInterface, UIDragDropInterface { private BufferedImage buffer; private int width; private int height; private boolean clipAlpha; private boolean blendAlpha; private Frame window; private Component focusedComponent = null; private JComponent root; private static UIRepaintManager repaintMgr = new UIRepaintManager(); //private RepaintManager defaultMgr ; private UIOverlay overlay; private boolean packed = false; private UIWindowManager manager = null; private boolean disabled = false; private boolean draggable = false; boolean textComponentFocus = false; Component last = null; public int getWidth() { return ( width ); } public int getHeight() { return ( height ); } public void setWindowManager( UIWindowManager manager ) { this.manager = manager; } public UIWindowManager getWindowManager() { return ( manager ); } public void setRoot( JComponent root ) { window.add( root ); this.root = root; pack(); pack(); repaintMgr.markCompletelyDirty( root ); } private void constructBuffer() { if ( clipAlpha || blendAlpha ) buffer = new BufferedImage( width + 50, height + 50, BufferedImage.TYPE_INT_ARGB ); //buffer = DirectBufferedImage.getDirectImageRGBA( width + 50, height + 50 ); else buffer = new BufferedImage( width + 50, height + 50, BufferedImage.TYPE_INT_RGB ); //buffer = DirectBufferedImage.getDirectImageRGB( width + 50, height + 50 ); //buffer = new BufferedImage( width + 50, height + 50, BufferedImage.TYPE_INT_RGB ); } public BufferedImage getBuffer() { return ( buffer ); } public UIOverlay getOverlay() { return ( overlay ); } /* private void renderToBuffer(Point curPoint, Component c, Graphics g) { System.out.println( "rendering " + c.getClass().getName() + " at " + c.getLocation() + " size " + c.getSize() ); Point p = new Point( c.getLocation() ); //p.x += curPoint.x; //p.y += curPoint.y; g.translate( p.x, p.y ); //g.setClip( p.x, p.y, c.getSize().width, c.getSize().height ); g.setClip( 0, 0, c.getSize().width, c.getSize().height ); g.setFont( c.getFont() ); g.setColor( c.getForeground() ); c.paint( g ); c.paintAll( g ); if (c instanceof Container) { Container ca = (Container)c; Component cs[] = ca.getComponents(); for (int i = 0; i < cs.length; i++) { renderToBuffer( p, cs[ i ], g ); } } g.translate( -p.x, -p.y ); } */ /* private MouseEvent adjustMouse(Component c, MouseEvent me) { if (c == null) return me; Point mousePoint = me.getPoint(); Point compPoint = new Point( c.getLocation() ); Point newPoint = new Point( mousePoint.x - compPoint.x, mousePoint.y - compPoint.y ); MouseEvent newEvent = new MouseEvent( c, me.getID(), me.getWhen(), me.getModifiers(), newPoint.x, newPoint.y, me.getClickCount(), false, me.getButton() ); return ( newEvent ); } */ private Component dispatchMouseEvent( int index, Component c, MouseEvent me ) { /* for (int i = 0; i < index; i++) System.out.print( " " ); */ //System.out.println( "checking " + c.getClass().getName() + " for mouse " + me.getPoint() + " against " + c.getLocation() ); Point mousePoint = me.getPoint(); Component retValue = null; if ( c instanceof Container ) { Container ca = (Container)c; Component cs[] = ca.getComponents(); for ( int i = 0; i < cs.length; i++ ) { if ( cs[ i ].getBounds().contains( mousePoint ) && cs[ i ].isVisible() ) { //System.out.println( "container hit : " + cs[i].getClass().getName() ); Point compPoint = new Point( cs[ i ].getLocation() ); Point newPoint = new Point( mousePoint.x - compPoint.x, mousePoint.y - compPoint.y ); MouseEvent newEvent = new MouseEvent( cs[ i ], me.getID(), me.getWhen(), me.getModifiers(), newPoint.x, newPoint.y, me.getClickCount(), false, me.getButton() ); Component focus = dispatchMouseEvent( index + 1, cs[ i ], newEvent ); if ( retValue == null && focus != null ) retValue = focus; if ( me.isConsumed() ) { System.out.println( "consumed" ); return ( cs[ i ] ); } } } } //System.out.println( "dispatching event to " + c.getClass().getName() ); if ( !c.isVisible() ) return ( null ); if ( !c.isEnabled() ) return ( null ); c.dispatchEvent( me ); // if we already have a new focus then keep that one if ( retValue != null ) return ( retValue ); // check to see if this can have focus if ( c.isFocusable() ) return ( c ); return ( null ); } /** * Finds the deepest component that will accept the dragging request. * * @return null if there is no such component. */ private UIDraggingInformation startDrag( int index, Component c, MouseEvent me ) { Point mousePoint = me.getPoint(); UIDraggingInformation retValue = null; X3DLog.debug( "c is of type : ", c.getClass().getSimpleName() ); if ( c instanceof Container ) { Container ca = (Container)c; Component cs[] = ca.getComponents(); for ( int i = 0; i < cs.length; i++ ) { if ( cs[ i ].getBounds().contains( mousePoint ) && cs[ i ].isVisible() ) { Point compPoint = new Point( cs[ i ].getLocation() ); me.translatePoint( -compPoint.x, -compPoint.y ); UIDraggingInformation focus = startDrag( index + 1, cs[ i ], me ); me.translatePoint( compPoint.x, compPoint.y ); if ( retValue == null && focus != null ) retValue = focus; } } } if ( !c.isVisible() ) { X3DLog.debug( "return null" ); return ( null ); } // if we already have a new focus then keep that one if ( retValue != null ) { X3DLog.debug( "return retValue" ); return ( retValue ); } // check to see if this can have focus if ( c instanceof UIDragDropInterface ) { return ( ( (UIDragDropInterface)c ).startDrag( me ) ); } X3DLog.debug( "return null cause no focus" ); return ( null ); } /** * Finds the deepest component that will accept the dragging request. * * @return null if there is no such component. */ public boolean dragging( int index, Component c, MouseEvent me, UIDraggingInformation info ) { Point mousePoint = me.getPoint(); boolean retValue = false; if ( c instanceof Container ) { Container ca = (Container)c; Component cs[] = ca.getComponents(); for ( int i = 0; i < cs.length; i++ ) { if ( cs[ i ].getBounds().contains( mousePoint ) && cs[ i ].isVisible() ) { Point compPoint = new Point( cs[ i ].getLocation() ); me.translatePoint( -compPoint.x, -compPoint.y ); boolean focus = dragging( index + 1, cs[ i ], me, info ); me.translatePoint( compPoint.x, compPoint.y ); if ( retValue == false && focus == true ) retValue = focus; } } } if ( !c.isVisible() ) return ( false ); // if we already have a new focus then keep that one if ( retValue != false ) return ( retValue ); // check to see if this can have focus if ( c instanceof UIDragDropInterface ) return ( ( (UIDragDropInterface)c ).dragging( me, info ) ); return ( false ); } /** * Finds the deepest component that will accept the drop request. * * @return false if there is no such component. */ public boolean drop( int index, Component c, MouseEvent me, UIDraggingInformation info ) { Point mousePoint = me.getPoint(); boolean retValue = false; if ( c instanceof Container ) { Container ca = (Container)c; Component cs[] = ca.getComponents(); for ( int i = 0; i < cs.length; i++ ) { if ( cs[ i ].getBounds().contains( mousePoint ) && cs[ i ].isVisible() ) { Point compPoint = new Point( cs[ i ].getLocation() ); me.translatePoint( -compPoint.x, -compPoint.y ); boolean focus = drop( index + 1, cs[ i ], me, info ); me.translatePoint( compPoint.x, compPoint.y ); if ( retValue == false && focus == true ) retValue = focus; } } } if ( !c.isVisible() ) return ( false ); // if we already have a new focus then keep that one if ( retValue != false ) return ( retValue ); // check to see if this can have focus if ( c instanceof UIDragDropInterface ) return ( ( (UIDragDropInterface)c ).dropped( me, info ) ); return ( false ); } private void getDirtyAreas( Point curPoint, JComponent c, ArrayList< UIDirtyRegion > areas ) { if ( !c.isVisible() ) return; //System.out.println( "rendering " + c.getClass().getName() + " at " + c.getLocation() + " size " + c.getSize() ); Point p = new Point( c.getLocation() ); p.x += curPoint.x; p.y += curPoint.y; if ( c instanceof Container ) { Container ca = c; Component cs[] = ca.getComponents(); for ( int i = 0; i < cs.length; i++ ) { if ( cs[ i ] instanceof JComponent ) getDirtyAreas( p, (JComponent)cs[ i ], areas ); } } // get the dirty rectangle, if there is one. UIDirtyRegion dr = repaintMgr.getUIDirtyRegion( c ); if ( dr != null ) { Rectangle absR = new Rectangle( dr.getCompDirty() ); Point newPoint = new Point( absR.x + p.x, absR.y + p.y ); absR.setLocation( newPoint ); dr.setAbsDirty( absR ); areas.add( dr ); repaintMgr.removeUIDirtyRegion( c ); } } public ArrayList< ? > getDirtyAreas() { ArrayList< UIDirtyRegion > list = new ArrayList< UIDirtyRegion >(); getDirtyAreas( new Point(), (JComponent)getRootComponent(), list ); return ( list ); } public void pack() { if ( root instanceof JInternalFrame ) ( (JInternalFrame)root ).pack(); pack( getRootComponent() ); } private void pack( Component c ) { if ( c instanceof Container ) { Container ca = (Container)c; Component cs[] = ca.getComponents(); for ( int i = 0; i < cs.length; i++ ) { pack( cs[ i ] ); } } // absolutely critical... the lightweight peer is not assigned until // this is done... took 6 hours to figure this out - yazel if ( c.getParent() != null ) c.addNotify(); if ( c instanceof JComponent ) { ( (JComponent)c ).doLayout(); ( (JComponent)c ).revalidate(); } c.setVisible( true ); if ( c instanceof JTextField ) { Caret caret = ( (JTextField)c ).getCaret(); caret.setBlinkRate( 0 ); } } private Component getRootComponent() { return ( root ); } public void setComponentFocus( Component focus ) { boolean same = ( focus == focusedComponent ); if ( focusedComponent != null ) { if ( focusedComponent instanceof JTextField ) { textComponentFocus = false; Caret caret = ( (JTextField)focusedComponent ).getCaret(); if ( caret.isVisible() ) { caret.setBlinkRate( 0 ); caret.setVisible( false ); } } if ( !same ) { //System.out.println( "setting component to focus lost" ); if ( focusedComponent instanceof FocusListener ) { //FocusListener listener = (FocusListener)focusedComponent; //listener.focusGained( new FocusEvent( focusedComponent, FocusEvent.FOCUS_LOST ) ); } //focusedComponent.dispatchEvent( new FocusEvent( focusedComponent, FocusEvent.FOCUS_LOST ) ); } } if ( focus != null && focus.isEnabled() ) { focusedComponent = focus; getWindowManager().setOverlayFocus( this ); if ( focusedComponent instanceof JTextField ) { textComponentFocus = true; /* int x = focusedComponent.getLocation().x + 1; int y = focusedComponent.getLocation().y + 1; // this stupid thing is to force focus focusedComponent.dispatchEvent( new MouseEvent( focusedComponent, MouseEvent.MOUSE_PRESSED, 0, MouseEvent.BUTTON1_MASK, x, y, 1, false ) ); focusedComponent.dispatchEvent( new MouseEvent( focusedComponent, MouseEvent.MOUSE_RELEASED, 0, MouseEvent.BUTTON1_MASK, x, y, 1, false ) ); focusedComponent.dispatchEvent( new MouseEvent( focusedComponent, MouseEvent.MOUSE_CLICKED, 0, MouseEvent.BUTTON1_MASK, x, y, 1, false ) ); // this is to show Caret caret = ((JTextField)focusedComponent).getCaret(); caret.setBlinkRate( 0 ); caret.setVisible( true ); */ } if ( !same ) { //System.out.println( "setting component to focus gained" ); if ( focusedComponent instanceof FocusListener ) { //FocusListener listener = (FocusListener)focusedComponent; //listener.focusGained( new FocusEvent( focusedComponent, FocusEvent.FOCUS_GAINED ) ); } } //focusedComponent.dispatchEvent( new FocusEvent( focusedComponent, FocusEvent.FOCUS_GAINED ) ); } else if ( focus == null ) { focusedComponent = null; } } public void setVisible( boolean visible ) { getWindowManager().setVisible( this, visible ); } public void dispatchEvent( AWTEvent e ) { //RepaintManager.setCurrentManager( repaintMgr ); if ( e instanceof MouseEvent ) { Component focus = dispatchMouseEvent( 0, getRootComponent(), (MouseEvent)e /*adjustMouse( parent, (MouseEvent)e )*/); if ( focus != null ) focus.dispatchEvent( new MouseEvent( focus, MouseEvent.MOUSE_ENTERED, 0, MouseEvent.BUTTON1_MASK, focus.getLocation().x, focus.getLocation().y, 0, false ) ); if ( focus != null && last == null ) last = focus; else if ( last != null && last != focus ) { last.dispatchEvent( new MouseEvent( last, MouseEvent.MOUSE_EXITED, 0, MouseEvent.BUTTON1_MASK, last.getLocation().x, last.getLocation().y, 0, false ) ); last = focus; } if ( ( focus != focusedComponent ) && ( focus != null ) && ( (MouseEvent)e ).getID() == MouseEvent.MOUSE_PRESSED ) { setComponentFocus( focus ); } } else if ( e instanceof KeyEvent ) { if ( focusedComponent != null ) { focusedComponent.dispatchEvent( e ); } } // get all the components which have been marked as dirty //RepaintManager.setCurrentManager( defaultMgr ); } /* public void processWindowEvent( WindowEvent event ) { } */ public synchronized void renderToBuffer( ArrayList< ? > dirtyList ) { if ( !packed ) { pack(); packed = true; } final Graphics g = buffer.getGraphics(); if ( blendAlpha ) { Graphics2D g2 = (Graphics2D)g; g2.setComposite( AlphaComposite.Src ); g.setColor( new Color( 0, 0, 0, 0 ) ); g.fillRect( 0, 0, width, height ); g2.setComposite( AlphaComposite.SrcOver ); } //Component c = getRootComponent(); // print the component using the swing thread to stop deadlocks from // happening in some cases. try { SwingUtilities.invokeAndWait( new Runnable() { public void run() { root.print( g ); } } ); } catch ( InterruptedException e ) { e.printStackTrace(); } catch ( InvocationTargetException e ) { e.printStackTrace(); } g.dispose(); overlay.repaintChanged( buffer, dirtyList ); } public void getSize( Dimension dim ) { dim.setSize( width, height ); } public void update() { if ( !repaintMgr.isDirty() ) return; ArrayList< UIDirtyRegion > dirtyList = new ArrayList< UIDirtyRegion >(); getDirtyAreas( new Point(), (JComponent)getRootComponent(), dirtyList ); if ( dirtyList.size() > 0 ) { //if (repaintMgr.isDirty()) { renderToBuffer( dirtyList ); overlay.update(); //repaintMgr.clear(); } } public Node getRoot() { return ( overlay.getRoot() ); } public boolean isOpaque() { return ( overlay.isOpaque() ); } public UIDraggingInformation startDrag( MouseEvent me ) { return ( startDrag( 0, getRootComponent(), me ) ); } public boolean dragging( MouseEvent me, UIDraggingInformation info ) { return ( dragging( 0, getRootComponent(), me, info ) ); } public boolean dropped( MouseEvent me, UIDraggingInformation info ) { RepaintManager.setCurrentManager( repaintMgr ); boolean val = drop( 0, getRootComponent(), me, info ); //RepaintManager.setCurrentManager( defaultMgr ); return ( val ); } public void setDisabled( boolean b ) { this.disabled = b; overlay.getRoot().setPickable( !b ); } public boolean isDisabled() { return ( disabled ); } public void setDraggable( boolean b ) { draggable = b; } public boolean isDraggable() { return ( draggable ); } public int getX() { return ( getWindowManager().getX( this ) ); } public int getY() { return ( getWindowManager().getY( this ) ); } public void setPosition( int x, int y ) { getWindowManager().setPosition( this, x, y ); } public void setFocus( boolean hasFocus ) { X3DLog.debug( "setting window focus to ", hasFocus ); if ( !hasFocus && focusedComponent != null ) setComponentFocus( null ); } public UIWindow( int width, int height, boolean clipAlpha, boolean blendAlpha ) { this.width = width; this.height = height; this.clipAlpha = clipAlpha; this.blendAlpha = blendAlpha; constructBuffer(); /* * it is unfortunate that we need this at all, since all we care about * is the lightweight swing components... but the mishmash disaster that is called * AWT/Swing just can't handle not having a heavyweight parent */ window = new Frame( GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration() ); window.setEnabled( true ); window.setSize( width, height ); window.setUndecorated( true ); window.addNotify(); //defaultMgr = new RepaintManager(); RepaintManager.setCurrentManager( repaintMgr ); // now build an overlay which matches the size of the window. overlay = new UIOverlay( width, height, clipAlpha, blendAlpha, false ); } public UIWindow( JComponent root, int width, int height, boolean clipAlpha, boolean blendAlpha ) { this( width, height, clipAlpha, blendAlpha ); setRoot( root ); } }