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 04/07/2022
 * @version 2.3.3
 * 
 */

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PGraphics;
import processing.core.PImage;
import processing.event.KeyEvent;

/**
 * <p>
 * Controller is an abstract class that is extended by any available controller within controlP5. this is the full
 * documentation list for all methods available for a controller. An event triggered by a controller will be forwarded
 * to the main program. If a void controlEvent(ControlEvent theEvent) {} method is available, this method will be
 * called.
 * </p>
 * <p>
 * A Controller can notify the main program in 2 different ways:
 * </p>
 * <ul>
 * <li>(1) add method controlEvent(ControlEvent theEvent) to your sketch. ControlP5 will automatically detect this
 * method and will used it to forward any controlEvent triggered by a controller - you can disable forwarding by using
 * setBroadcast(false) {@link controlP5.Controller#setBroadcast(boolean)}</li>
 * <li>(2) each controller requires a unique name when being create. In case an existing name is used for a newly
 * created Controller, the existing one will be overwritten. each unique name can be used to automatically link a
 * controller to either a method or a field within your program.</li>
 * </ul>
 * 
 * @see controlP5.Bang
 * @see controlP5.Button
 * @see controlP5.Knob
 * @see controlP5.Matrix
 * @see controlP5.MultiList
 * @see controlP5.Numberbox
 * @see controlP5.RadioButton
 * @see controlP5.ListBox
 * @see controlP5.Slider
 * @see controlP5.Textarea
 * @see controlP5.Textfield
 * @see controlP5.Textlabel
 * @see controlP5.Toggle
 * @see controlP5.ControlGroup
 * @see controlP5.ControlBehavior
 * @see controlP5.ControlEvent
 * 
 * @example use/ControlP5basics
 */
public abstract class Controller< T > implements ControllerInterface< T > , CDrawable , ControlP5Constants {

	protected float[] position = new float[ 2 ];
	protected float[] positionBuffer = new float[ 2 ];
	protected float[] absolutePosition = new float[ 2 ];
	protected ControllerInterface< ? > _myParent;
	protected final String _myName;
	protected ControlWindow _myControlWindow;
	protected boolean isMousePressed = false;
	protected ControlP5 cp5;
	private int width;
	private int height;
	protected int _myId = -1;
	protected float _myValue = Float.NaN;
	protected float _myDefaultValue = Float.NaN;
	protected String _myStringValue = "";
	protected float[] _myArrayValue;
	protected Label _myCaptionLabel;
	protected Label _myValueLabel;
	protected String _myDescription;
	protected boolean isLabelVisible = true;
	protected boolean isMoveable = true;
	protected boolean isBroadcast = true;
	protected boolean isVisible = true;
	protected boolean isActive = false;
	protected boolean isLock = false;
	protected boolean isUserInteraction = true;
	protected boolean isInit = false;
	protected List< ControlListener > _myControlListener;
	protected Map< Integer , List< CallbackListener >> _myControlListenerLUT;
	protected CColor color = new CColor( );
	protected float _myMin;
	protected float _myMax;
	protected float _myUnit;
	protected String target;
	protected Object targetObject;
	protected ControlBehavior _myBehavior;
	protected boolean isBehavior;
	protected List< Controller< ? >> subelements;
	protected int _myBroadcastType = FLOAT;
	protected boolean isUpdate = false;
	protected int _myDecimalPoints = 2;
	public static int autoWidth = 49;
	public static int autoHeight = 19;
	public static float[] autoSpacing = new float[] { 10 , 10 };
	protected boolean[] availableImages = new boolean[ 4 ];
	protected PImage[] images = new PImage[ 4 ];
	protected ControllerView< T > _myControllerView;
	protected ControllerView< T > _myDebugView;
	protected int _myDisplayMode = DEFAULT;
	protected int _myPickingColor = 0xffffff00;
	protected boolean mouseover;
	protected String _myAddress = "";
	protected List< ControllerPlug > _myControllerPlugList;
	protected boolean tooltipEnabled;
	protected boolean listening;
	protected boolean isInside = false;
	private T me;
	protected boolean isDragged;

	/**
	 * TODO add distribution options for MOVE, RELEASE, and PRESSED. setDecimalPoints: setDcimalPoints(6) does only show
	 * 2 digits after the point
	 */

	/**
	 * Convenience constructor to extend Controller.
	 * 
	 * @example use/ControlP5extendController
	 * @param theControlP5
	 * @param theName
	 */
	public Controller( ControlP5 theControlP5 , String theName ) {
		this( theControlP5 , theControlP5.getDefaultTab( ) , theName , 0 , 0 , autoWidth , autoHeight );
		theControlP5.register( theControlP5.papplet , theName , this );
	}

	protected Controller( final ControlP5 theControlP5 , final ControllerGroup< ? > theParent , final String theName , final float theX , final float theY , final int theWidth , final int theHeight ) {
		cp5 = theControlP5;
		me = ( T ) this;
		if ( cp5 == null ) {
			isBroadcast = false;
		}
		_myName = theName;

		set( position , theX , theY );
		set( positionBuffer , theX , theY );

		setParent( theParent );
		if ( theParent != null ) {
			color.set( theParent.color );
		} else {
			color.set( cp5.color );
		}
		width = theWidth;
		height = theHeight;

		_myCaptionLabel = new Label( cp5 , theName );
		_myCaptionLabel.setColor( color.getCaptionLabel( ) );
		_myValueLabel = new Label( cp5 , "-" );
		_myValueLabel.setColor( color.getCaptionLabel( ) );

		_myControllerPlugList = new ArrayList< ControllerPlug >( );
		_myControlListener = new ArrayList< ControlListener >( );
		subelements = new ArrayList< Controller< ? >>( );
		_myArrayValue = new float[ 0 ];
		_myDebugView = new DebugView( );
		setView( _myDebugView );
	}

	List< Controller< ? >> getSubelements( ) {
		return subelements;
	}

	/**
	 * {@inheritDoc}
	 */
	@ControlP5.Layout public String getAddress( ) {
		return _myAddress;
	}

	@Override @ControlP5.Invisible @ControlP5.Layout public T setAddress( String theAddress ) {
		if ( _myAddress.length( ) == 0 ) {
			_myAddress = theAddress;
		}
		return me;
	}

	protected Controller( final ControlP5 theControlP5 , final String theName , final float theX , final float theY , final int theWidth , final int theHeight ) {
		this( theControlP5 , theControlP5.getDefaultTab( ) , theName , theX , theY , theWidth , theHeight );
	}

	protected Controller( final String theName , final int theX , final int theY ) {
		_myName = theName;
		set( position , theX , theY );
	}

	@ControlP5.Invisible public void init( ) {

		_myDefaultValue = _myValue;

		// plug to a method or field inside the main
		// papplet.
		// forwarding a ControlEvent to the controlEvent()
		// method inside
		// the main papplet is done by the
		// controlbroadcaster.
		cp5.getControlBroadcaster( ).plug( cp5.papplet , this , _myName );
		initControllerValue( );
		isInit = cp5.isAutoInitialization;
		setValue( _myDefaultValue );
		isInit = true;
		updateDisplayMode( DEFAULT );
	}

