controlp5/src/controlP5/Textfield.java

476 lines
14 KiB
Java
Executable File

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.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PGraphics;
import processing.event.Event;
import processing.event.KeyEvent;
/**
* A singleline input textfield, use arrow keys to go back and forth, use backspace to delete
* characters. Using the up and down arrows lets you cycle through the history of the textfield.
*
* This is the best you can get. Font handling, font switching, measuring, left align, right align,
* etc. was giving me a big headache. not perfect, i think this is a good compromise.
*
* @example controllers/ControlP5textfield
* @nosuperclasses Controller Controller
*/
public class Textfield extends Controller< Textfield > {
/* TODO textspacing does not work properly for bitfonts sometimes first row of pixels in a
* bitfont texture gets cut off */
protected boolean isTexfieldActive;
protected boolean isKeepFocus;
protected StringBuffer _myTextBuffer = new StringBuffer( );
protected int _myTextBufferIndex = 0;
protected int _myTextBufferOverflow = 0;
protected int _myTextBufferIndexPosition = 0;
public static int cursorWidth = 1;
protected Map< Integer , TextfieldCommand > keyMapping;
protected InputFilter _myInputFilter = InputFilter.BITFONT;
protected List< Integer > ignorelist;
protected LinkedList< String > _myHistory;
protected int _myHistoryIndex;
protected int len = 0;
protected int offset = 2;
protected int margin = 2;
protected boolean isPasswordMode;
protected boolean autoclear = true;
protected int _myColorCursor = 0x88ffffff;
private PGraphics buffer;
public enum InputFilter {
INTEGER(Arrays.asList( '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' )), FLOAT(Arrays.asList( '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , '.' )), BITFONT(Arrays.asList( '\n' , '\r' , ' ' , '!' , '"' , '#' , '$' , '%' ,
'&' , '\'' , '(' , ')' , '*' , '+' , ',' , '-' , '.' , '/' , '0' , '1' , '2' , '3' , '4' , '5' , '6' , '7' , '8' , '9' , ':' , ';' , '<' , '=' , '>' , '?' , '@' , 'A' , 'B' , 'C' , 'D' , 'E' , 'F' , 'G' , 'H' , 'I' , 'J' , 'K' , 'L' , 'M' ,
'N' , 'O' , 'P' , 'Q' , 'R' , 'S' , 'T' , 'U' , 'V' , 'W' , 'X' , 'Y' , 'Z' , '[' , '\\' , ']' , '^' , '_' , '`' , 'a' , 'b' , 'c' , 'd' , 'e' , 'f' , 'g' , 'h' , 'i' , 'j' , 'k' , 'l' , 'm' , 'n' , 'o' , 'p' , 'q' , 'r' , 's' , 't' , 'u' ,
'v' , 'w' , 'x' , 'y' , 'z' , '{' , '|' , '}' , '~' )), DEFAULT(new LinkedList< Character >( ));
final List< Character > allowed;
InputFilter( List< Character > theList ) {
allowed = theList;
}
protected boolean apply( char theCharater ) {
if ( allowed.isEmpty( ) ) {
return true;
} else {
return allowed.contains( theCharater );
}
}
}
/**
* Convenience constructor to extend Textfield.
*
* @example use/ControlP5extendController
* @param theControlP5
* @param theName
*/
public Textfield( ControlP5 theControlP5 , String theName ) {
this( theControlP5 , theControlP5.getDefaultTab( ) , theName , "" , 0 , 0 , 199 , 19 );
theControlP5.register( theControlP5.papplet , theName , this );
}
public Textfield( ControlP5 theControlP5 , ControllerGroup< ? > theParent , String theName , String theDefaultValue , int theX , int theY , int theWidth , int theHeight ) {
super( theControlP5 , theParent , theName , theX , theY , theWidth , theHeight );
_myCaptionLabel = new Label( cp5 , theName , 0 , 0 , color.getCaptionLabel( ) );
_myValueLabel.setFont( cp5.controlFont == cp5.defaultFont ? cp5.defaultFontForText : cp5.controlFont );
_myCaptionLabel.align( ControlP5.LEFT , ControlP5.BOTTOM_OUTSIDE );
_myCaptionLabel.setPaddingX( 0 );
_myBroadcastType = STRING;
_myValueLabel.setFixedSize( true );
_myValueLabel.set( "" );
_myValueLabel.setWidth( getWidth( ) - margin * 2 );
_myValueLabel.setPadding( 0 , 0 );
_myValueLabel.align( LEFT , CENTER );
_myValueLabel.setColor( color.getValueLabel( ) );
_myValueLabel.toUpperCase( false );
_myValueLabel.setLabeltype( _myValueLabel.new SinglelineTextfield( ) );
_myHistory = new LinkedList< String >( );
_myHistory.addFirst( "" );
setSize( getWidth( ) , getHeight( ) );
keyMapping = new HashMap< Integer , TextfieldCommand >( );
keyMapping.put( ENTER , new Enter( ) );
keyMapping.put( DEFAULT , new InsertCharacter( ) );
keyMapping.put( DELETE , new DeleteCharacter( ) );
keyMapping.put( BACKSPACE , new DeleteCharacter( ) );
keyMapping.put( LEFT , new MoveLeft( ) );
keyMapping.put( RIGHT , new MoveRight( ) );
keyMapping.put( UP , new MoveUp( ) );
keyMapping.put( DOWN , new MoveDown( ) );
ignorelist = new LinkedList< Integer >( );
ignorelist.add( SHIFT );
ignorelist.add( ALT );
ignorelist.add( CONTROL );
ignorelist.add( TAB );
ignorelist.add( COMMANDKEY );
setInputFilter( DEFAULT );
}
@Override public Textfield setWidth( int theWidth ) {
_myValueLabel.setWidth( theWidth );
return super.setWidth( theWidth );
}
@Override public Textfield setHeight( int theHeight ) {
_myValueLabel.setHeight( theHeight );
return super.setHeight( theHeight );
}
public Textfield setFocus( boolean theValue ) {
isTexfieldActive = isActive = theValue;
return this;
}
/**
* check if the textfield is active and in focus.
*
* @return boolean
*/
public boolean isFocus( ) {
return isTexfieldActive;
}
public Textfield keepFocus( boolean theValue ) {
isKeepFocus = theValue;
if ( isKeepFocus ) {
setFocus( true );
}
return this;
}
// public Textfield setFont( PFont thePFont ) {
// getValueLabel( ).setFont( thePFont );
// return this;
// }
//
// public Textfield setFont( ControlFont theFont ) {
// getValueLabel( ).setFont( theFont );
// return this;
// }
public Textfield setFont( int theFont ) {
getValueLabel( ).setFont( theFont );
return this;
}
public Textfield setPasswordMode( boolean theFlag ) {
isPasswordMode = theFlag;
return this;
}
public Textfield setInputFilter( int theInputType ) {
switch ( theInputType ) {
case ( INTEGER ):
_myInputFilter = InputFilter.INTEGER;
break;
case ( FLOAT ):
_myInputFilter = InputFilter.FLOAT;
break;
case ( BITFONT ):
_myInputFilter = InputFilter.BITFONT;
break;
default:
_myInputFilter = InputFilter.DEFAULT;
break;
}
return this;
}
@Override public Textfield setValue( float theValue ) {
// use setText(String) instead
return this;
}
@Override protected void updateFont( ControlFont theControlFont ) {
super.updateFont( theControlFont );
}
public Textfield setValue( String theText ) {
_myTextBuffer = new StringBuffer( theText );
setIndex( _myTextBuffer.length( ) );
return this;
}
public Textfield setText( String theText ) {
return setValue( theText );
}
public Textfield clear( ) {
// create a new text buffer
_myTextBuffer = new StringBuffer( );
// reset the buffer index
setIndex( 0 );
return this;
}
public Textfield setAutoClear( boolean theValue ) {
autoclear = theValue;
return this;
}
public boolean isAutoClear( ) {
return autoclear;
}
@Override protected void mousePressed( ) {
if ( isActive ) {
// TODO System.out.println("adjust cursor");
}
int x = ( int ) ( getControlWindow( ).mouseX - x( getAbsolutePosition( ) ) );
int y = ( int ) ( getControlWindow( ).mouseY - y( getAbsolutePosition( ) ) );
// TODO System.out.println(x + ":" + y);
setFocus( true );
}
@Override protected void mouseReleasedOutside( ) {
if ( isKeepFocus == false ) {
isTexfieldActive = isActive = false;
}
}
public int getIndex( ) {
return _myTextBufferIndex;
}
public String getText( ) {
return _myTextBuffer.toString( );
}
public Textfield setColor( int theColor ) {
getValueLabel( ).setColor( theColor );
return this;
}
public Textfield setColorCursor( int theColor ) {
_myColorCursor = theColor;
return this;
}
@Override public Textfield setSize( int theWidth , int theHeight ) {
super.setSize( theWidth , theHeight );
buffer = cp5.papplet.createGraphics( getWidth( ) , getHeight( ) );
return this;
}
@Override public void draw( PGraphics theGraphics ) {
theGraphics.pushStyle( );
theGraphics.fill( color.getBackground( ) );
theGraphics.pushMatrix( );
theGraphics.translate( x( position ) , y( position ) );
theGraphics.rect( 0 , 0 , getWidth( ) , getHeight( ) );
theGraphics.noStroke( );
theGraphics.fill( _myColorCursor );
theGraphics.pushMatrix( );
theGraphics.pushStyle( );
buffer.beginDraw( );
buffer.background( 0 , 0 );
final String text = passCheck( getText( ) );
final int textWidth = ControlFont.getWidthFor( text.substring( 0 , _myTextBufferIndex ) , _myValueLabel , buffer );
final int dif = PApplet.max( textWidth - _myValueLabel.getWidth( ) , 0 );
final int _myTextBufferIndexPosition = ControlFont.getWidthFor( text.substring( 0 , _myTextBufferIndex ) , _myValueLabel , buffer );
_myValueLabel.setText( text );
_myValueLabel.draw( buffer , -dif , 0 , this );
buffer.noStroke( );
if ( isTexfieldActive ) {
if ( !cp5.papplet.keyPressed ) {
buffer.fill( _myColorCursor , PApplet.abs( PApplet.sin( cp5.papplet.frameCount * 0.05f )) * 255 );
} else {
buffer.fill( _myColorCursor );
}
buffer.rect( PApplet.max( 1 , PApplet.min( _myTextBufferIndexPosition , _myValueLabel.getWidth( ) - 3 ) ) , 0 , 1 , getHeight( ) );
}
buffer.endDraw( );
theGraphics.image( buffer , 0 , 0 );
theGraphics.popStyle( );
theGraphics.popMatrix( );
theGraphics.fill( isTexfieldActive ? color.getActive( ) : color.getForeground( ) );
theGraphics.rect( 0 , 0 , getWidth( ) , 1 );
theGraphics.rect( 0 , getHeight( ) - 1 , getWidth( ) , 1 );
theGraphics.rect( -1 , 0 , 1 , getHeight( ) );
theGraphics.rect( getWidth( ) , 0 , 1 , getHeight( ) );
_myCaptionLabel.draw( theGraphics , 0 , 0 , this );
theGraphics.popMatrix( );
theGraphics.popStyle( );
}
private String passCheck( String label ) {
if ( !isPasswordMode ) {
return label;
}
String newlabel = "";
for ( int i = 0 ; i < label.length( ) ; i++ ) {
newlabel += "*";
}
return newlabel;
}
public void keyEvent( KeyEvent theKeyEvent ) {
if ( isUserInteraction && isTexfieldActive && isActive && theKeyEvent.getAction( ) == KeyEvent.PRESS ) {
if ( ignorelist.contains( cp5.getKeyCode( ) ) ) {
return;
}
if ( keyMapping.containsKey( cp5.getKeyCode( ) ) ) {
keyMapping.get( cp5.getKeyCode( ) ).execute( );
} else {
keyMapping.get( DEFAULT ).execute( );
}
}
}
/**
* make the controller execute a return event. submit the current content of the texfield.
*
*/
public Textfield submit( ) {
keyMapping.get( ENTER ).execute( );
return this;
}
public String[] getTextList( ) {
String[] s = new String[ _myHistory.size( ) ];
_myHistory.toArray( s );
return s;
}
private Textfield setIndex( int theIndex ) {
_myTextBufferIndex = theIndex;
return this;
}
interface TextfieldCommand {
void execute( );
}
class InsertCharacter implements TextfieldCommand {
public void execute( ) {
if ( ( int ) ( cp5.getKey( ) ) == 65535 ) {
return;
}
if ( _myInputFilter.apply( cp5.getKey( ) ) ) {
_myTextBuffer.insert( _myTextBufferIndex , ( char ) cp5.getKey( ) );
setIndex( _myTextBufferIndex + 1 );
}
}
}
class Enter implements TextfieldCommand {
public void execute( ) {
setStringValue( _myTextBuffer.toString( ) );
broadcast( );
// update current buffer with the last item inside the input history
_myHistory.set( _myHistory.size( ) - 1 , _myTextBuffer.toString( ) );
// set the history index to our last item
_myHistoryIndex = _myHistory.size( );
// add a new and empty buffer to the history
_myHistory.add( "" );
if ( autoclear ) {
clear( );
}
}
}
class DeleteCharacter implements TextfieldCommand {
public void execute( ) {
if ( _myTextBuffer.length( ) > 0 && _myTextBufferIndex > 0 ) {
_myTextBuffer.deleteCharAt( _myTextBufferIndex - 1 );
setIndex( _myTextBufferIndex - 1 );
}
}
}
class MoveLeft implements TextfieldCommand {
public void execute( ) {
setIndex( ( ( cp5.modifiers & Event.META ) > 0 ) ? 0 : PApplet.max( 0 , _myTextBufferIndex - 1 ) );
}
}
class MoveRight implements TextfieldCommand {
public void execute( ) {
setIndex( ( ( cp5.modifiers & Event.META ) > 0 ) ? _myTextBuffer.length( ) : PApplet.min( _myTextBuffer.length( ) , _myTextBufferIndex + 1 ) );
}
}
class MoveUp implements TextfieldCommand {
public void execute( ) {
if ( _myHistoryIndex == 0 ) {
return;
}
_myHistoryIndex = PApplet.max( 0 , --_myHistoryIndex );
_myTextBuffer = new StringBuffer( _myHistory.get( _myHistoryIndex ) );
setIndex( _myTextBuffer.length( ) );
}
}
class MoveDown implements TextfieldCommand {
public void execute( ) {
if ( _myHistoryIndex >= _myHistory.size( ) - 1 ) {
return;
}
_myHistoryIndex = PApplet.min( _myHistory.size( ) - 1 , ++_myHistoryIndex );
_myTextBuffer = new StringBuffer( _myHistory.get( _myHistoryIndex ) );
setIndex( _myTextBuffer.length( ) );
}
}
}