com.metsci.glimpse.layout.GlimpseLayout Maven / Gradle / Ivy
/*
* 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.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.locks.ReentrantLock;
import com.metsci.glimpse.canvas.LayoutManager;
import com.metsci.glimpse.context.GlimpseBounds;
import com.metsci.glimpse.context.GlimpseContext;
import com.metsci.glimpse.context.GlimpseTarget;
import com.metsci.glimpse.context.GlimpseTargetStack;
import com.metsci.glimpse.event.mouse.GlimpseMouseAllListener;
import com.metsci.glimpse.event.mouse.GlimpseMouseEvent;
import com.metsci.glimpse.event.mouse.GlimpseMouseListener;
import com.metsci.glimpse.event.mouse.GlimpseMouseMotionListener;
import com.metsci.glimpse.event.mouse.GlimpseMouseWheelListener;
import com.metsci.glimpse.event.mouse.Mouseable;
import com.metsci.glimpse.painter.base.GlimpsePainter;
import com.metsci.glimpse.painter.base.GlimpsePainterCallback;
import com.metsci.glimpse.support.settings.LookAndFeel;
/**
* GlimpseLayout provides a means of rendering to specific areas of a GlimpseCanvas
* controlled by Mig Layout constraints. It also acts as a RenderTarget onto which
* other GlimpsePainters may be painted. GlimpseLayout satisfies the Glimpse-facing
* interfaces GlimpsePainter and RenderTarget.
*
* GlimpseLayout uses a delegate class {@link GlimpseLayoutDelegate GlimpseLayoutDelegate}
* to interface with Mig Layout and hold transient state during layout operations.
* The final results of the layout are stored in a {@link GlimpseLayoutCache LayoutCache}.
*
* Don't forget the "bottomtotop" layout constraint for MiG, or things will be
* upside-down from what you probably expect.
*
* @author osborn
* @author ulman
* @see GlimpseLayoutDelegate
*/
public class GlimpseLayout implements GlimpsePainter, GlimpseTarget, Mouseable
{
protected String name = "";
// stores true when child GlimpseLayouts have been laid out
// for a given GlimpseLayoutStack. A null or false value
// indicates that the children must be laid out again
protected GlimpseLayoutCache layoutClean;
// stores the location/bounds of this GlimpseLayout
// as laid out inside its parent GlimpseLayout for
// a given GlimpseLayoutStack
protected GlimpseLayoutCache layoutCache;
// delegate class which manages actually laying out
// child GlimpseLayouts
protected GlimpseLayoutDelegate layoutDelegate;
// helper class which handles ordering of GlimpseLayouts
protected LayoutManager manager;
// lock controlling access to mutable state of this GlimpseLayout
protected ReentrantLock lock = new ReentrantLock( );
// listeners attached to this GlimpseLayout
protected Set mouseListeners;
protected Set mouseMotionListeners;
protected Set mouseWheelListeners;
// unmodifiable views to the above listeners for passing
// to external classes
protected Collection mouseListenersUnmodifiable;
protected Collection mouseMotionListenersUnmodifiable;
protected Collection mouseWheelListenersUnmodifiable;
// flags indicating event handling and repaint behavior
protected boolean isEventGenerator = true;
protected boolean isEventConsumer = true;
protected boolean isVisible = true;
public GlimpseLayout( GlimpseLayout parent, String name )
{
this.layoutClean = new GlimpseLayoutCache( );
this.layoutCache = new GlimpseLayoutCache( );
this.layoutDelegate = new GlimpseLayoutDelegate( this );
this.manager = new LayoutManager( );
this.lock = new ReentrantLock( );
this.mouseListeners = new CopyOnWriteArraySet( );
this.mouseMotionListeners = new CopyOnWriteArraySet( );
this.mouseWheelListeners = new CopyOnWriteArraySet( );
this.mouseListenersUnmodifiable = Collections.unmodifiableCollection( this.mouseListeners );
this.mouseMotionListenersUnmodifiable = Collections.unmodifiableCollection( this.mouseMotionListeners );
this.mouseWheelListenersUnmodifiable = Collections.unmodifiableCollection( this.mouseWheelListeners );
this.name = name;
if ( parent != null )
{
parent.addLayout( this );
}
}
public GlimpseLayout( GlimpseLayout parent )
{
this( parent, null );
}
public GlimpseLayout( String name )
{
this( null, name );
}
public GlimpseLayout( )
{
this( null, null );
}
public ReentrantLock getLock( )
{
return lock;
}
public String getName( )
{
lock.lock( );
try
{
return name;
}
finally
{
lock.unlock( );
}
}
public void setName( String name )
{
lock.lock( );
try
{
this.name = name;
}
finally
{
lock.unlock( );
}
}
public void setLayoutManager( GlimpseLayoutManager manager )
{
lock.lock( );
try
{
layoutDelegate.setLayoutManager( manager );
invalidateLayout( );
}
finally
{
lock.unlock( );
}
}
public void setLayoutData( Object layoutData )
{
lock.lock( );
try
{
layoutDelegate.setLayoutData( layoutData );
invalidateLayout( );
}
finally
{
lock.unlock( );
}
}
@Override
public void removeLayout( GlimpseLayout layout )
{
lock.lock( );
try
{
manager.removeLayout( layout );
layoutDelegate.removeLayout( layout );
invalidateLayout( );
}
finally
{
lock.unlock( );
}
}
/**
* Removes all GlimpseLayouts added via {@code #addLayout(GlimpseLayout)}.
*/
@Override
public void removeAllLayouts( )
{
lock.lock( );
try
{
manager.removeAllLayouts( );
layoutDelegate.removeAll( );
invalidateLayout( );
}
finally
{
lock.unlock( );
}
}
/**
* Historical accident caused removeAll() and removeAllLayouts() to both exist
* they are both retained for backwards compatibility.
*
* @deprecated see {@link #removeAllLayouts()}
*/
public void removeAll( )
{
removeAllLayouts( );
}
/**
* @see {@link #setZOrder(GlimpseLayout, int)}
*/
@Override
public void setZOrder( GlimpseLayout layout, int zOrder )
{
lock.lock( );
try
{
manager.setZOrder( layout, zOrder );
layoutDelegate.setZOrder( layout, zOrder );
}
finally
{
lock.unlock( );
}
}
/**
* Sets the relative ordering constant for this painter. Painters with low
* z order will be painter first (in the back) and those with high z order
* will be painted last (in the front).
*
* The value itself has no meaning; it is relative to the z orders
* of the other painters in the GlimpseLayout.
* For {@link com.metsci.glimpse.layout.com.metsciGlimpseLayout} instances,
* the z order also affects the order in which mouse events are delivered to
* overlapping components.
*
* The z order is set to 0 by default. GlimpsePainters with the same z order
* are painted in the order they were added to the GlimpseLayout. This means the
* first painters added will be obscured by later painters.
*/
public void setZOrder( GlimpsePainter painter, int zOrder )
{
if ( painter instanceof GlimpseLayout )
{
setZOrder( ( GlimpseLayout ) painter, zOrder );
}
else
{
lock.lock( );
try
{
layoutDelegate.setZOrder( painter, zOrder );
}
finally
{
lock.unlock( );
}
}
}
@Override
public void addLayout( GlimpseLayout layout )
{
addLayout( layout, null, 0 );
}
public void addLayout( GlimpseLayout layout, GlimpsePainterCallback callback )
{
addLayout( layout, callback, 0 );
}
@Override
public void addLayout( GlimpseLayout layout, int zOrder )
{
addLayout( layout, null, zOrder );
}
public void addLayout( GlimpseLayout layout, GlimpsePainterCallback callback, int zOrder )
{
lock.lock( );
try
{
manager.addLayout( layout, zOrder );
layoutDelegate.addLayout( layout, callback, zOrder );
invalidateLayout( );
}
finally
{
lock.unlock( );
}
}
public void addPainter( GlimpsePainter painter )
{
addPainter( painter, null, 0 );
}
public void addPainter( GlimpsePainter painter, GlimpsePainterCallback callback )
{
addPainter( painter, callback, 0 );
}
public void addPainter( GlimpsePainter painter, int zOrder )
{
addPainter( painter, null, zOrder );
}
public void addPainter( GlimpsePainter painter, GlimpsePainterCallback callback, int zOrder )
{
if ( painter instanceof GlimpseLayout )
{
addLayout( ( GlimpseLayout ) painter, callback, zOrder );
}
else
{
addPainter0( painter, callback, zOrder );
}
}
protected void addPainter0( GlimpsePainter painter, GlimpsePainterCallback callback, int zOrder )
{
lock.lock( );
try
{
layoutDelegate.addPainter( painter, callback, zOrder );
invalidateLayout( );
}
finally
{
lock.unlock( );
}
}
public void removePainter( GlimpsePainter painter )
{
lock.lock( );
try
{
layoutDelegate.removePainter( painter );
invalidateLayout( );
}
finally
{
lock.unlock( );
}
}
public void invalidateLayout( )
{
lock.lock( );
try
{
layoutClean.clear( );
layoutDelegate.invalidateLayout( );
}
finally
{
lock.unlock( );
}
}
public GlimpseBounds layoutTo( GlimpseTargetStack stack )
{
lock.lock( );
try
{
// get our cached bounds for the current context
GlimpseBounds bounds = layoutCache.getValue( stack );
// if the cache doesn't contain our bounds then we take our size from
// the size of the top GlimpseTarget in the current context
// (i.e we fill our parent completely)
if ( bounds == null )
{
bounds = stack.getBounds( );
layoutCache.setValue( stack, bounds );
}
// now that we know our size, if we are marked as dirty,
// lay out our children (and their children, recursively)
if ( isDirty( stack ) )
{
layoutDelegate.layoutTo( stack, bounds );
setDirty( stack, false );
}
return bounds;
}
finally
{
lock.unlock( );
}
}
// the top of the GlimpseTargetStack is the GlimpseLayout's immediate parent
public GlimpseBounds layoutTo( GlimpseContext context )
{
return layoutTo( context.getTargetStack( ) );
}
@Override
// the top of the GlimpseTargetStack is the GlimpseLayout's immediate parent
// (the GlimseTarget which we are "painting onto")
public void paintTo( GlimpseContext context )
{
lock.lock( );
try
{
// ensure that we have been laid out properly
GlimpseBounds bounds = layoutTo( context );
// push our bounds onto the layout stack
context.getTargetStack( ).push( this, bounds );
// paint our children with our bounds on top of the layout stack
layoutDelegate.paintTo( context );
// once our children (and their children recursively) have finished
// painting remove our bounds from the layout stack
context.getTargetStack( ).pop( );
}
finally
{
lock.unlock( );
}
}
@Override
public GlimpseBounds getTargetBounds( GlimpseTargetStack stack )
{
lock.lock( );
try
{
GlimpseBounds bounds = layoutCache.getValue( stack );
if ( bounds == null )
{
return layoutTo( stack );
}
else
{
return bounds;
}
}
finally
{
lock.unlock( );
}
}
@SuppressWarnings( { "unchecked", "rawtypes" } )
@Override
public List getTargetChildren( )
{
// layoutManager returns an unmodifiable list, thus this cast is typesafe
// (there is no way for the recipient of the List view to
// add GlimpseTargets which are not GlimpseLayouts to the list)
return ( List ) manager.getLayoutList( );
}
@Override
public void dispose( GlimpseContext context )
{
lock.lock( );
try
{
layoutDelegate.dispose( context );
}
finally
{
lock.unlock( );
}
}
@Override
public boolean isDisposed( )
{
lock.lock( );
try
{
return layoutDelegate.isDisposed( );
}
finally
{
lock.unlock( );
}
}
@Override
public String toString( )
{
return name == null ? super.toString( ) : name;
}
@Override
public Collection getGlimpseMouseListeners( )
{
return this.mouseListenersUnmodifiable;
}
@Override
public Collection getGlimpseMouseMotionListeners( )
{
return this.mouseMotionListenersUnmodifiable;
}
@Override
public Collection getGlimpseMouseWheelListeners( )
{
return this.mouseWheelListenersUnmodifiable;
}
@Override
public void addGlimpseMouseListener( GlimpseMouseListener listener )
{
this.mouseListeners.add( listener );
}
@Override
public void addGlimpseMouseMotionListener( GlimpseMouseMotionListener listener )
{
this.mouseMotionListeners.add( listener );
}
@Override
public void addGlimpseMouseWheelListener( GlimpseMouseWheelListener listener )
{
this.mouseWheelListeners.add( listener );
}
@Override
public void addGlimpseMouseAllListener( GlimpseMouseAllListener listener )
{
this.addGlimpseMouseListener( listener );
this.addGlimpseMouseMotionListener( listener );
this.addGlimpseMouseWheelListener( listener );
}
@Override
public void removeGlimpseMouseAllListener( GlimpseMouseAllListener listener )
{
this.removeGlimpseMouseListener( listener );
this.removeGlimpseMouseMotionListener( listener );
this.removeGlimpseMouseWheelListener( listener );
}
@Override
public void removeGlimpseMouseListener( GlimpseMouseListener listener )
{
this.mouseListeners.remove( listener );
}
@Override
public void removeGlimpseMouseMotionListener( GlimpseMouseMotionListener listener )
{
this.mouseMotionListeners.remove( listener );
}
@Override
public void removeGlimpseMouseWheelListener( GlimpseMouseWheelListener listener )
{
this.mouseWheelListeners.remove( listener );
}
@Override
public void removeAllGlimpseListeners( )
{
lock.lock( );
try
{
this.mouseListeners.clear( );
this.mouseWheelListeners.clear( );
this.mouseMotionListeners.clear( );
}
finally
{
lock.unlock( );
}
}
protected boolean isDirty( GlimpseTargetStack stack )
{
Boolean isClean = layoutClean.getValue( stack );
return isClean == null || !isClean;
}
protected void setDirty( GlimpseTargetStack stack, boolean dirty )
{
layoutClean.setValue( stack, !dirty );
}
protected void cacheBounds( GlimpseContext context, GlimpseBounds bounds )
{
this.layoutCache.setValue( context, bounds );
}
protected void cacheBounds( GlimpseTargetStack stack, GlimpseBounds bounds )
{
this.layoutCache.setValue( stack, bounds );
}
protected GlimpseBounds getBounds( GlimpseContext context )
{
return layoutCache.getValue( context );
}
protected void preLayout( GlimpseTargetStack stack, GlimpseBounds bounds )
{
// do nothing, subclasses may override
}
protected void preLayout( GlimpseContext context, GlimpseBounds bounds )
{
preLayout( context.getTargetStack( ), bounds );
}
public GlimpseLayoutManager getLayoutManager( )
{
return getDelegate( ).getLayoutManager( );
}
protected GlimpseLayoutDelegate getDelegate( )
{
return layoutDelegate;
}
@Override
public void mouseEntered( GlimpseMouseEvent event )
{
for ( GlimpseMouseListener listener : mouseListeners )
{
listener.mouseEntered( event );
if ( event.isHandled( ) ) break;
}
}
@Override
public void mouseExited( GlimpseMouseEvent event )
{
for ( GlimpseMouseListener listener : mouseListeners )
{
listener.mouseExited( event );
if ( event.isHandled( ) ) break;
}
}
@Override
public void mousePressed( GlimpseMouseEvent event )
{
for ( GlimpseMouseListener listener : mouseListeners )
{
listener.mousePressed( event );
if ( event.isHandled( ) ) break;
}
}
@Override
public void mouseReleased( GlimpseMouseEvent event )
{
for ( GlimpseMouseListener listener : mouseListeners )
{
listener.mouseReleased( event );
if ( event.isHandled( ) ) break;
}
}
@Override
public void mouseMoved( GlimpseMouseEvent event )
{
for ( GlimpseMouseMotionListener listener : mouseMotionListeners )
{
listener.mouseMoved( event );
if ( event.isHandled( ) ) break;
}
}
@Override
public void mouseWheelMoved( GlimpseMouseEvent event )
{
for ( GlimpseMouseWheelListener listener : mouseWheelListeners )
{
listener.mouseWheelMoved( event );
if ( event.isHandled( ) ) break;
}
}
@Override
public void setLookAndFeel( LookAndFeel laf )
{
lock.lock( );
try
{
layoutDelegate.setLookAndFeel( laf );
}
finally
{
lock.unlock( );
}
}
/**
* Event consumers do not pass on mouse events to their parents.
*/
@Override
public boolean isEventConsumer( )
{
return this.isEventConsumer;
}
@Override
public void setEventConsumer( boolean consume )
{
this.isEventConsumer = consume;
}
/**
* Event generators do not generate mouse events if they are the top layout
* when a mouse event occurs.
*/
@Override
public boolean isEventGenerator( )
{
return this.isEventGenerator;
}
@Override
public void setEventGenerator( boolean generate )
{
this.isEventGenerator = generate;
}
public void setVisible( boolean visible )
{
this.isVisible = visible;
}
public boolean isVisible( )
{
return this.isVisible;
}
}