	protected final void initControllerValue( ) {

		// this is painful. deciding if the value has been
		// set by parameter or by reading the
		// current value of the corresponding field is
		// very painful here due to too many ifs and thens.
		// therefore this is done manually here -
		// very ugly though.

		boolean go = false;

		if ( getClass( ).equals( Numberbox.class ) ) {
			go = _myDefaultValue == 0;
		} else {
			go = _myDefaultValue == getMin( ) || Float.isNaN( getValue( ) );
		}
		if ( go ) {
			if ( _myControllerPlugList.size( ) == 1 ) {
				if ( getControllerPlugList( ).get( 0 ).getValue( ) == null ) {
					setDefaultValue( getMin( ) );
				} else {
					float myInitValue = 0;
					if ( getControllerPlugList( ).get( 0 ).getValue( ) instanceof Boolean ) {
						final boolean myBoolean = new Boolean( getControllerPlugList( ).get( 0 ).getValue( ).toString( ) ).booleanValue( );
						myInitValue = ( myBoolean == true ) ? 1f : 0f;

					} else if ( getControllerPlugList( ).get( 0 ).getValue( ) instanceof Float ) {
						myInitValue = ( new Float( getControllerPlugList( ).get( 0 ).getValue( ).toString( ) ) ).floatValue( );

					} else if ( getControllerPlugList( ).get( 0 ).getValue( ) instanceof Integer ) {
						myInitValue = ( new Integer( getControllerPlugList( ).get( 0 ).getValue( ).toString( ) ) ).intValue( );

					} else if ( getControllerPlugList( ).get( 0 ).getValue( ) instanceof String ) {
						_myStringValue = getControllerPlugList( ).get( 0 ).getValue( ).toString( );
					}
					setDefaultValue( myInitValue );
				}
			} else {
				if ( Float.isNaN( getDefaultValue( ) ) ) {
					setDefaultValue( getMin( ) );
				}
			}
		}
		_myValue = _myDefaultValue;
	}

	protected void updateFont( ControlFont theControlFont ) {
		setFont( theControlFont );
	}

	/**
	 * with setBehavior you can add a ControlBehavior to a controller. A ControlBehavior can be used to e.g.
	 * automatically change state, function, position, etc.
	 * 
	 * @example ControlP5behavior
	 * @param theBehavior ControlBehavior
	 * @return Controller
	 */
	public T setBehavior( final ControlBehavior theBehavior ) {
		isBehavior = true;
		_myBehavior = theBehavior;
		_myBehavior.init( this );
		return me;
	}

	/**
	 * @return Controller
	 */
	public T removeBehavior( ) {
		isBehavior = false;
		_myBehavior = null;
		return me;
	}

	/**
	 * @return ControlBehavior
	 */
	public ControlBehavior getBehavior( ) {
		return _myBehavior;
	}

	/**
	 * @return float
	 */
	@ControlP5.Layout public float getDefaultValue( ) {
		return _myDefaultValue;
	}

	/**
	 * set the default value.
	 * 
	 * @param theValue float
	 * @return Controller
	 */
	@ControlP5.Layout public T setDefaultValue( final float theValue ) {
		_myDefaultValue = theValue;
		return me;
	}

	/**
	 * enable or prevent the controller to be moveable. By default a controller is moveable.
	 * 
	 * @param theValue boolean
	 * @return Controller
	 */
	@ControlP5.Layout public T setMoveable( final boolean theValue ) {
		isMoveable = theValue;
		return me;
	}

	/**
	 * checks if a controller is moveable.
	 * 
	 * @return boolean
	 */
	@ControlP5.Layout public boolean isMoveable( ) {
		return isMoveable;
	}

	/**
	 * show or hide the labels of a controller.
	 * 
	 * @param theValue boolean
	 * @return Controller
	 */
	@ControlP5.Layout public T setLabelVisible( final boolean theValue ) {
		isLabelVisible = theValue;
		return me;
	}

	/**
	 * @return boolean
	 */
	@ControlP5.Layout public boolean isLabelVisible( ) {
		return isLabelVisible;
	}

	/**
	 * Use setBroadcast to enable and disable the broadcasting of changes in a controller's value. By default any value
	 * changes are forwarded to function controlEvent inside your program. use setBroadcast(false) to disable
	 * forwarding.
	 * 
	 * @param theFlag boolean
	 * @return Controller
	 */
	@ControlP5.Layout public T setBroadcast( final boolean theFlag ) {
		isBroadcast = theFlag;
		return me;
	}

	/**
	 * check if broadcasting is enabled or disabled for a controller. Every event relevant for a value change will be
	 * broadcasted to any of the value-listeners. By default broadcasting for a controller is enabled.
	 * 
	 * @return boolean
	 */
	@ControlP5.Layout public boolean isBroadcast( ) {
		return isBroadcast;
	}

