package controlP5; /** * controlP5 is a processing gui library. * * 2006-2015 by Andreas Schlegel * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA * * @author Andreas Schlegel (http://www.sojamo.de) * @modified ##date## * @version ##version## * */ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.AbstractMap.SimpleEntry; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Set; /** * The ControlBroadcaster handles all controller value changes and distributes them accordingly to * its listeners. The ControlBroadcaster is primarily for internal use only but can be accessed * through an instance of the ControlP5 class. Instead of accessing the ControlBroadcaster directly, * use the convenience methods available from the ControlP5 class. * * @see controlP5.ControlP5#getControlBroadcaster() */ public class ControlBroadcaster { private int _myControlEventType = ControlP5Constants.INVALID; private ControllerPlug _myControlEventPlug = null; private ControllerPlug _myControllerCallbackEventPlug = null; private ControlP5 cp5; private String _myEventMethod = "controlEvent"; private String _myControllerCallbackEventMethod = "controlEvent"; private ArrayList< ControlListener > _myControlListeners; private Set< Entry< CallbackListener , Controller< ? >>> _myControllerCallbackListeners; private static boolean setPrintStackTrace = true; private static boolean ignoreErrorMessage = false; private static Map< Class< ? > , Field[] > fieldcache = new HashMap< Class< ? > , Field[] >( ); private static Map< Class< ? > , Method[] > methodcache = new HashMap< Class< ? > , Method[] >( ); boolean broadcast = true; protected ControlBroadcaster( ControlP5 theControlP5 ) { cp5 = theControlP5; _myControlListeners = new ArrayList< ControlListener >( ); _myControllerCallbackListeners = new HashSet< Entry< CallbackListener , Controller< ? >>>( ); _myControlEventPlug = checkObject( cp5.papplet , getEventMethod( ) , new Class[] { ControlEvent.class } ); _myControllerCallbackEventPlug = checkObject( cp5.papplet , _myControllerCallbackEventMethod , new Class[] { CallbackEvent.class } ); if ( _myControlEventPlug != null ) { _myControlEventType = ControlP5Constants.METHOD; } } public ControlBroadcaster addListener( ControlListener ... theListeners ) { for ( ControlListener l : theListeners ) { _myControlListeners.add( l ); } return this; } public ControlBroadcaster removeListener( ControlListener ... theListeners ) { for ( ControlListener l : theListeners ) { _myControlListeners.remove( l ); } return this; } /** * Returns a ControlListener by index * * @param theIndex * @return */ public ControlListener getListener( int theIndex ) { if ( theIndex < 0 || theIndex >= _myControlListeners.size( ) ) { return null; } return _myControlListeners.get( theIndex ); } /** * Returns the size of the ControlListener list * * @return */ public int listenerSize( ) { return _myControlListeners.size( ); } public ControlBroadcaster addCallback( CallbackListener ... theListeners ) { for ( CallbackListener l : theListeners ) { _myControllerCallbackListeners.add( new SimpleEntry< CallbackListener , Controller< ? >>( l , new EmptyController( ) ) ); } return this; } public ControlBroadcaster addCallback( CallbackListener theListener ) { _myControllerCallbackListeners.add( new SimpleEntry< CallbackListener , Controller< ? >>( theListener , new EmptyController( ) ) ); return this; } /** * Adds a CallbackListener for a list of controllers. * * @param theListener * @param theController */ public void addCallback( CallbackListener theListener , Controller< ? > ... theController ) { for ( Controller< ? > c : theController ) { _myControllerCallbackListeners.add( new SimpleEntry< CallbackListener , Controller< ? >>( theListener , c ) ); } } public ControlBroadcaster removeCallback( CallbackListener ... theListeners ) { for ( CallbackListener c : theListeners ) { _myControllerCallbackListeners.remove( c ); } return this; } public ControlBroadcaster removeCallback( CallbackListener theListener ) { _myControllerCallbackListeners.remove( theListener ); return this; } /** * Removes a CallbackListener for a particular controller * * @param theController */ public ControlBroadcaster removeCallback( Controller< ? > ... theControllers ) { for ( Controller< ? > c : theControllers ) { for ( Entry< CallbackListener , Controller< ? >> entry : _myControllerCallbackListeners ) { if ( c != null && entry.getValue( ).equals( c ) ) { _myControllerCallbackListeners.remove( entry ); } } } return this; } public ControlBroadcaster plug( Object theObject , final String theControllerName , final String theTargetMethod ) { plug( theObject , cp5.getController( theControllerName ) , theTargetMethod ); return this; } public ControlBroadcaster plug( Object theObject , final Controller< ? > theController , final String theTargetMethod ) { if ( theController != null ) { ControllerPlug myControllerPlug = checkObject( theObject , theTargetMethod , ControlP5Constants.acceptClassList ); if ( myControllerPlug == null ) { return this; } else { if ( theController.checkControllerPlug( myControllerPlug ) == false ) { theController.addControllerPlug( myControllerPlug ); } return this; } } return this; } static Field[] getFieldsFor( Class< ? > theClass ) { if ( !fieldcache.containsKey( theClass ) ) { fieldcache.put( theClass , theClass.getDeclaredFields( ) ); } return fieldcache.get( theClass ); } static Method[] getMethodFor( Class< ? > theClass ) { if ( !methodcache.containsKey( theClass ) ) { methodcache.put( theClass , theClass.getMethods( ) ); } return methodcache.get( theClass ); } protected static ControllerPlug checkObject( final Object theObject , final String theTargetName , final Class< ? >[] theAcceptClassList ) { Class< ? > myClass = theObject.getClass( ); Method[] myMethods = getMethodFor( myClass ); for ( int i = 0 ; i < myMethods.length ; i++ ) { if ( ( myMethods[ i ].getName( ) ).equals( theTargetName ) ) { if ( myMethods[ i ].getParameterTypes( ).length == 1 ) { // hack to detect controlEvent(CallbackEvent) which is otherwise // overwritten by controlEvent(ControlEvent) if ( theAcceptClassList.length == 1 ) { if ( theAcceptClassList[ 0 ] == CallbackEvent.class ) { ControllerPlug cp = new ControllerPlug( CallbackEvent.class , theObject , theTargetName , ControlP5Constants.EVENT , -1 ); if ( cp.getMethod( ) == null ) { return null; } return cp; } } if ( myMethods[ i ].getParameterTypes( )[ 0 ] == ControlP5Constants.controlEventClass ) { return new ControllerPlug( ControlEvent.class , theObject , theTargetName , ControlP5Constants.EVENT , -1 ); } for ( int j = 0 ; j < theAcceptClassList.length ; j++ ) { if ( myMethods[ i ].getParameterTypes( )[ 0 ] == theAcceptClassList[ j ] ) { return new ControllerPlug( theObject , theTargetName , ControlP5Constants.METHOD , j , theAcceptClassList ); } } } else if ( myMethods[ i ].getParameterTypes( ).length == 0 ) { return new ControllerPlug( theObject , theTargetName , ControlP5Constants.METHOD , -1 , theAcceptClassList ); } break; } } Field[] myFields = getFieldsFor( myClass ); for ( int i = 0 ; i < myFields.length ; i++ ) { if ( ( myFields[ i ].getName( ) ).equals( theTargetName ) ) { for ( int j = 0 ; j < theAcceptClassList.length ; j++ ) { if ( myFields[ i ].getType( ) == theAcceptClassList[ j ] ) { return new ControllerPlug( theObject , theTargetName , ControlP5Constants.FIELD , j , theAcceptClassList ); } } break; } } return null; } public ControlBroadcaster broadcast( final ControlEvent theControlEvent , final int theType ) { if ( broadcast ) { for ( ControlListener cl : _myControlListeners ) { cl.controlEvent( theControlEvent ); } if ( !theControlEvent.isTab( ) && !theControlEvent.isGroup( ) ) { if ( theControlEvent.getController( ).getControllerPlugList( ).size( ) > 0 ) { if ( theType == ControlP5Constants.STRING ) { for ( ControllerPlug cp : theControlEvent.getController( ).getControllerPlugList( ) ) { callTarget( cp , theControlEvent.getStringValue( ) ); } } else if ( theType == ControlP5Constants.ARRAY ) { } else if ( theType == ControlP5Constants.BOOLEAN ) { for ( ControllerPlug cp : theControlEvent.getController( ).getControllerPlugList( ) ) { Controller controller = theControlEvent.getController( ); if ( controller instanceof Icon ) { callTarget( cp , ( ( Icon ) controller ).getBooleanValue( ) ); } else if ( controller instanceof Button ) { callTarget( cp , ( ( Button ) controller ).getBooleanValue( ) ); } else if ( controller instanceof Toggle ) { callTarget( cp , ( ( Toggle ) controller ).getBooleanValue( ) ); } } } else { for ( ControllerPlug cp : theControlEvent.getController( ).getControllerPlugList( ) ) { if ( cp.checkType( ControlP5Constants.EVENT ) ) { invokeMethod( cp.getObject( ) , cp.getMethod( ) , new Object[] { theControlEvent } ); } else { callTarget( cp , theControlEvent.getValue( ) ); } } } } } if ( _myControlEventType == ControlP5Constants.METHOD ) { invokeMethod( _myControlEventPlug.getObject( ) , _myControlEventPlug.getMethod( ) , new Object[] { theControlEvent } ); } } return this; } protected void callTarget( final ControllerPlug thePlug , final float theValue ) { if ( thePlug.checkType( ControlP5Constants.METHOD ) ) { invokeMethod( thePlug.getObject( ) , thePlug.getMethod( ) , thePlug.getMethodParameter( theValue ) ); } else if ( thePlug.checkType( ControlP5Constants.FIELD ) ) { invokeField( thePlug.getObject( ) , thePlug.getField( ) , thePlug.getFieldParameter( theValue ) ); } } protected void callTarget( final ControllerPlug thePlug , final String theValue ) { if ( thePlug.checkType( ControlP5Constants.METHOD ) ) { invokeMethod( thePlug.getObject( ) , thePlug.getMethod( ) , new Object[] { theValue } ); } else if ( thePlug.checkType( ControlP5Constants.FIELD ) ) { invokeField( thePlug.getObject( ) , thePlug.getField( ) , theValue ); } } protected void callTarget( final ControllerPlug thePlug , final boolean theValue ) { if ( thePlug.checkType( ControlP5Constants.METHOD ) ) { invokeMethod( thePlug.getObject( ) , thePlug.getMethod( ) , new Object[] { theValue } ); } else if ( thePlug.checkType( ControlP5Constants.FIELD ) ) { invokeField( thePlug.getObject( ) , thePlug.getField( ) , theValue ); } } private void invokeField( final Object theObject , final Field theField , final Object theParam ) { try { theField.set( theObject , theParam ); } catch ( IllegalAccessException e ) { ControlP5.logger( ).warning( e.toString( ) ); } } private void invokeMethod( final Object theObject , final Method theMethod , final Object[] theParam ) { try { if ( theParam[ 0 ] == null ) { theMethod.invoke( theObject , new Object[ 0 ] ); } else { theMethod.invoke( theObject , theParam ); } } catch ( IllegalArgumentException e ) { ControlP5.logger( ).warning( e.toString( ) ); /** * TODO thrown when plugging a String method/field. */ } catch ( IllegalAccessException e ) { printMethodError( theMethod , e ); } catch ( InvocationTargetException e ) { printMethodError( theMethod , e ); } catch ( NullPointerException e ) { printMethodError( theMethod , e ); } } protected String getEventMethod( ) { return _myEventMethod; } protected void invokeAction( CallbackEvent theEvent ) { boolean invoke; for ( Entry< CallbackListener , Controller< ? >> entry : _myControllerCallbackListeners ) { invoke = ( entry.getValue( ).getClass( ).equals( EmptyController.class ) ) ? true : ( entry.getValue( ).equals( theEvent.getController( ) ) ) ? true : false; if ( invoke ) { entry.getKey( ).controlEvent( theEvent ); } } if ( _myControllerCallbackEventPlug != null ) { invokeMethod( cp5.papplet , _myControllerCallbackEventPlug.getMethod( ) , new Object[] { theEvent } ); } } private void printMethodError( Method theMethod , Exception theException ) { if ( !ignoreErrorMessage ) { ControlP5.logger( ).severe( "An error occured while forwarding a Controller event, please check your code at " + theMethod.getName( ) + ( !setPrintStackTrace ? " " + "exception: " + theException : "" ) ); if ( setPrintStackTrace ) { theException.printStackTrace( ); } } } public static void ignoreErrorMessage( boolean theFlag ) { ignoreErrorMessage = theFlag; } public static void setPrintStackTrace( boolean theFlag ) { setPrintStackTrace = theFlag; } private class EmptyController extends Controller< EmptyController > { protected EmptyController( ) { this( 0 , 0 ); } protected EmptyController( int theX , int theY ) { super( "empty" + ( ( int ) ( Math.random( ) * 1000000 ) ) , theX , theY ); // TODO Auto-generated constructor stub } @Override public EmptyController setValue( float theValue ) { // TODO Auto-generated method stub return this; } } /** * @exclude */ @Deprecated public void plug( final String theControllerName , final String theTargetMethod ) { plug( cp5.papplet , theControllerName , theTargetMethod ); } }