/* * Copyright (c) 2016, Metron, Inc. * 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 Metron, Inc. 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 METRON, INC. 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.metsci.glimpse.layout; import java.awt.Dimension; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.List; import javax.media.opengl.GL; import com.metsci.glimpse.context.GlimpseBounds; import com.metsci.glimpse.context.GlimpseContext; import com.metsci.glimpse.context.GlimpseTargetStack; import com.metsci.glimpse.gl.util.GLUtils; import com.metsci.glimpse.painter.base.GlimpsePainter; import com.metsci.glimpse.painter.base.GlimpsePainterCallback; import com.metsci.glimpse.support.settings.LookAndFeel; import net.miginfocom.layout.ComponentWrapper; import net.miginfocom.layout.ContainerWrapper; public class GlimpseLayoutDelegate implements ComponentWrapper, ContainerWrapper { public static final int DEFAULT = -1; public static final int DEFAULT_WIDTH = 100; public static final int DEFAULT_HEIGHT = 100; private int x, y, width, height; private boolean isDisposed = false; private boolean visualPadding = false; private static final boolean zeroMinSize = true; //TODO These default constraints make filling all available space the default behavior of a GlimpseLayout // because older code sometimes used GlimpseLayouts in this way. However, with the new setup there // should be no reason to have a GlimpseLayout which completely fills its parent GlimpseLayout (since // it doesn't add/change anything) private GlimpseLayoutManager layoutManager = new GlimpseLayoutManagerMig( "bottomtotop, gapx 0, gapy 0, insets 0", null, null ); private Object layoutData = "push, grow"; // the GlimpseLayout associated with this GlimpseLayoutDelegate private GlimpseLayout layout; // GlimpseLayouts may be part of multiple hierarchies and thus may have // multiple parents. This pointer is temporarily set whenever the layout // algorithm is run private GlimpseLayoutDelegate layoutParent; private List<GlimpseLayoutDelegate> layoutChildren; private LinkedHashMap<GlimpsePainter, Member> memberMap; private List<Member> memberList; private static class Member { public GlimpsePainter painter; public GlimpsePainterCallback callback; public int zOrder = 0; public Member( GlimpsePainter painter, GlimpsePainterCallback callback, int zOrder ) { this.painter = painter; this.callback = callback; this.zOrder = zOrder; } public void setZOrder( int zOrder ) { this.zOrder = zOrder; } public int getZOrder( ) { return this.zOrder; } @Override public int hashCode( ) { return 31 + ( ( painter == null ) ? 0 : painter.hashCode( ) ); } @Override public boolean equals( Object obj ) { if ( this == obj ) return true; if ( obj == null ) return false; if ( getClass( ) != obj.getClass( ) ) return false; Member other = ( Member ) obj; if ( painter == null && other.painter != null ) return false; else if ( !painter.equals( other.painter ) ) return false; return true; } } public GlimpseLayoutDelegate( GlimpseLayout layout ) { this.layout = layout; this.layoutChildren = new ArrayList<GlimpseLayoutDelegate>( ); this.memberList = new ArrayList<Member>( ); this.memberMap = new LinkedHashMap<GlimpsePainter, Member>( ); } public void paintTo( GlimpseContext context ) { final int[] scale = context.getSurfaceScale( ); final int scaleX = scale[0]; final int scaleY = scale[1]; GL gl = context.getGL( ); GlimpseBounds bounds = context.getTargetStack( ).getBounds( ); GlimpseBounds clippedBounds = GLUtils.getClippedBounds( context ); if ( !clippedBounds.isValid( ) ) return; for ( Member m : memberList ) { try { // if a GlimpsePainter is visible, paint it // if it is not visible, but is a GlimpseLayout (GlimpseLayout implements GlimpsePainter, which it // arguably should not) then GlimpseLayout still needs to layout its children or odd behavior may // result if the GlimpseLayout is resized while not visible (but nothing further should be painted) boolean isLayout = m.painter instanceof GlimpseLayout; boolean isVisible = layout.isVisible; if ( isVisible ) { gl.glEnable( GL.GL_SCISSOR_TEST ); gl.glViewport( bounds.getX( ) * scaleX, bounds.getY( ) * scaleY, bounds.getWidth( ) * scaleX, bounds.getHeight( ) * scaleY ); gl.glScissor( clippedBounds.getX( ) * scaleX, clippedBounds.getY( ) * scaleY, clippedBounds.getWidth( ) * scaleX, clippedBounds.getHeight( ) * scaleY ); if ( m.callback != null ) m.callback.prePaint( m.painter, context ); m.painter.paintTo( context ); if ( m.callback != null ) m.callback.postPaint( m.painter, context ); } else if ( isLayout ) { ( ( GlimpseLayout ) m.painter ).layoutTo( context ); } } finally { gl.glDisable( GL.GL_SCISSOR_TEST ); } } } public void layoutTo( GlimpseContext context, GlimpseBounds bounds ) { layoutTo( context.getTargetStack( ), bounds ); } // lay out the children of this GlimpseLayout // therefore GlimpseLayout should be at the top of the // GlimpseContext stack with the proper GlimpseBounds public void layoutTo( GlimpseTargetStack stack, GlimpseBounds bounds ) { // push ourself onto the stack in preparation for laying out our children stack.push( this.layout, bounds ); // update the size of our axes this.layout.preLayout( stack, bounds ); // fields in GlimpseLayoutDelegate are temporary and are reset for each new Context // which the GlimpseLayout is laid out to (the fields are used by the GlimpseLayoutManager) // set temporary bound field setBounds( bounds ); // set temporary parent fields for ( GlimpseLayoutDelegate child : this.layoutChildren ) { child.setParent( this ); } // run the GlimpseLayoutManager to set the bounds of our children this.layoutManager.layout( this ); // retrieve the bounds set by the previous call and store them in the // GlimpseLayout cache for the current context for ( GlimpseLayoutDelegate child : this.layoutChildren ) { GlimpseBounds childBounds = child.getBounds( ); child.cacheBounds( stack, childBounds ); child.layoutTo( stack, childBounds ); } // pop ourself off the stack stack.pop( ); } public void setLookAndFeel( LookAndFeel laf ) { for ( Member m : memberList ) { m.painter.setLookAndFeel( laf ); } } public void invalidateLayout( ) { for ( GlimpseLayoutDelegate child : layoutChildren ) { child.layout.invalidateLayout( ); } } public void removeLayout( GlimpseLayout layout ) { Member member = memberMap.remove( layout ); memberList.remove( member ); GlimpseLayoutDelegate delegate = layout.getDelegate( ); layoutChildren.remove( delegate ); } public void removeAll( ) { layoutChildren.clear( ); memberList.clear( ); memberMap.clear( ); } public void addLayout( GlimpseLayout layout ) { addLayout( layout, null, 0 ); } public void addLayout( GlimpseLayout layout, GlimpsePainterCallback callback, int zOrder ) { Member member = new Member( layout, callback, zOrder ); memberMap.put( layout, member ); memberList.add( member ); updateMemeberList( ); GlimpseLayoutDelegate delegate = layout.getDelegate( ); layoutChildren.add( delegate ); } public void addPainter( GlimpsePainter painter ) { addPainter( painter, null, 0 ); } public void addPainter( GlimpsePainter painter, GlimpsePainterCallback callback, int zOrder ) { Member member = new Member( painter, callback, zOrder ); memberMap.put( painter, member ); memberList.add( member ); updateMemeberList( ); } public void removePainter( GlimpsePainter painter ) { Member member = memberMap.remove( painter ); memberList.remove( member ); } public void setZOrder( GlimpsePainter painter, int zOrder ) { Member member = memberMap.get( painter ); if ( member != null ) { member.setZOrder( zOrder ); updateMemeberList( ); } } public void updateMemeberList( ) { Collections.sort( memberList, new Comparator<Member>( ) { @Override public int compare( Member arg0, Member arg1 ) { if ( arg0.getZOrder( ) < arg1.getZOrder( ) ) { return -1; } else if ( arg0.getZOrder( ) > arg1.getZOrder( ) ) { return 1; } else { return 0; } } } ); } public GlimpseBounds getCachedBounds( GlimpseContext context ) { return this.layout.getBounds( context ); } public void cacheBounds( GlimpseContext context, GlimpseBounds bounds ) { this.layout.cacheBounds( context, bounds ); } public void cacheBounds( GlimpseTargetStack stack, GlimpseBounds bounds ) { this.layout.cacheBounds( stack, bounds ); } public GlimpseBounds getBounds( ) { return new GlimpseBounds( x, y, width, height ); } public Object getLayoutData( ) { return layoutData; } public void setLayoutManager( GlimpseLayoutManager manager ) { this.layoutManager = manager; } public GlimpseLayoutManager getLayoutManager( ) { return layoutManager; } public void setLayoutData( Object layoutData ) { this.layoutData = layoutData; } public void setParent( GlimpseLayoutDelegate parent ) { this.layoutParent = parent; } public void dispose( GlimpseContext context ) { if ( !isDisposed ) { for ( Member member : memberList ) { member.painter.dispose( context ); } } isDisposed = true; } public boolean isDisposed( ) { return isDisposed; } // /////////////////////////////////////////////////////////// // MIG Layout Stuff // /////////////////////////////////////////////////////////// public void setBounds( GlimpseBounds bounds ) { this.x = bounds.getX( ); this.y = bounds.getY( ); this.width = bounds.getWidth( ); this.height = bounds.getHeight( ); } @Override public void setBounds( int x, int y, int width, int height ) { this.x = x; this.y = y; this.width = width; this.height = height; } @Override public GlimpseLayoutDelegate getComponent( ) { return this; } @Override public int getX( ) { return x; } @Override public int getY( ) { return y; } @Override public int getWidth( ) { return width; } @Override public int getHeight( ) { return height; } private final Dimension computeSize( int wHint, int hHint ) { int width = wHint == DEFAULT ? DEFAULT_WIDTH : wHint; int height = hHint == DEFAULT ? DEFAULT_HEIGHT : hHint; int border = 0; width += border * 2; height += border * 2; return new Dimension( width, height ); } @Override public int getMinimumWidth( int hHint ) { return zeroMinSize ? 0 : getPreferredWidth( hHint ); } @Override public int getMinimumHeight( int wHint ) { return zeroMinSize ? 0 : getPreferredHeight( wHint ); } @Override public int getPreferredWidth( int hHint ) { return computeSize( DEFAULT, hHint ).width; } @Override public int getPreferredHeight( int wHint ) { return computeSize( wHint, DEFAULT ).height; } @Override public int getMaximumWidth( int hHint ) { return Short.MAX_VALUE; } @Override public int getMaximumHeight( int wHint ) { return Short.MAX_VALUE; } @Override public boolean isVisible( ) { return layout.isVisible( ); } @Override public int getBaseline( int width, int height ) { return -1; } @Override public boolean hasBaseline( ) { return false; } @Override public GlimpseLayoutDelegate getParent( ) { return layoutParent; } @Override public String getLinkId( ) { return null; } @Override public int getLayoutHashCode( ) { int h; h = x + ( y << 12 ) + ( width << 22 ) + ( height << 16 ); if ( isVisible( ) ) h |= ( 1 << 25 ); String id = getLinkId( ); if ( id != null ) h += id.hashCode( ); if ( isLeftToRight( ) ) h |= ( 1 << 26 ); return h; } public boolean isPadded( ) { return visualPadding; } public void setPadding( boolean v ) { visualPadding = v; } @Override public int[] getVisualPadding( ) { return null; } @Override public int getComponetType( boolean disregardScrollPane ) { return TYPE_CONTAINER; } // //////////////////////////////////////////////////// // // TODO: Handle screen functions when not off-screen. // //////////////////////////////////////////////////// RuntimeException screenAccessException( ) { return new UnsupportedOperationException( "GL layout parameters cannot use screen parameters." ); } @Override public void paintDebugOutline( ) { throw screenAccessException( ); } @Override public float getPixelUnitFactor( boolean isHor ) { // TODO: Figure out how to return a meaningful value here. return 1f; } @Override public int getHorizontalScreenDPI( ) { throw screenAccessException( ); } @Override public int getVerticalScreenDPI( ) { throw screenAccessException( ); } @Override public int getScreenWidth( ) { throw screenAccessException( ); } @Override public int getScreenHeight( ) { throw screenAccessException( ); } @Override public int getScreenLocationX( ) { throw screenAccessException( ); } @Override public int getScreenLocationY( ) { throw screenAccessException( ); } // /////////////////////////////////// // // MIG Container Specific // /////////////////////////////////// @Override public ComponentWrapper[] getComponents( ) { return layoutChildren.toArray( new ComponentWrapper[0] ); } @Override public int getComponentCount( ) { return layoutChildren.size( ); } @Override public GlimpseLayoutManager getLayout( ) { return layoutManager; } @Override public boolean isLeftToRight( ) { return true; } @Override public void paintDebugCell( int x, int y, int width, int height ) { throw screenAccessException( ); } }