/***********************************************************************
* mt4j Copyright (c) 2008 - 2009, C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package org.mt4j.components.visibleComponents.widgets;
import java.util.ArrayList;
import java.util.Iterator;
import javax.media.opengl.GL;
import org.mt4j.components.TransformSpace;
import org.mt4j.components.clipping.Clip;
import org.mt4j.components.visibleComponents.font.BitmapFont;
import org.mt4j.components.visibleComponents.font.BitmapFontCharacter;
import org.mt4j.components.visibleComponents.font.IFont;
import org.mt4j.components.visibleComponents.font.IFontCharacter;
import org.mt4j.components.visibleComponents.shapes.MTRectangle;
import org.mt4j.components.visibleComponents.widgets.keyboard.ITextInputListener;
import org.mt4j.components.visibleComponents.widgets.keyboard.MTKeyboard;
import org.mt4j.input.inputProcessors.componentProcessors.lassoProcessor.IdragClusterable;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.MTColor;
import org.mt4j.util.math.Tools3D;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;
import processing.core.PApplet;
import processing.core.PGraphics;
/**
* The Class MTTextArea. This widget allows to display text with a specified font.
* If the constructor with no fixed text are dimensions is used, the text area will
* expand itself to fit the text in.
* <br>
* If the constructor with fixed dimensions is used, the text will have word wrapping
* and be clipped to the specified dimensions.
*
* @author Christopher Ruff
*/
public class MTTextArea extends MTRectangle implements IdragClusterable, ITextInputListener{
/** The pa. */
private PApplet pa;
/** The character list. */
private ArrayList<IFontCharacter> characterList;
/** The font. */
private IFont font;
/** The font b box height. */
private int fontHeight;
/** The show caret. */
private boolean showCaret;
/** The show caret time. */
private long showCaretTime = 1500; //ms
/** The caret time counter. */
private int caretTimeCounter = 0;
/** The enable caret. */
private boolean enableCaret;
/** The caret width. */
private float caretWidth;
/** The upper left local. */
private Vertex upperLeftLocal;
private float innerPadding;
private float totalScrollTextX;
private float totalScrollTextY;
//TODO create mode : expand vertically but do word wrap horizontally
//TODO different font sizes in one textarea?
private static final int MODE_EXPAND = 0;
private static final int MODE_WRAP = 1;
private int mode;
/**
* Instantiates a new mT text area.
* This constructor creates a textarea with fixed dimensions.
* If the text exceeds the dimensions the text is clipped.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param font the font
* @param pApplet the applet
*/
public MTTextArea(float x, float y, float width, float height,IFont font, PApplet pApplet) {
super( 0, -1 * font.getFontMaxAscent(), //upper left corner
width, //width
height, //height
pApplet);
init(pApplet, font, MODE_WRAP);
//Position textarea at x,y
PositionAnchor prevAnchor = this.getAnchor();
this.setAnchor(PositionAnchor.UPPER_LEFT);
this.setPositionGlobal(new Vector3D(x,y,0));
this.setAnchor(prevAnchor);
}
/**
* Instantiates a new text area. This constructor creates
* a text area with variable dimensions that expands itself when text is added.
*
* @param pApplet the applet
* @param font the font
*/
public MTTextArea(PApplet pApplet, IFont font) {
super( 0, -1 * font.getFontMaxAscent(), //upper left corner
0, //width
0, //height
pApplet);
init(pApplet, font, MODE_EXPAND);
//Position textarea at 0,0
PositionAnchor prevAnchor = this.getAnchor();
this.setAnchor(PositionAnchor.UPPER_LEFT);
this.setPositionGlobal(Vector3D.ZERO_VECTOR);
this.setAnchor(prevAnchor);
//Expand vertically at enter
this.setHeightLocal(this.getTotalLinesHeight());
this.setWidthLocal(getMaxLineWidth());
}
private void init(PApplet pApplet, IFont font, int mode){
this.pa = pApplet;
this.font = font;
this.mode = mode;
switch (this.mode) {
case MODE_EXPAND:
//We dont have to clip since we expand the area
break;
case MODE_WRAP:
if (MT4jSettings.getInstance().isOpenGlMode()){
//Clip the text to the area
this.setClip(new Clip(pApplet, this.getVerticesLocal()[0].x, this.getVerticesLocal()[0].y, this.getWidthXY(TransformSpace.LOCAL), this.getHeightXY(TransformSpace.LOCAL)));
}
break;
default:
break;
}
characterList = new ArrayList<IFontCharacter>();
if (MT4jSettings.getInstance().isOpenGlMode())
this.setUseDirectGL(true);
fontHeight = font.getFontAbsoluteHeight();
caretWidth = 0;
innerPadding = 5;
showCaret = false;
enableCaret = false;
showCaretTime = 1000;
//upper left corner of the textarea rectangle, used later when re-setting the height
upperLeftLocal = new Vertex(0, -1 * font.getFontMaxAscent(), 0);
this.setStrokeWeight(1.5f);
this.setStrokeColor(new MTColor(255, 255, 255, 255));
this.setDrawSmooth(true);
//Draw this component and its children above
//everything previously drawn and avoid z-fighting
this.setDepthBufferDisabled(true);
this.totalScrollTextX = 0.0f;
this.totalScrollTextY = 0.0f;
}
/* (non-Javadoc)
* @see com.jMT.components.MTBaseComponent#updateComponent(long)
*/
@Override
public void updateComponent(long timeDelta) {
super.updateComponent(timeDelta);
if (enableCaret){
caretTimeCounter+=timeDelta;
if (caretTimeCounter >= showCaretTime && !showCaret){
showCaret = true;
caretTimeCounter = 0;
}else if (caretTimeCounter >= showCaretTime && showCaret){
showCaret = false;
caretTimeCounter = 0;
}
}
}
@Override
public void preDraw(PGraphics graphics) {
super.preDraw(graphics);
if (this.mode == MODE_WRAP && this.getClip() != null && !this.isNoStroke()){
noStrokeSettingSaved = this.isNoStroke();
this.setNoStroke(true);
}
}
/* (non-Javadoc)
* @see com.jMT.components.visibleComponents.shapes.MTPolygon#drawComponent()
*/
@Override
public void drawComponent(PGraphics g) {
super.drawComponent(g);
//Add caret if its time
if (enableCaret && showCaret){
characterList.add(this.getFont().getFontCharacterByUnicode("|"));
}
int charListSize = characterList.size();
float thisLineTotalXAdvancement = 0;
float lastXAdvancement = innerPadding;
// /*//
//To set caret at start pos when charlist empty
if (enableCaret && showCaret && charListSize == 1){
lastXAdvancement = 0;
}
// */
if (this.isUseDirectGL()){
GL gl = Tools3D.beginGL(pa);
gl.glPushMatrix(); //FIXME TEST
gl.glTranslatef(totalScrollTextX, totalScrollTextY, 0);//FIXME TEST
for (int i = 0; i < charListSize; i++) {
IFontCharacter character = characterList.get(i);
//Step to the right by the amount of the last characters x advancement
gl.glTranslatef(lastXAdvancement, 0, 0);
//Save total amount gone to the right in this line
thisLineTotalXAdvancement += lastXAdvancement;
lastXAdvancement = 0;
//Draw the letter
character.drawComponent(gl);
//Check if newLine occurs, goto start at new line
if (character.getUnicode().equals("\n")){
gl.glTranslatef(-thisLineTotalXAdvancement, fontHeight, 0);
thisLineTotalXAdvancement = 0;
lastXAdvancement = innerPadding;
}else{
//If caret is showing and we are at index one before caret calc the advancement to include the caret in the text area
if (enableCaret && showCaret && i == charListSize-2){
if (character.getUnicode().equals("\t")){
lastXAdvancement = character.getHorizontalDist() - character.getHorizontalDist()/20;
}else{
//approximated value, cant get the real one
lastXAdvancement = 2+character.getHorizontalDist() - (character.getHorizontalDist()/3);
}
}else{
lastXAdvancement = character.getHorizontalDist();
}
}
}
gl.glPopMatrix(); //FIXME TEST
Tools3D.endGL(pa);
}
else{ //P3D rendering
g.pushMatrix(); //FIXME TEST
g.translate(totalScrollTextX, totalScrollTextY, 0);//FIXME TEST
for (int i = 0; i < charListSize; i++) {
IFontCharacter character = characterList.get(i);
//Step to the right by the amount of the last characters x advancement
pa.translate(lastXAdvancement, 0, 0); //original
//Save total amount gone to the right in this line
thisLineTotalXAdvancement += lastXAdvancement;
lastXAdvancement = 0;
//Draw the letter
character.drawComponent(g);
//Check if newLine occurs, goto start at new line
if (character.getUnicode().equals("\n")){
pa.translate(-thisLineTotalXAdvancement, fontHeight, 0);
thisLineTotalXAdvancement = 0;
lastXAdvancement = innerPadding;
}else{
//If caret is showing and we are at index one before caret calc the advancement
if (enableCaret && showCaret && i == charListSize-2){
if (character.getUnicode().equals("\t")){
lastXAdvancement = character.getHorizontalDist() - character.getHorizontalDist()/20;
}else{
//approximated value, cant get the real one
lastXAdvancement = 2+character.getHorizontalDist() - (character.getHorizontalDist()/3);
}
}else{
lastXAdvancement = character.getHorizontalDist();
}
}
}
g.popMatrix();
}
//remove caret
if (enableCaret && showCaret){
characterList.remove(charListSize-1);
}
}
private boolean noStrokeSettingSaved;
@Override
public void postDraw(PGraphics graphics) {
super.postDraw(graphics);
if (this.mode == MODE_WRAP && this.getClip()!=null && !noStrokeSettingSaved){
this.setNoStroke(noStrokeSettingSaved);
boolean noFillSavedSetting = this.isNoFill();
this.setNoFill(true);
super.drawComponent(graphics);//Draw only stroke line after we ended clipping do preserve anti aliasing - hack
this.setNoFill(noFillSavedSetting);
}
}
//FIXME TEST
protected void scrollTextX(float amount){
this.totalScrollTextX += amount;
}
protected void scrollTextY(float amount){
this.totalScrollTextY += amount;
}
protected float getScrollTextX() {
return this.totalScrollTextX;
}
protected float getScrollTextY() {
return this.totalScrollTextY;
}
//FIXME TEST ?
/**
* Changes the texture filtering for the textarea's bitmap font.
* (if a bitmap font is used).
* If the parameter is "true" this will allow the text being scaled without getting
* too pixelated. If the text isnt going to be scaled ever, it is best to leave or
* set this to "false" for a sharper text.
* <br>NOTE: Only applies if OpenGL is the renderer and the textarea uses a bitmap font.
* <br>NOTE: This affects the whole bitmap font so if it is used elsewhere it is changed
* there, too.
*
* @param scalable the new bitmap font scalable
*/
public void setBitmapFontTextureFiltered(boolean scalable){
if (MT4jSettings.getInstance().isOpenGlMode()){
if (this.getFont() instanceof BitmapFont){
BitmapFont font = (BitmapFont)this.getFont();
IFontCharacter[] characters = font.getCharacters();
for (int i = 0; i < characters.length; i++) {
IFontCharacter fontCharacter = characters[i];
if (fontCharacter instanceof BitmapFontCharacter) {
BitmapFontCharacter bChar = (BitmapFontCharacter) fontCharacter;
bChar.setTextureFiltered(scalable);
}
}
}
}
}
//FIXME REMOVE!?
// private boolean filteringDone;
// private boolean isBitmapFont;
//
// @Override
// public void setMatricesDirty(boolean baseMatrixDirty) {
// super.setMatricesDirty(baseMatrixDirty);
//
// if (isBitmapFont && !filteringDone){
//// filteringDone = true;
//
// MTComponent current = this;
// boolean hasScale;
// do {
// Matrix local = current.getLocalMatrix();
// current = current.getParent();
// } while (current != null);
// while (current.getParent() != null) {
//
//
// }
//
// //TODO change fonts' filtering from NEAREST to LINEAR once after scaling is done
// }
// }
//
// private boolean checkForScaling(){
// return checkForScalingRecursive(this);
// }
//
// private MTComponent checkForScalingRecursive(MTComponent current){
// //System.out.println("Processing: " + current.getName());
// if (current.getParent() != null){
//
// }
// }
/**
* Sets the width local.
*
* @param width the new width local
*/
@Override
public void setWidthLocal(float width){
Vertex[] v = this.getVerticesLocal();
MTColor c = this.getFillColor();
this.setVertices(
new Vertex[]{
v[0],
new Vertex(width, v[1].getY(), v[1].getZ(), c.getR(), c.getG(), c.getB(), c.getAlpha()),
new Vertex(width, v[2].getY(), v[2].getZ(), c.getR(), c.getG(), c.getB(), c.getAlpha()),
v[3],
v[4]});
}
/**
* Sets the height local.
*
* @param height the new height local
*/
@Override
public void setHeightLocal(float height){
Vertex[] v = this.getVerticesLocal();
// this.setVertices(
// new Vertex[]{
// new Vertex(v[2].getX(), upperLeftLocal.y + height, v[2].getZ(), this.getFillRed(), this.getFillGreen(), this.getFillBlue(), this.getFillAlpha()),
// v[0],
// v[1] ,
// new Vertex(v[2].getX(), upperLeftLocal.y + height, v[2].getZ(), this.getFillRed(), this.getFillGreen(), this.getFillBlue(), this.getFillAlpha()),
// new Vertex(v[3].getX(), upperLeftLocal.y + height, v[3].getZ(), this.getFillRed(), this.getFillGreen(), this.getFillBlue(), this.getFillAlpha()),
// v[4]});
this.setVertices(new Vertex[]{
new Vertex(v[0].x, -font.getFontMaxAscent() - innerPadding, v[0].z, v[0].getTexCoordU(), v[0].getTexCoordV(), v[0].getR(), v[0].getG(), v[0].getB(), v[0].getA()),
new Vertex(v[1].x, -font.getFontMaxAscent() - innerPadding, v[1].z, v[1].getTexCoordU(), v[1].getTexCoordV(), v[1].getR(), v[1].getG(), v[1].getB(), v[1].getA()),
new Vertex(v[2].x, -font.getFontMaxAscent() - innerPadding + height + (2 * innerPadding), v[2].z, v[2].getTexCoordU(), v[2].getTexCoordV(), v[2].getR(), v[2].getG(), v[2].getB(), v[2].getA()),
new Vertex(v[3].x, -font.getFontMaxAscent() - innerPadding + height + (2 * innerPadding), v[3].z, v[3].getTexCoordU(), v[3].getTexCoordV(), v[3].getR(), v[3].getG(), v[3].getB(), v[3].getA()),
new Vertex(v[4].x, -font.getFontMaxAscent() - innerPadding, v[4].z, v[4].getTexCoordU(), v[4].getTexCoordV(), v[4].getR(), v[4].getG(), v[4].getB(), v[4].getA()),
});
}
/**
* Appends the string to the textarea.
*
* @param string the string
*/
synchronized public void appendText(String string){
for (int i = 0; i < string.length(); i++) {
appendCharByUnicode(string.substring(i, i+1));
}
}
/**
* Sets the provided string as the text of this textarea.
*
* @param string the string
*/
synchronized public void setText(String string){
clear();
for (int i = 0; i < string.length(); i++) {
appendCharByUnicode(string.substring(i, i+1));
}
}
/* (non-Javadoc)
* @see mTouch.components.visibleComponents.keyboard.ITextInputAcceptor#getText()
*/
public String getText(){
String returnString = "";
for (Iterator<IFontCharacter> iter = this.characterList.iterator(); iter.hasNext();) {
IFontCharacter character = (IFontCharacter) iter.next();
String unicode = character.getUnicode();
if (unicode.equalsIgnoreCase("tab")){
returnString += " ";
}
// else if (unicode.equalsIgnoreCase("\n")){
// returnString += " ";
// }
else{
returnString += unicode;
}
}
return returnString;
}
/**
* Append char by name.
*
* @param characterName the character name
*/
synchronized public void appendCharByName(String characterName){
//Get the character from the font
IFontCharacter character = font.getFontCharacterByName(characterName);
if (character == null){
System.err.println("Error adding character with name '" + characterName + "' to the textarea. The font couldnt find the character. -> Trying to use 'missing glyph'");
character = font.getFontCharacterByName("missing-glyph");
if (character != null)
addCharacter(character);
}else{
addCharacter(character);
}
}
/* (non-Javadoc)
* @see com.jMT.components.visibleComponents.keyboard.ITextInputAcceptor#appendCharByUnicode(java.lang.String)
*/
synchronized public void appendCharByUnicode(String unicode){
//Get the character from the font
IFontCharacter character = font.getFontCharacterByUnicode(unicode);
if (character == null){
System.err.println("Error adding character with unicode '" + unicode + "' to the textarea. The font couldnt find the character. ->Trying to use 'missing glyph'");
character = font.getFontCharacterByUnicode("missing-glyph");
if (character != null)
addCharacter(character);
}else{
addCharacter(character);
}
}
/**
* Gets the characters.
* @return the characters
*/
public IFontCharacter[] getCharacters(){
return this.characterList.toArray(new IFontCharacter[this.characterList.size()]);
}
/**
* Adds the character.
*
* @param character the character
*/
private void addCharacter(IFontCharacter character){
this.characterList.add(character);
this.characterAdded(character);
}
protected void characterAdded(IFontCharacter character){
switch (this.mode) {
case MODE_EXPAND:
if (character.getUnicode().equals("\n")){
//Expand vertically at enter
this.setHeightLocal(this.getTotalLinesHeight());
//TODO make behaviour settable
//Moves the Textarea up at a enter character instead of down
this.translate(new Vector3D(0, -fontHeight, 0));
}else{
//Expand the textbox to the extend of the widest line width
this.setWidthLocal(getMaxLineWidth());
}
break;
case MODE_WRAP:
float localWidth = this.getWidthXY(TransformSpace.LOCAL);
// float maxLineWidth = this.getMaxLineWidth(); //TODO last line width instead?
float maxLineWidth = this.getLastLineWidth();
if (maxLineWidth > localWidth && this.characterList.size() > 0) {
this.characterList.add(this.characterList.size() -1 , this.font.getFontCharacterByUnicode("\n"));
}
break;
default:
break;
}
}
protected void characterRemoved(IFontCharacter character){
switch (this.mode) {
case MODE_EXPAND:
//Resize text field
if (character.getUnicode().equals("\n")){
//Reduce field vertically at enter
this.setHeightLocal(this.getTotalLinesHeight());
//makes the textarea go down when a line is removed instead staying at the same loc.
this.translate(new Vector3D(0, fontHeight, 0));
}else{
//Reduce field horizontally
this.setWidthLocal(getMaxLineWidth());
}
break;
case MODE_WRAP:
break;
default:
break;
}
}
/**
* Removes the last character in the textarea.
*/
synchronized public void removeLastCharacter(){
if (this.characterList.isEmpty())
return;
//REMOVE THE CHARACTER
IFontCharacter lastCharacter = this.characterList.get(this.characterList.size()-1);
this.characterList.remove(this.characterList.size()-1);
this.characterRemoved(lastCharacter);
}
/**
* resets the textarea, clears all characters.
*/
public void clear(){
while (!characterList.isEmpty()){
removeLastCharacter();
}
}
protected float getLastLineWidth(){
float currentLineWidth = 2 * this.getInnerPadding() + caretWidth;
for (int i = 0; i < this.characterList.size(); i++) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals("\n")){
currentLineWidth = 2 * this.getInnerPadding() + caretWidth;;
}else{
currentLineWidth += character.getHorizontalDist();
}
}
return currentLineWidth;
}
/**
* Gets the max line width. The padding is also added.
*
* @return the max line width
*/
protected float getMaxLineWidth(){
float currentLineWidth = 2 * this.getInnerPadding() + caretWidth;
float maxWidth = currentLineWidth;
for (int i = 0; i < this.characterList.size(); i++) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals("\n")){
if (currentLineWidth > maxWidth){
maxWidth = currentLineWidth;
}
currentLineWidth = 2 * this.getInnerPadding() + caretWidth;
}else{
currentLineWidth += character.getHorizontalDist();
if (currentLineWidth > maxWidth){
maxWidth = currentLineWidth;
}
}
}
return maxWidth;
}
/**
* Gets the total lines height. Padding is not included
*
* @return the total lines height
*/
protected float getTotalLinesHeight(){
float height = font.getFontAbsoluteHeight() ;//
for (int i = 0; i < this.characterList.size(); i++) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals("\n")){
height += fontHeight;
}
}
return height;
}
public float getInnerPadding(){
return this.innerPadding;
}
public void setInnerPadding(float innerPadding){
this.innerPadding = innerPadding;
}
/**
* Gets the line count.
*
* @return the line count
*/
public int getLineCount(){
int count = 0;
for (int i = 0; i < this.characterList.size(); i++) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals("\n")){
count++;
}
}
return count;
}
/**
* Gets the font.
*
* @return the font
*/
public IFont getFont() {
return font;
}
/**
* Snap to keyboard.
*
* @param mtKeyboard the mt keyboard
*/
public void snapToKeyboard(MTKeyboard mtKeyboard){
//OLD WAY
// this.translate(new Vector3D(30, -(getFont().getFontAbsoluteHeight() * (getLineCount())) + getFont().getFontMaxDescent() - borderHeight, 0));
mtKeyboard.addChild(this);
this.setPositionRelativeToParent(new Vector3D(40, -this.getHeightXY(TransformSpace.LOCAL)*0.5f));
}
/* (non-Javadoc)
* @see com.jMT.input.inputAnalyzers.clusterInputAnalyzer.IdragClusterable#isSelected()
*/
public boolean isSelected() {
// TODO Auto-generated method stub
return false;
}
/* (non-Javadoc)
* @see com.jMT.input.inputAnalyzers.clusterInputAnalyzer.IdragClusterable#setSelected(boolean)
*/
public void setSelected(boolean selected) {
// TODO Auto-generated method stub
}
/**
* Checks if is enable caret.
*
* @return true, if is enable caret
*/
public boolean isEnableCaret() {
return enableCaret;
}
/**
* Sets the enable caret.
*
* @param enableCaret the new enable caret
*/
public void setEnableCaret(boolean enableCaret) {
if (this.getFont().getFontCharacterByUnicode("|") != null){
this.enableCaret = enableCaret;
if (enableCaret){
this.caretWidth = 10;
}else{
this.caretWidth = 0;
}
if (this.mode == MODE_EXPAND){
this.setWidthLocal(this.getMaxLineWidth());
}
}else{
System.err.println("Cant enable caret for this textfield, the font doesent include the letter '|'");
}
}
}