/* * 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.context; import java.util.Iterator; import java.util.List; import java.util.ListIterator; import com.metsci.glimpse.event.mouse.GlimpseMouseEvent; /** * <p>Utility method for manipulating {@link GlimpseTargetStack} instances.</p> * * <p>In order to uniquely name a GlimpseLayout, the stack containing the * GlimpseLayout and all of its parent layouts down to the underlying GlimpseCanvas * must be provided (because the GlimpseLayout may be reused and could have * multiple parents).</p> * * @author ulman */ public class TargetStackUtil { /** * Creates a new GlimpseTargetStack which is an exact copy of the given stack. * * @param stack the stack to copy * @return a deep copy of the provided stack */ public static GlimpseTargetStack newTargetStack( GlimpseTargetStack stack ) { return newTargetStack( ).push( stack ); } /** * Creates a new GlimpseTargetStack which is an exact copy of the given stack * and is unmodifiable. * * @param stack the stack to copy * @return a deep copy of the provided stack */ public static GlimpseTargetStack newUnmodifiableTargetStack( GlimpseTargetStack stack ) { return new UnmodifiableTargetStack( newTargetStack( ).push( stack ) ); } /** * Creates a new target stack which is the concatenation of the provided GlimpseTargets. * The first provided GlimpseTarget is placed at the bottom of the new GlimpseTargetStack. * * @param targets the GlimpseTargets to concatenate * @return the newly constructed GlimpseTargetStack */ public static GlimpseTargetStack newTargetStack( GlimpseTarget... targets ) { return new GlimpseTargetStackImpl( targets ); } /** * Returns true if the query stack ends with the sequence of GlimpseTargets defined by the prefix stack. * Ignores the GlimpseBounds. * * @param query the GlimpseTargetStack to investigate * @param suffix a GlimpseTargetStack to search for at the end of the query stack * @return whether the query stack ends with the GlimpseTargets in the prefix stack */ public static boolean endsWith( GlimpseTargetStack query, GlimpseTargetStack suffix ) { int suffixSize = suffix.getSize( ); int querySize = query.getSize( ); if ( suffixSize > querySize ) return false; List<GlimpseTarget> suffixList = suffix.getTargetList( ); ListIterator<GlimpseTarget> suffixIter = suffixList.listIterator( suffixList.size( ) ); List<GlimpseTarget> queryList = query.getTargetList( ); ListIterator<GlimpseTarget> queryIter = queryList.listIterator( queryList.size( ) ); while ( suffixIter.hasPrevious( ) ) { GlimpseTarget suffixTarget = suffixIter.previous( ); GlimpseTarget queryTarget = queryIter.previous( ); if ( !queryTarget.equals( suffixTarget ) ) return false; } return true; } /** * Returns true if the query target stack contains the provided target. * * @param query the GlimpseTargetStack to investigate * @param target the GlimpseTarget to look for in the query stack * @return true if the query stack contains the target */ public static boolean contains( GlimpseTargetStack query, GlimpseTarget target ) { Iterator<GlimpseTarget> queryIter = query.getTargetList( ).iterator( ); while ( queryIter.hasNext( ) ) { GlimpseTarget queryTarget = queryIter.next( ); if ( queryTarget.equals( target ) ) return true; } return false; } /** * * @return true if either target stack contain a GlimpseTarget in common */ public static boolean intersects( GlimpseTargetStack query1, GlimpseTargetStack query2 ) { for ( GlimpseTarget target1 : query1.getTargetList( ) ) { for ( GlimpseTarget target2 : query2.getTargetList( ) ) { if ( target1.equals( target2 ) ) return true; } } return false; } /** * Returns true if the query stack starts with the sequence of GlimpseTargets defined by the prefix stack. * Ignores the GlimpseBounds. * * @param query the GlimpseTargetStack to investigate * @param prefix a GlimpseTargetStack to search for at the start of the query stack * @return whether the query stack starts with the GlimpseTargets in the prefix stack */ public static boolean startsWith( GlimpseTargetStack query, GlimpseTargetStack prefix ) { int prefixSize = prefix.getSize( ); int querySize = query.getSize( ); if ( prefixSize > querySize ) return false; Iterator<GlimpseTarget> prefixIter = prefix.getTargetList( ).iterator( ); Iterator<GlimpseTarget> queryIter = query.getTargetList( ).iterator( ); while ( prefixIter.hasNext( ) ) { GlimpseTarget prefixTarget = prefixIter.next( ); GlimpseTarget queryTarget = queryIter.next( ); if ( !queryTarget.equals( prefixTarget ) ) return false; } return true; } public static GlimpseTargetStack popTo( GlimpseTargetStack stack, GlimpseTarget target ) { // short circuit of the event is already relative to the target if ( stack.getTarget( ) == target ) return stack; stack = newTargetStack( stack ); while ( stack.getSize( ) > 0 ) { stack.pop( ); if ( stack.getTarget( ) == target ) return stack; } return null; } public static GlimpseTargetStack pushToBottom( GlimpseTargetStack stack, int x, int y ) { stack = newTargetStack( stack ); for ( ;; ) { boolean foundNextChild = false; GlimpseTarget target = stack.getTarget( ); List<GlimpseTarget> children = target.getTargetChildren( ); for ( int i = children.size( ) - 1; i >= 0; i-- ) { GlimpseTarget child = children.get( i ); GlimpseBounds childBounds = child.getTargetBounds( stack ); if ( childBounds.contains( x, y ) ) { stack.push( child, childBounds ); foundNextChild = true; break; } } if ( !foundNextChild ) { break; } } return stack; } public static GlimpseMouseEvent translateCoordinates( GlimpseMouseEvent event, GlimpseTarget target ) { // short circuit of the event is already relative to the target if ( event.getTargetStack( ).getTarget( ) == target ) return event; GlimpseTargetStack stack = newTargetStack( event.getTargetStack( ) ); // coordinates relative to the GlimpseTarget at the top of the stack int x = event.getX( ); int y = event.getY( ); GlimpseBounds topBounds = stack.getBounds( ); while ( stack.getSize( ) > 0 ) { stack.pop( ); if ( stack.getTarget( ) == target ) { GlimpseBounds targetBounds = stack.getBounds( ); x += topBounds.getX( ) - targetBounds.getX( ); y += targetBounds.getHeight( ) - ( topBounds.getY( ) - targetBounds.getY( ) + topBounds.getHeight( ) ); return new GlimpseMouseEvent( event, stack, x, y ); } } // the provided GlimpseTarget was not in the GlimpseMouseEvent hierarchy // (the click was not on top of the provided GlimpseTarget) return null; } }