	/**
	 * get the position of a controller. e.g. Controller.getPosition().x; {@inheritDoc}
	 */
	@ControlP5.Layout public float[] getPosition( ) {
		return position;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override @ControlP5.Layout public T setPosition( float[] thePos ) {
		set( position , x( thePos ) , y( thePos ) );
		set( positionBuffer , x( thePos ) , y( thePos ) );
		return me;
	}

	/**
	 * set the position of a controller. The position of a controller is relative.
	 * 
	 * @param theX float
	 * @param theY float
	 * @return Controller
	 */
	@Override public T setPosition( final float theX , final float theY ) {
		set( position , theX , theY );
		set( positionBuffer , theX , theY );
		return me;
	}

	public float[] getAbsolutePosition( ) {
		return absolutePosition;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override @ControlP5.Invisible public T setAbsolutePosition( float[] thePos ) {
		// TODO
		// doesnt work properly yet.
		// absolute position should not be changed from the
		// outside anyway.
		set( absolutePosition , x( thePos ) , y( thePos ) );
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	@ControlP5.Invisible public T updateAbsolutePosition( ) {
		return me;
	}

	/**
	 * continuousUpdateEvents is used for internal updates of a controller. this method is final and can't be
	 * overridden.
	 * 
	 * @exclude
	 */
	@ControlP5.Invisible public final void continuousUpdateEvents( ) {
		if ( isBehavior ) {
			if ( _myBehavior.isActive( ) && !isMousePressed ) {
				_myBehavior.update( );
			}
		}
	}

	/**
	 * updateEvents is used for internal updates of a controller. this method is final and can't be overwritten.
	 */
	@ControlP5.Invisible public final T updateEvents( ) {
		if ( isInside ) {
			boolean moved = ( ( _myControlWindow.mouseX - _myControlWindow.pmouseX ) != 0 || ( _myControlWindow.mouseY - _myControlWindow.pmouseY ) != 0 );
			if ( isMousePressed ) {
				if ( moved ) {
					if ( !isDragged ) {
						onStartDrag( );
						callListener( ACTION_START_DRAG );
					}
					onDrag( );
					isDragged = true;
					callListener( ACTION_DRAG );
				}
			} else {
				if ( moved && this.equals( _myControlWindow.getFirstFromMouseOverList( ) ) ) {
					onMove( );
					cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_MOVE ) );
					callListener( ACTION_MOVE );
				}
			}
		}

		if ( isVisible && ( isMousePressed == _myControlWindow.mouselock ) ) {
			if ( isMousePressed && cp5.isAltDown( ) && isMoveable ) {
				if ( !cp5.isMoveable ) {
					set( positionBuffer , x( positionBuffer ) + _myControlWindow.mouseX - _myControlWindow.pmouseX , y( positionBuffer ) + _myControlWindow.mouseY - _myControlWindow.pmouseY );
					if ( cp5.isShiftDown( ) ) {
						set( position , ( ( int ) ( x( positionBuffer ) ) / 10 ) * 10 , ( ( int ) ( y( positionBuffer ) ) / 10 ) * 10 );
					} else {
						set( position , positionBuffer );
					}
				}
			} else {
				if ( !isLock ) {
					if ( isInside ) {
						setMouseOver( true );
					}

					if ( inside( ) ) {
						if ( cp5.getMouseOverList( ).isEmpty( ) ) {
							if ( !isInside ) {
								onEnter( );
								setIsInside( true );
								callListener( ACTION_ENTER );
							}
							setIsInside( true );
						}
					} else {
						if ( isInside && !isMousePressed ) {
							onLeave( );
							setMouseOver( false );
							setIsInside( false );
							callListener( ACTION_LEAVE );
						}
						if ( !isInside && mouseover ) {

							setMouseOver( false );

							// here the mouseOver is set to false when the mouse
							// is released outside a controller. the mouseoverlist is not
							// updated when the mouse is still pressed but has left the
							// controller - updating the list here currently conflicts 
							// with callbacks called from inside setMouseOver()
							//
							// Note: the mouseoverlist is only updated for ControllerGroups 
							// when the mouse is pressed but is hovering other controllers while 
							// mouse is dragged.
						}
					}
				}
			}
		}
		return me;
	}

	public Pointer getPointer( ) {
		return new Pointer( ) {

			public int x( ) {
				return ( int ) ( _myControlWindow.mouseX - Controller.x( _myParent.getAbsolutePosition( ) ) - Controller.x( position ) );
			}

			public int y( ) {
				return ( int ) ( _myControlWindow.mouseY - Controller.y( _myParent.getAbsolutePosition( ) ) - Controller.y( position ) );
			}

			public int px( ) {
				return ( int ) ( _myControlWindow.pmouseX - Controller.x( _myParent.getAbsolutePosition( ) ) - Controller.x( position ) );
			}

			public int py( ) {
				return ( int ) ( _myControlWindow.pmouseY - Controller.y( _myParent.getAbsolutePosition( ) ) - Controller.y( position ) );
			}

			public int dx( ) {
				return x( ) - px( );
			}

			public int dy( ) {
				return y( ) - py( );
			}

			public long pt( ) {
				return _myControlWindow.pmousePressedTime;
			}

			public long dt( ) {
				return _myControlWindow.mousePressedTime - _myControlWindow.pmousePressedTime;
			}

			public long t( ) {
				return _myControlWindow.mousePressedTime;
			}

			public String toString( ) {
				return String.format( "{ x: %d y: %d px: %d py: %d dx: %d dy: %d}" , x( ) , y( ) , px( ) , py( ) , dx( ) , dy( ) );
			}
		};
	}

	@ControlP5.Invisible public final boolean setMousePressed( final boolean theStatus ) {
		if ( !isVisible && !isUserInteraction ) {
			return false;
		}
		if ( theStatus == true ) {
			if ( isInside ) {
				isMousePressed = true;
				if ( !cp5.isAltDown( ) ) {
					mousePressed( );
					onPress( );
					cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_PRESS ) );
					callListener( ACTION_PRESS );
					if ( getPointer( ).dt( ) < 500 ) {
						onDoublePress( );
						callListener( ACTION_DOUBLE_PRESS );
					}
				}
				return true;
			}
		} else {
			if ( isMousePressed == true && inside( ) ) {
				isMousePressed = false;
				if ( !cp5.isAltDown( ) ) {

					mouseReleased( );
					onRelease( );
					cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_RELEASE ) );
					callListener( ACTION_RELEASE );
					if ( isDragged ) {
						/* first release, then stopDrag */
						onEndDrag( );
						callListener( ACTION_END_DRAG );
					} else {
						onClick( );
						cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_CLICK ) );
						callListener( ACTION_CLICK );
					}

					isDragged = false;

				}
			}
			if ( !inside( ) ) {
				//System.out.println("CONTROLLER SET MOUSE PRESSED NOT INSIDE");
				setIsInside( false );
				if ( isMousePressed ) {
					isMousePressed = false;
					mouseReleasedOutside( );
					onReleaseOutside( );
					cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_RELEASE_OUTSIDE ) );
					callListener( ACTION_RELEASE_OUTSIDE );
				}
				if ( this instanceof Textfield ) {
					mouseReleasedOutside( );
					onReleaseOutside( );
					cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_RELEASE_OUTSIDE ) );
					callListener( ACTION_RELEASE_OUTSIDE );
				}
				if ( this instanceof ScrollableList) {
					mouseReleasedOutside( );
					onReleaseOutside( );
					cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_RELEASE_OUTSIDE ) );
					callListener( ACTION_RELEASE_OUTSIDE );
				}
			}
		}
		return false;
	}

	/**
	 * enables a controller to listen to changes made to the variable linked to the controller. Use true to enable and
	 * false to disable a controller from listening to changes.
	 * 
	 * @param theFlag
	 * @return Controller
	 */
	public T listen( boolean theValue ) {
		if ( theValue == listening ) {
			return me;
		}
		listening = theValue;
		if ( listening ) {
			cp5.listenTo( _myName , cp5.getObjectForController( this ) );
		} else {
			cp5.stopListeningTo( _myName , cp5.getObjectForController( this ) );
		}
		return me;
	}

	/**
	 * returns true or false for the current listening status. by default it is set to false
	 * 
	 * @see controlP5.Controller#listen(boolean)
	 * 
	 * @return boolean
	 */
	public boolean isListening( ) {
		return listening;
	}

	/**
	 * check if the mouse is within this particular controller.
	 * 
	 * @return boolean
	 */
	public boolean isMouseOver( ) {
		return mouseover;
	}

	public T setMouseOver( boolean theFlag ) {
		if ( mouseover == theFlag ) {
			return me;
		}
		mouseover = theFlag;
		if ( mouseover ) {
			_myControlWindow.setMouseOverController( this );
			cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_ENTER ) );
			cp5.getTooltip( ).activate( this );
		} else {
			cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_LEAVE ) );
			_myControlWindow.removeMouseOverFor( this );
			cp5.getTooltip( ).deactivate( );
			setIsInside( false ); /* added after issue 6 has been reported */
		}
		return me;
	}

	/**
	 * @see ControllerInterface.updateInternalEvents
	 */
	@ControlP5.Invisible public T updateInternalEvents( final PApplet theApplet ) {
		return me;
	}

	/**
	 * the default draw function for each controller extending superclass Controller. This draw function will take care
	 * of default matrix operations and will call the display function of the current ControllerView object active for
	 * this particular controller.
	 * 
	 * @exclude
	 * @see controlP5.ControllerView
	 * @param theApplet PApplet
	 */
	@ControlP5.Invisible @Override public void draw( final PGraphics theGraphics ) {

		theGraphics.pushMatrix( );
		theGraphics.translate( x( position ) , y( position ) );
		_myControllerView.display( theGraphics , me );
		// theGraphics.pushMatrix( );
		// _myDebugView.display( theGraphics , me );
		// theGraphics.popMatrix( );
		theGraphics.popMatrix( );

	}

	/**
	 * @param theElement ControllerInterface
	 * @return Controller
	 */
	@ControlP5.Invisible public T add( final ControllerInterface< ? > theElement ) {
		ControlP5.logger( ).warning( " add() not supported." );
		return me;
	}

	/**
	 * @param theElement ControllerInterface
	 * @return Controller
	 */
	@ControlP5.Invisible public T remove( final ControllerInterface< ? > theElement ) {
		ControlP5.logger( ).warning( "remove() not supported." );
		return me;
	}

	/**
	 * removes a controller from controlP5.
	 */
	public void remove( ) {
		_myControlWindow.removeMouseOverFor( this );
		if ( _myParent != null ) {
			_myParent.remove( this );
		}
		if ( cp5 != null ) {
			cp5.remove( this );
		}
	}

	@Override public T bringToFront( ) {
		return bringToFront( this );
	}

	@Override public T bringToFront( ControllerInterface< ? > theController ) {
		if ( _myParent instanceof Tab ) {
			moveTo( ( Tab ) _myParent );
		} else {
			_myParent.bringToFront( theController );
		}
		if ( theController != this ) {
			if ( getSubelements( ).contains( theController ) ) {
				if ( theController instanceof Controller< ? > ) {
					getSubelements( ).remove( theController );
					getSubelements( ).add( ( Controller< ? > ) theController );
				}
			}
		}
		return me;
	}

	/**
	 * returns the index name of the controller.
	 * 
	 * @return String
	 */
	public String getName( ) {
		return _myName;
	}

	/**
	 * moves the controller to another tab. The tab is defined by parameter theTabName. if controlP5 can't find a tab
	 * with given name, controlP5 will create this tab and add it to the main window.
	 * 
	 * @param theTabName String
	 * @return Controller
	 */
	public final T moveTo( final String theTabName ) {
		setTab( theTabName );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.moveTo( theTabName );
		}
		return me;
	}

	/**
	 * moves the controller to another tab.
	 * 
	 * @param theTab
	 * @return Controller
	 */
	public final T moveTo( final Tab theTab ) {
		setTab( theTab.getWindow( ) , theTab.getName( ) );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.moveTo( theTab );
		}
		return me;
	}

	/**
	 * moves the controller to the default tab inside the main window.
	 * 
	 * @param theApplet
	 * @return Controller
	 */
	public final T moveTo( final PApplet theApplet ) {
		setTab( "default" );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.moveTo( theApplet );
		}
		return me;
	}

	/**
	 * moves the controller to a tab inside the main window.
	 * 
	 * @param theApplet
	 * @param theTabName
	 */
	public final T moveTo( final PApplet theApplet , final String theTabName ) {
		setTab( theTabName );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.moveTo( theApplet , theTabName );
		}
		return me;
	}

	/**
	 * moves the controller to the default tab of a control window - other than the main window.
	 * 
	 * @param theControlWindow
	 */
	public final T moveTo( final ControlWindow theControlWindow ) {
		setTab( theControlWindow , "default" );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.moveTo( theControlWindow );
		}
		return me;
	}

	/**
	 * 
	 * @param theControlWindow
	 * @param theTabName
	 * @return Controller
	 */
	public final T moveTo( final ControlWindow theControlWindow , final String theTabName ) {
		setTab( theControlWindow , theTabName );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.moveTo( theControlWindow , theTabName );
		}
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	public final T moveTo( final ControllerGroup< ? > theGroup , final Tab theTab , ControlWindow theControlWindow ) {
		for ( Controller< ? > c : getSubelements( ) ) {
			c.moveTo( theGroup , theTab , theControlWindow );
		}

		if ( theGroup != null ) {
			setGroup( theGroup );
			return me;
		}

		if ( theControlWindow == null ) {
			theControlWindow = cp5.controlWindow;
		}

		setTab( theControlWindow , theTab.getName( ) );
		return me;
	}

	/**
	 * 
	 * @param theGroup
	 * @return Controller
	 */
	public final T moveTo( final ControlGroup< ? > theGroup ) {
		if ( theGroup != null ) {
			setGroup( theGroup );
		}
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	public final T moveTo( final ControllerGroup< ? > theGroup ) {
		if ( theGroup != null ) {
			setGroup( theGroup );
		}
		return me;
	}

	/**
	 * sets the tab of the controller.
	 * 
	 * @param theName String
	 * @return Controller
	 */
	public final T setTab( final String theName ) {
		setParent( cp5.getTab( theName ) );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.setTab( theName );
		}
		return me;
	}

	/**
	 * 
	 */
	public final T setTab( final ControlWindow theWindow , final String theName ) {
		setParent( cp5.getTab( theWindow , theName ) );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.setTab( theWindow , theName );
		}
		return me;
	}

	/**
	 * sets the group of the controller.
	 * 
	 * @param theName String
	 * @return Controller
	 */
	public final T setGroup( final String theName ) {
		setParent( cp5.getGroup( theName ) );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.setGroup( theName );
		}
		return me;
	}

	public final T setGroup( final ControllerGroup< ? > theGroup ) {
		setParent( theGroup );
		for ( Controller< ? > c : getSubelements( ) ) {
			c.setGroup( theGroup );
		}
		return me;
	}

	/**
	 * get the instance of the tab the controller belongs to.
	 * 
	 * @return Tab
	 */
	public Tab getTab( ) {
		if ( _myParent instanceof Tab ) {
			return ( Tab ) _myParent;
		}
		return _myParent.getTab( );
	}

	/**
	 * set the parent of a parent of a controller. this method is only meant for internal use. this method is final and
	 * can't be overwritten.
	 * 
	 * @param theParent ControllerInterface
	 * @return Controller
	 */
	@ControlP5.Invisible public final T setParent( final ControllerInterface< ? > theParent ) {
		if ( _myParent != null ) {
			_myParent.remove( this );
		}
		set( absolutePosition , x( position ) , y( position ) );
		if ( theParent != null ) {
			_myParent = theParent;
			_myParent.add( this );
			set( absolutePosition , x( absolutePosition ) + x( _myParent.getPosition( ) ) , y( absolutePosition ) + y( _myParent.getPosition( ) ) );
			_myControlWindow = _myParent.getWindow( );
		}
		setMouseOver( false );
		return me;
	}

	/**
	 * returns the parent of a controller.
	 * 
	 * @return ControllerInterface
	 */

	@ControlP5.Invisible @Override @ControlP5.Layout public ControllerInterface< ? > getParent( ) {
		return _myParent;
	}

	/**
	 * returns the control window of the controller
	 * 
	 * @return ControlWindow
	 */
	public ControlWindow getWindow( ) {
		return _myControlWindow;
	}

	/**
	 * checks if the mouse is within the area of a controller.
	 * 
	 * @return boolean
	 */
	protected boolean inside( ) {
		/* constrain the bounds of the controller to the dimensions of the cp5 area, required since PGraphics as render
		 * area has been introduced. */
		float x0 = PApplet.max( 0 , x( position ) + x( _myParent.getAbsolutePosition( ) ) );
		float x1 = PApplet.min( cp5.pgw , x( position ) + x( _myParent.getAbsolutePosition( ) ) + getWidth( ) );
		float y0 = PApplet.max( 0 , y( position ) + y( _myParent.getAbsolutePosition( ) ) );
		float y1 = PApplet.min( cp5.pgh , y( position ) + y( _myParent.getAbsolutePosition( ) ) + getHeight( ) );
		return ( _myControlWindow.mouseX > x0 && _myControlWindow.mouseX < x1 && _myControlWindow.mouseY > y0 && _myControlWindow.mouseY < y1 );
	}

	/**
	 * returns true or false and indicates if the mouse is inside the area of a controller.
	 * 
	 * @return boolean
	 */
	public boolean isInside( ) {
		return isInside;
	}

	/**
	 * checks if a controller is active.
	 * 
	 * @return boolean
	 */
	@ControlP5.Layout public boolean isActive( ) {
		return isActive;
	}

	/**
	 * returns true or false if the mouse has is pressed.
	 * 
	 * @return boolean
	 */
	public boolean isMousePressed( ) {
		return isMousePressed;
	}

	public T addListenerFor( int theAction , CallbackListener theListener ) {
		if ( _myControlListenerLUT == null ) {
			_myControlListenerLUT = new HashMap< Integer , List< CallbackListener >>( );
		}
		if ( _myControlListenerLUT.get( theAction ) == null ) {
			_myControlListenerLUT.put( theAction , new ArrayList( ) );
		}
		_myControlListenerLUT.get( theAction ).add( theListener );
		return me;
	}

	public T removeListenerFor( int theAction , CallbackListener theListener ) {
		if ( _myControlListenerLUT == null ) {
			return me;
		}
		if ( _myControlListenerLUT.get( theAction ) == null ) {
			return me;
		}
		_myControlListenerLUT.get( theAction ).remove( theListener );
		return me;
	}

	public T removeListenersFor( int theAction ) {
		if ( _myControlListenerLUT == null ) {
			return me;
		}
		if ( _myControlListenerLUT.get( theAction ) == null ) {
			return me;
		}
		_myControlListenerLUT.get( theAction ).clear( );
		return me;
	}

	protected void callListener( int theAction ) {
		if ( _myControlListenerLUT != null ) {
			List< CallbackListener > l = _myControlListenerLUT.get( theAction );
			if ( l != null ) {
				CallbackEvent event = new CallbackEvent( this , theAction );
				for ( CallbackListener cl : l ) {
					cl.controlEvent( event );
				}
			}
		}
	}

	public T onDraw( ControllerView< T > theView ) {
		return setView( theView );
	}

	public T onEnter( CallbackListener theListener ) {
		return addListenerFor( ACTION_ENTER , theListener );
	}

	public T onLeave( CallbackListener theListener ) {
		return addListenerFor( ACTION_LEAVE , theListener );
	}

	public T onDrag( CallbackListener theListener ) {
		return addListenerFor( ACTION_DRAG , theListener );
	}

	public T onStartDrag( CallbackListener theListener ) {
		return addListenerFor( ACTION_START_DRAG , theListener );
	}

	public T onEndDrag( CallbackListener theListener ) {
		return addListenerFor( ACTION_END_DRAG , theListener );
	}

	public T onWheel( CallbackListener theListener ) {
		return addListenerFor( ACTION_WHEEL , theListener );
	}

	public T onMove( CallbackListener theListener ) {
		return addListenerFor( ACTION_MOVE , theListener );
	}

	public T onClick( CallbackListener theListener ) {
		return addListenerFor( ACTION_CLICK , theListener );
	}

	public T onPress( CallbackListener theListener ) {
		return addListenerFor( ACTION_PRESS , theListener );
	}

	public T onDoublePress( CallbackListener theListener ) {
		return addListenerFor( ACTION_DOUBLE_PRESS , theListener );
	}

	public T onRelease( CallbackListener theListener ) {
		return addListenerFor( ACTION_RELEASE , theListener );
	}

	public T onReleaseOutside( CallbackListener theListener ) {
		return addListenerFor( ACTION_RELEASE_OUTSIDE , theListener );
	}

	public T onChange( CallbackListener theListener ) {
		return addListenerFor( ACTION_BROADCAST , theListener );
	}

	protected void onEnter( ) {
	}

	protected void onLeave( ) {
	}

	protected void onDrag( ) {
	}

	protected void onStartDrag( ) {
	}

	protected void onEndDrag( ) {
	}

	protected void onWheel( ) {
	}

	protected void onMove( ) {
	}

	protected void onClick( ) {
	}

	protected void onPress( ) {
	}

	protected void onDoublePress( ) {
	}

	protected void onRelease( ) {
	}

	protected void onScroll( int theAmount ) {
	}

	protected void onReleaseOutside( ) {
	}

	protected void mousePressed( ) {
	}

	protected void mouseReleased( ) {
	}

	protected void mouseReleasedOutside( ) {
	}

	protected void setIsInside( boolean theFlag ) {
		isInside = theFlag;
	}

	protected boolean getIsInside( ) {
		return isInside;
	}

	/**
	 * 
	 * @param KeyEvent theEvent
	 */
	@ControlP5.Invisible public void keyEvent( final KeyEvent theEvent ) {
	}

	/**
	 * set the id of a controller.
	 * 
	 * @param int theId
	 * @return Controller
	 */
	@ControlP5.Layout public T setId( final int theId ) {
		_myId = theId;
		return me;
	}

	/**
	 * returns the id of a controller, by default the id is -1. Any int can be given to a controller as its ID,
	 * controlP5 does not recognize duplicates, this has to be managed on the user site.
	 * 
	 * @return int
	 */
	@ControlP5.Layout public int getId( ) {
		return _myId;
	}

	protected ControllerPlug getControllerPlug( int theIndex ) {
		return _myControllerPlugList.get( theIndex );
	}

	/**
	 * 
	 * @return List<ControllerPlug>
	 */
	public List< ControllerPlug > getControllerPlugList( ) {
		return _myControllerPlugList;
	}

	protected boolean checkControllerPlug( ControllerPlug thePlug ) {
		for ( ControllerPlug cp : _myControllerPlugList ) {
			if ( cp.getObject( ).equals( thePlug.getObject( ) ) && cp.getName( ).equals( thePlug.getName( ) ) ) {
				return true;
			}
		}
		return false;
	}

	protected void addControllerPlug( final ControllerPlug thePlug ) {
		if ( checkControllerPlug( thePlug ) )
			return;
		_myControllerPlugList.add( thePlug );
	}

	protected void removeControllerPlug( final ControllerPlug thePlug ) {
		_myControllerPlugList.remove( thePlug );
	}

	/**
	 * plugs the controller to a list of objects
	 * 
	 * @param theObject
	 * @return Controller
	 */
	public T plugTo( final Object[] theObjects ) {
		for ( Object o : theObjects ) {
			plugTo( o , _myName );
		}
		return me;
	}

	/**
	 * 
	 * @param theObjects
	 * @param theName
	 * @return Controller
	 */
	public T plugTo( final Object[] theObjects , String theName ) {
		for ( Object o : theObjects ) {
			plugTo( o , theName );
		}
		return me;
	}

	/**
	 * @param theObject
	 * @return Controller
	 */
	public T plugTo( Object theObject ) {
		return plugTo( theObject , _myName );
	}

	public T plugTo( Object theObject , String theName ) {
		if ( ( theObject instanceof PApplet ) ) {
			unplugFrom( theObject );
		}
		cp5.getControlBroadcaster( ).plug( theObject , this , theName );
		cp5.getControlBroadcaster( ).plug( theObject , this , cp5.getControlBroadcaster( ).getEventMethod( ) );
		return me;
	}

	/**
	 * unplugs the controller from a list of objects
	 * 
	 * @param theObjects
	 * @return
	 */
	public T unplugFrom( final Object[] theObjects ) {
		for ( Object o : theObjects ) {
			unplugFrom( o );
		}
		return me;
	}

	/**
	 * unplugs the Controller for a single object
	 * 
	 * @param theObject
	 * @return Controller
	 */
	public T unplugFrom( final Object theObject ) {
		for ( Iterator< ControllerPlug > i = _myControllerPlugList.iterator( ) ; i.hasNext( ) ; ) {
			if ( i.next( ).getObject( ).equals( theObject ) ) {
				i.remove( );
			}
		}
		return me;
	}

	/**
	 * @param theValue float
	 */
	@ControlP5.Layout public T setValue( float theValue ) {
		_myValue = theValue;
		broadcast( FLOAT );
		return me;
	}

	public T setValueSelf( float theValue ) {
		boolean broadcast = isBroadcast( );
		setBroadcast( false );
		_myValue = theValue;
		broadcast( FLOAT );
		setBroadcast( broadcast );
		return me;
	}

	/**
	 * @see Controller#getStringValue()
	 * @see Controller#getArrayValue()
	 * @return float
	 */
	@ControlP5.Layout public float getValue( ) {
		return _myValue;
	}

	/**
	 * @param theValue
	 * @return Controller
	 */
	@ControlP5.Layout public T setStringValue( String theValue ) {
		_myStringValue = theValue;
		return me;
	}

	/**
	 * @see Controller#getValue()
	 * @see Controller#getArrayValue()
	 * 
	 * @return String
	 */
	@ControlP5.Layout public String getStringValue( ) {
		return _myStringValue;
	}

	/**
	 * returns the current float array value of a controller.
	 * 
	 * @see Controller#getValue()
	 * @see Controller#getStringValue()
	 * 
	 * @return float[]
	 */
	@ControlP5.Layout public float[] getArrayValue( ) {
		return _myArrayValue;
	}

	/**
	 * @param theIndex
	 * @return float
	 */
	public float getArrayValue( int theIndex ) {
		if ( theIndex >= 0 && theIndex < _myArrayValue.length ) {
			return _myArrayValue[ theIndex ];
		} else {
			return Float.NaN;
		}
	}

	/**
	 * 
	 * @param theArray
	 * @return Controller
	 */
	@ControlP5.Layout public T setArrayValue( float[] theArray ) {
		_myArrayValue = theArray;
		return me;
	}

	/**
	 * 
	 * @param theIndex
	 * @param theValue
	 * @return Controller
	 */
	public T setArrayValue( int theIndex , float theValue ) {
		if ( theIndex >= 0 && theIndex < _myArrayValue.length ) {
			_myArrayValue[ theIndex ] = theValue;
		}
		return me;
	}

	/**
	 * sets the value of the controller without sending the broadcast event. this function is final.
	 * 
	 * @param theValue float
	 * @return Controller
	 */
	public final T changeValue( float theValue ) {
		boolean br = this.isBroadcast;
		this.isBroadcast = false;
		setValue( theValue );
		this.isBroadcast = br;
		return me;
	}

	/**
	 * Set a description of this controller (e.g. "This button starts playing music.").
	 * This can be used to work around the broken tooltip class.
	 * 
	 * @param theValue String
	 * @return Controller
	 */
	public T setDescription( String theValue ) {
		_myDescription = theValue;
		return me;
	}

	/**
	 * Get a description of this controller (e.g. "This button starts playing music.").
	 * This can be used to work around the broken tooltip class.
	 * Null by default
	 * 
	 * @see Controller#setDescription()
	 * @return String
	 */
	public String getDescription() {
		return _myDescription;
	}

	/**
	 * updates the value of the controller without having to set the value explicitly. update does not visually update
	 * the controller. the updating status can be set with setUpdate(true/false) and checked with isUpdate().
	 * 
	 * @see Controller#setUpdate(boolean)
	 * @see Controller#isUpdate()
	 * @return Controller
	 */
	public T update( ) {
		return me;
	}

	/**
	 * disables the update function for a controller.
	 * 
	 * @see Controller#update()
	 * @see Controller#isUpdate()
	 * 
	 * @param theFlag boolean
	 * @return Controller
	 */
	@Override public T setUpdate( final boolean theFlag ) {
		isUpdate = theFlag;
		return me;
	}

	/**
	 * enables the update function for a controller.
	 * 
	 * @see Controller#update()
	 * @see Controller#setUpdate(boolean)
	 * 
	 * @return boolean
	 */
	public boolean isUpdate( ) {
		return isUpdate;
	}

	@ControlP5.Invisible public int getPickingColor( ) {
		return _myPickingColor;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override @ControlP5.Layout public CColor getColor( ) {
		return color;
	}

	/**
	 * sets the content of the caption label of a controller.
	 * 
	 * @param theLabel
	 * @return Controller
	 */
	@ControlP5.Layout public T setCaptionLabel( final String theLabel ) {
		_myCaptionLabel.set( theLabel );
		return me;
	}

	/**
	 * set or change the value of the value label of a controller. (this is cheating, but maybe useful for some cases.)
	 * 
	 * @param theLabel
	 * @return Controller
	 */
	@ControlP5.Layout public T setValueLabel( final String theLabel ) {
		_myValueLabel.set( theLabel );
		return me;
	}

	/**
	 * returns the controller's caption label text.
	 * 
	 * @return String
	 */
	@ControlP5.Invisible public String getLabel( ) {
		return _myCaptionLabel.getText( );
	}

	/**
	 * @see controlP5.ControlListener
	 * @param theListener ControlListener
	 * @return Controller
	 */
	public T addListener( final ControlListener theListener ) {
		_myControlListener.add( theListener );
		return me;
	}

	/**
	 * @see controlP5.ControlListener
	 * @param theListener ControlListener
	 * @return Controller
	 */
	public T removeListener( final ControlListener theListener ) {
		_myControlListener.remove( theListener );
		return me;
	}

	/**
	 * @return int
	 */
	public int listenerSize( ) {
		return _myControlListener.size( );
	}

	/**
	 * @see CallbackListener
	 * @param theListener
	 * @return Controller
	 */
	public T addCallback( CallbackListener theListener ) {
		cp5.addCallback( theListener , this );
		return me;
	}

	/**
	 * @see CallbackListener
	 * @param theListener
	 * @return Controller
	 */
	public T removeCallback( CallbackListener theListener ) {
		cp5.removeCallback( theListener );
		return me;
	}

	/**
	 * @return Controller
	 */
	public T removeCallback( ) {
		cp5.removeCallback( this );
		return me;
	}

	protected void broadcast( ) {
		broadcast( _myBroadcastType );
	}

	protected void broadcast( int theType ) {
		theType = _myBroadcastType;
		final ControlEvent myEvent = new ControlEvent( this );
		for ( ControlListener cl : _myControlListener ) {
			cl.controlEvent( myEvent );
		}
		if ( isBroadcast && isInit ) {
			cp5.getControlBroadcaster( ).broadcast( myEvent , theType );
			cp5.getControlBroadcaster( ).invokeAction( new CallbackEvent( this , ACTION_BROADCAST ) );
			callListener( ACTION_BROADCAST );
		}
		isInit = true;
	}

	/**
	 * @return boolean
	 */
	@ControlP5.Layout @Override public boolean isVisible( ) {
		if ( getParent( ) != null ) {
			if ( getParent( ).isVisible( ) == false ) {
				return false;
			}
		}
		return isVisible;
	}

	/**
	 * @param theFlag boolean
	 * @return Controller
	 */
	@ControlP5.Layout public T setVisible( final boolean theFlag ) {
		isVisible = theFlag;
		if ( theFlag == false ) {
			isActive = false;
		}
		return me;
	}

	/**
	 * @return Controller
	 */
	@Override public T hide( ) {
		setMouseOver( false );
		isVisible = false;
		isActive = false;
		return me;
	}

	/**
	 * @return Controller
	 */
	@Override public T show( ) {
		isVisible = true;
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override public T setColor( CColor theColor ) {
		color.set( theColor );
		setColorCaptionLabel( theColor.getCaptionLabel( ) );
		setColorValueLabel( theColor.getValueLabel( ) );
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override @ControlP5.Layout public T setColorActive( final int theColor ) {
		color.setActive( theColor );
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override @ControlP5.Layout public T setColorForeground( final int theColor ) {
		color.setForeground( theColor );
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override @ControlP5.Layout public T setColorBackground( final int theColor ) {
		color.setBackground( theColor );
		return me;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @param theColor
	 * @return Controller
	 */
	@ControlP5.Layout public T setColorCaptionLabel( final int theColor ) {
		color.setCaptionLabel( theColor );
		_myCaptionLabel.setColor( color.getCaptionLabel( ) );
		return me;
	}

	/**
	 * @param theColor
	 * @return Controller
	 */
	@ControlP5.Layout public T setColorValueLabel( final int theColor ) {
		color.setValueLabel( theColor );
		if ( _myValueLabel != null ) {
			_myValueLabel.setColor( color.getValueLabel( ) );
		}
		return me;
	}

	/**
	 * by default controllers use simple shapes, to replace these shapes with images, use setImages(). This can be handy
	 * for buttons, toggles, bangs, for more complex controllers such as sliders, range, dropdownlist this is not
	 * advisable.
	 * 
	 * @param theImageDefault
	 * @param theImageOver
	 * @param theImageActive
	 * @return Controller
	 */
	public T setImages( PImage theImageDefault , PImage theImageOver , PImage theImageActive ) {
		setImage( theImageDefault , DEFAULT );
		setImage( theImageOver , OVER );
		setImage( theImageActive , ACTIVE );
		setImage( theImageActive , HIGHLIGHT );
		return me;
	}

	public T setImages( PImage theImageDefault , PImage theImageOver , PImage theImageActive , PImage theImageHighlight ) {
		setImage( theImageDefault , DEFAULT );
		setImage( theImageOver , OVER );
		setImage( theImageActive , ACTIVE );
		setImage( theImageHighlight , HIGHLIGHT );
		return me;
	}

	public T setImages( PImage ... imgs ) {
		if ( imgs.length < 3 || imgs.length > 4 ) {
			return me;
		}
		setImage( imgs[ 0 ] , DEFAULT );
		setImage( imgs[ 1 ] , OVER );
		setImage( imgs[ 2 ] , ACTIVE );
		setImage( imgs.length == 3 ? imgs[ 2 ] : imgs[ 3 ] , HIGHLIGHT );
		return me;
	}

	public T setImage( PImage theImage ) {
		return setImage( theImage , DEFAULT );
	}

	/**
	 * @param theImage
	 * @param theState use Controller.DEFAULT (background) Controller.OVER (foreground) Controller.ACTIVE (active)
	 */
	public T setImage( PImage theImage , int theState ) {
		if ( theImage != null ) {
			images[ theState ] = theImage;
			availableImages[ theState ] = true;
			updateDisplayMode( IMAGE );
		}
		return me;
	}

	public T updateSize( ) {
		if ( images[ DEFAULT ] != null ) {
			setSize( images[ DEFAULT ] );
		}
		return me;
	}

	/**
	 * auto-updates the size of a controller according to the dimensions of the PImage.
	 * 
	 * @param theImage
	 * @return Controller
	 */
	public T setSize( PImage theImage ) {
		if ( theImage != null ) {
			setSize( theImage.width , theImage.height );
		}
		return me;
	}

	/**
	 * @param theWidth
	 * @param theHeight
	 * @return Controller
	 */
	public T setSize( int theWidth , int theHeight ) {
		setWidth( theWidth );
		setHeight( theHeight );
		return me;
	}

	protected T updateDisplayMode( int theMode ) {
		if ( theMode != DEFAULT ) {
			ControlP5.logger( ).warning( "Image-based or custom displays are not yet implemented for this type of controller. (" + this.getClass( ).getName( ) + ")" );
		}
		return me;
	}

	/**
	 * use setDisplay to customize your controller look. A new controller-display class required to implement interface
	 * ControllerView. By default the display mode will be set to CUSTOM when setting a new display.
	 * 
	 * @see controlP5.ControllerView
	 * @param theView
	 * @return Controller
	 */
	public T setView( ControllerView< T > theView ) {
		setView( theView , CUSTOM );
		return me;
	}

	public void setView( ControllerView< T > theView , int theMode ) {
		_myDisplayMode = theMode;
		_myControllerView = theView;
	}

	public ControllerView< T > getView( ) {
		return _myControllerView;
	}

	/**
	 * @see controlP5.Label
	 * @return Label
	 */
	@ControlP5.Layout public Label getCaptionLabel( ) {
		return _myCaptionLabel;
	}

	/**
	 * @return Label
	 */
	@ControlP5.Layout public Label getValueLabel( ) {
		return _myValueLabel;
	}

	/**
	 * returns the maximum value of the controller.
	 * 
	 * @return float
	 */
	@ControlP5.Layout public float getMax( ) {
		return _myMax;
	}

	/**
	 * returns the minimum value of the controller.
	 * 
	 * @return float
	 */
	@ControlP5.Layout public float getMin( ) {
		return _myMin;
	}

	/**
	 * sets the minimum value of the Controller.
	 * 
	 * @param theValue float
	 * @return Controller
	 */
	@ControlP5.Layout public T setMin( float theValue ) {
		_myMin = theValue;
		changeValue( getValue( ) );
		return me;
	}

	/**
	 * sets the maximum value of the Controller.
	 * 
	 * @param theValue float
	 * @return Controller
	 */
	@ControlP5.Layout public T setMax( float theValue ) {
		_myMax = theValue;
		changeValue( getValue( ) );
		return me;
	}

	/**
	 * @param theWidth
	 * @return Controller
	 */
	@ControlP5.Layout public T setWidth( int theWidth ) {
		width = theWidth;
		return me;
	}

	/**
	 * @param theHeight
	 * @return Controller
	 */
	@ControlP5.Layout public T setHeight( int theHeight ) {
		height = theHeight;
		return me;
	}

	/**
	 * @return int
	 */
	@ControlP5.Layout public int getWidth( ) {
		return width;
	}

	/**
	 * @return int
	 */
	@ControlP5.Layout public int getHeight( ) {
		return height;
	}

	/**
	 * sets the decimal precision of a controller's float value displayed. the precision does not apply to the returned
	 * float value.
	 * 
	 * @param theValue
	 * @return Controller
	 */
	@ControlP5.Layout public T setDecimalPrecision( int theValue ) {
		_myDecimalPoints = theValue;
		// TODO transfer color values of existing label
		int[] t = _myValueLabel.getAlign( );
		_myValueLabel = new Label( cp5 , "" + ( ( ( adjustValue( _myMax ) ).length( ) > ( adjustValue( _myMin ) ).length( ) ) ? adjustValue( _myMax ) : adjustValue( _myMin ) ) );
		_myValueLabel.align( t );
		_myValueLabel.setColor( color.getValueLabel( ) );
		_myValueLabel.set( "" + adjustValue( getValue( ) ) );
		return me;
	}

	/**
	 * @return int
	 */
	@ControlP5.Layout public int getDecimalPrecision( ) {
		return _myDecimalPoints;
	}

	/**
	 * @param theValue float
	 * @return String
	 */
	protected String adjustValue( final float theValue ) {
		return adjustValue( theValue , _myDecimalPoints );
	}

	/**
	 * @param theValue
	 * @param theFloatPrecision
	 * @return String
	 */
	protected String adjustValue( final float theValue , final int theFloatPrecision ) {

		int myFloatNumberLength = theFloatPrecision + 1;
		if ( getControllerPlugList( ).size( ) > 0 ) {
			for ( ControllerPlug cp : getControllerPlugList( ) ) {
				if ( cp.getClassType( ) == int.class ) {
					myFloatNumberLength = 0;
				}
			}
		}
		String myLabelValue = "" + theValue;
		int myIndex = myLabelValue.indexOf( '.' );
		if ( myIndex > 0 ) {

			if ( theFloatPrecision == 0 ) {
				myIndex--;
			}
			myLabelValue = myLabelValue.substring( 0 , ( int ) Math.min( myLabelValue.length( ) , myIndex + myFloatNumberLength ) );

			final int n = ( myLabelValue.length( ) - myIndex );
			if ( n < myFloatNumberLength ) {
				for ( int i = 0 ; i < myFloatNumberLength - n ; i++ ) {
					myLabelValue += "0";
				}
			}
		} else {
			myLabelValue += ".";
			for ( int i = 0 ; i < myFloatNumberLength ; i++ ) {
				myLabelValue += "0";
			}
		}

		return myLabelValue;
	}

	public T align( int theCaptionX , int theCaptionY , int theValueX , int theValueY ) {
		getCaptionLabel( ).align( theCaptionX , theCaptionY );
		getCaptionLabel( ).align( theValueX , theValueY );
		return me;
	}

	/**
	 * @return ControlWindow
	 */
	public ControlWindow getControlWindow( ) {
		return _myControlWindow;
	}

	/**
	 * disables the controller to be moved, or changed or controlled by the user.
	 * 
	 * @return Controller
	 */
	public T lock( ) {
		isLock = true;
		return me;
	}

	/**
	 * enables the controller to be moved, changed and controlled by the user.
	 * 
	 * @return Controller
	 */
	public T unlock( ) {
		isLock = false;
		return me;
	}

	/**
	 * @return boolean
	 */
	@ControlP5.Layout public boolean isLock( ) {
		return isLock;
	}

	/**
	 * sets the lock status of the controller
	 * 
	 * @param theValue
	 * @return Controller
	 */
	@ControlP5.Layout public T setLock( boolean theValue ) {
		isLock = theValue;
		return me;
	}

	/**
	 * @exclude TODO
	 */
	@ControlP5.Invisible public T setUserInteraction( boolean theValue ) {
		isUserInteraction = theValue;
		return me;
	}

	/**
	 * @exclude
	 * @return boolean
	 */
	@ControlP5.Invisible public boolean isUserInteraction( ) {
		return isUserInteraction;
	}

	/**
	 * adds a tooltip to a controller, by default the tooltip is disabled. A Tooltip is made visible when entering a
	 * controller with the mouse, when the mouse is moved inside the controller, the tooltip will hide.
	 * 
	 * @param theText
	 * @return Controller
	 */
	public T registerTooltip( String theText ) {
		cp5.getTooltip( ).register( this , theText );
		return me;
	}

	/**
	 * @see controlP5.Controller#registerTooltip(String)
	 * @return Controller
	 */
	public T unregisterTooltip( ) {
		cp5.getTooltip( ).unregister( this );
		return me;
	}

	protected T setTooltipEnabled( boolean theValue ) {
		tooltipEnabled = theValue;
		return me;
	}

	protected boolean isTooltipEnabled( ) {
		return tooltipEnabled;
	}

	/**
	 * @return Controller
	 */
	public T linebreak( ) {
		cp5.linebreak( this , true , autoWidth , autoHeight , autoSpacing );
		return me;
	}

	class DebugView implements ControllerView< T > {

		public void display( PGraphics theGraphics , T theController ) {
			if ( inside( ) ) {
				theGraphics.fill( 255 , 0 , 0 , 50 );
				theGraphics.stroke( 255 , 0 , 0 );
			} else {
				theGraphics.fill( 255 , 50 );
				theGraphics.stroke( 255 );
			}

			theGraphics.pushMatrix( );
			theGraphics.rect( 0 , 0 , width , height );
			theGraphics.popMatrix( );
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override public ControllerProperty getProperty( String thePropertyName ) {
		return cp5.getProperties( ).getProperty( this , thePropertyName );
	}

	/**
	 * {@inheritDoc}
	 */
	@Override public ControllerProperty getProperty( String theSetter , String theGetter ) {
		return cp5.getProperties( ).getProperty( this , theSetter , theGetter );
	}

	/**
	 * {@inheritDoc}
	 */
	@Override public T registerProperty( String thePropertyName ) {
		cp5.getProperties( ).register( this , thePropertyName );
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override public T registerProperty( String theSetter , String theGetter ) {
		cp5.getProperties( ).register( this , theSetter , theGetter );
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override public T removeProperty( String thePropertyName ) {
		cp5.getProperties( ).remove( this , thePropertyName );
		return me;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override public T removeProperty( String theSetter , String theGetter ) {
		cp5.getProperties( ).remove( this , theSetter , theGetter );
		return me;
	}

	/**
	 * @exclude {@inheritDoc}
	 */
	@Override public String toString( ) {
		return getName( ) + " [" + getClass( ).getSimpleName( ) + "]";
	}

	/**
	 * @exclude
	 * @return String
	 */
	public String getInfo( ) {
		return "[ type:\tController" + "\nname:\t" + _myName + "\n" + "label:\t" + _myCaptionLabel.getText( ) + "\n" + "id:\t" + _myId + "\n" + "value:\t" + getValue( ) + "\n" + "arrayvalue:\t" + CP.arrayToString( _myArrayValue ) + "\n" + "position:\t"
		    + position + "\n" + "absolute:\t" + absolutePosition + "\n" + "width:\t" + getWidth( ) + "\n" + "height:\t" + getHeight( ) + "\n" + "color:\t" + getColor( ) + "\n" + "visible:\t" + isVisible + "\n" + "moveable:\t" + isMoveable + " ]";
	}

	/**
	 * convenience method to fill a float array in favor of theArray[0] = 1.2; etc.
	 * takes a float array and fills it (starting from index 0) with arguments starting from index 1.  
	 */
	static public float[] set( float[] theArray , float ... theValues ) {
		if ( theValues.length > theArray.length ) {
			System.arraycopy( theValues , 0 , theArray , 0 , theArray.length );
		} else {
			System.arraycopy( theValues , 0 , theArray , 0 , theValues.length );
		}
		return theArray;
	}

	/**
	 * returns the first element of the float array.
	 */
	static public float x( float[] theArray ) {
		if ( theArray.length > 0 ) {
			return theArray[ 0 ];
		}
		return 0;
	}

	/**
	 * returns the second element of the float array.
	 */
	static public float y( float[] theArray ) {
		if ( theArray.length > 1 ) {
			return theArray[ 1 ];
		}
		return 0;
	}

	@Override public T setLabel( String theString ) {
		getCaptionLabel( ).setText( theString );
		return me;
	}

	@Override public T setColorLabel( int theColor ) {
		getCaptionLabel( ).setColor( theColor );
		return me;
	}

	@Override public T setColorValue( int theColor ) {
		getValueLabel( ).setColor( theColor );
		return me;
	}

	@Override public T setFont( PFont thePFont ) {
		getValueLabel( ).setFont( thePFont );
		getCaptionLabel( ).setFont( thePFont );
		return me;
	}

	@Override public T setFont( ControlFont theFont ) {
		getValueLabel( ).setFont( theFont );
		getCaptionLabel( ).setFont( theFont );
		return me;
	}

}
